const std = @import("std"); const net = @cImport(@cInclude("netinet/in.h")); const socket = @cImport(@cInclude("sys/socket.h")); const cio = @cImport(@cInclude("stdio.h")); const netdb = @cImport(@cInclude("netdb.h")); const assert = std.debug.assert; const warn = std.debug.warn; const irc_port = 6667; const addr = c"irc.esper.net"; const channel = "#openredstone"; const nick = "tbot_zig"; const real = nick; const user = nick; const recv_buf_len = 1024; var recv_buf: [recv_buf_len]u8 = undefined; fn strchr(str: []const u8, char: u8) ?usize { for (str) |c, i| { if (c == char) { return i; } } return null; } test "strings contain characters" { assert(strchr("foobar", 'f').? == 0); assert(strchr("foobar", 'o').? == 1); assert(strchr("foobar", 'b').? == 3); assert(strchr("foobar", 'a').? == 4); assert(strchr("foobar", 'x') == null); } fn streq(s: []const u8, t: []const u8) bool { if (s.len != t.len) { return false; } var i: usize = 0; while (i < s.len) : (i += 1) { if (s[i] != t[i]) { return false; } } return true; } test "strings are equal to each other" { assert(streq("foo bar", "foo bar")); assert(streq("", "")); assert(!streq("foo", "foobar")); assert(!streq("foo", "bar")); } fn strstr(str: []const u8, s: []const u8) ?usize { if (s.len == 0) { return 0; } outer: for (str) |c, i| { if (i + s.len > str.len) { break; } for (s) |sc, si| { if (sc != str[i + si]) { continue :outer; } } return i; } return null; } test "strings contain other strings" { assert(strstr("foobar", "foo").? == 0); assert(strstr("foobar", "bar").? == 3); assert(strstr("foobar", "foox") == null); assert(strstr("foobar", "xfoo") == null); } fn parse_nick(line: []const u8) ?[]const u8 { if (line[0] == ':') { var spce = strchr(line, ' ') orelse return null; var excl = strchr(line, '!') orelse return null; if (spce < excl) { return null; } var nick_end: usize = excl; return line[1..nick_end]; } return null; } test "parse nickname" { const line = ":tyler!testtesttest foo bar :trail\r\n"; assert(streq(parse_nick(line).?, "tyler")); const line2 = "foo bar :trail\r\n"; assert(parse_nick(line2) == null); } fn parse_command(line: []const u8) ?[]const u8 { var cmd_start: usize = 0; var cmd_end: usize = 0; if (line[0] == ':') { cmd_start = strchr(line, ' ') orelse unreachable; cmd_start += 1; cmd_end = strchr(line[cmd_start..], ' ') orelse strchr(line[cmd_start..], '\r') orelse unreachable; cmd_end += cmd_start; } else { cmd_end = strchr(line, ' ') orelse strchr(line, '\r') orelse unreachable; } return line[cmd_start..cmd_end]; } test "parse command" { const line = ":tyler!testtesttest foo bar :trail\r\n"; assert(streq(parse_command(line).?, "foo")); const line2 = "foo bar :trail\r\n"; assert(streq(parse_command(line2).?, "foo")); } const CError = error.CError; fn perror(msg: []const u8) anyerror { cio.perror(msg.ptr); return error.CError; } fn irc_send(sock: i32, value: []const u8) void { // for loop on var args kills compiler // for (args) |arg| { // warn("{}", arg); // socket.send(sock, arg, arg.len, 0); // } // var i: usize = 0; // while (i < args.len) : (i += 1) { // warn("{}", args[i]); // _ = socket.send(sock, args[i], args[i].len, 0); // } // inline for (args) |arg| { // warn("{}", arg); // socket.send(sock, arg, arg.len, 0); // } warn("<< {}\n", value); _ = socket.send(sock, value.ptr, value.len, 0); _ = socket.send(sock, c"\r\n", 2, 0); } pub fn main() !void { var stdout = try std.io.getStdOut(); try stdout.write("Test\n"); const buf_index: usize = 0; const host: ?*netdb.hostent = @ptrCast(?*netdb.hostent, netdb.gethostbyname(addr)) orelse return perror("getaddr"); const sock: i32 = socket.socket(socket.AF_INET, @enumToInt(socket.SOCK_STREAM), 0); if (sock == -1) { return perror("sock"); } var remote = net.sockaddr_in{ .sin_family = socket.AF_INET, .sin_port = net.htons(irc_port), .sin_addr = undefined, .sin_zero = [8]u8{ 0, 0, 0, 0, 0, 0, 0, 0 }, }; // @ptrCast(*net.in_addr, @alignCast(4, host.?.h_addr_list.?[0].?)).*, @memcpy(@ptrCast([*]u8, &remote.sin_addr), host.?.h_addr_list[0], @sizeOf(@typeOf(remote.sin_addr))); var conn_result = socket.connect(sock, @ptrCast([*]const socket.sockaddr, &remote), @sizeOf(@typeOf(remote))); if (conn_result == -1) { return perror("connect"); } irc_send(sock, "USER " ++ nick ++ " 0 * :" ++ nick); irc_send(sock, "NICK " ++ nick); irc_send(sock, "JOIN " ++ channel); var buffer_index: usize = 0; while (true) { // main loop var len: isize = 0; warn("buffer_index: {}\n", buffer_index); len = socket.recv(sock, @ptrCast(?*c_void, &recv_buf[buffer_index]), recv_buf_len - buffer_index, 0); // is there a better way to do this? var buf_view: []u8 = undefined; buf_view.ptr = @ptrCast([*]u8, &recv_buf); buf_view.len = @intCast(usize, len) + buffer_index; var one_line_end = strchr(recv_buf, '\n'); while (one_line_end != null) { warn("buf view at {} - {}\n", @ptrToInt(buf_view.ptr) - @ptrToInt(&recv_buf), @ptrToInt(buf_view.ptr) - @ptrToInt(&recv_buf) + buf_view.len); warn("buf_view len: {}\n", buf_view.len); warn("recv_buf: {x}\n", recv_buf); warn("buf_view: {x}\n", buf_view); warn("one_line_end: {}\n", one_line_end.?); var line = buf_view[0 .. one_line_end.? - 1]; warn(">> {}\n", line); var line_nick = parse_nick(line); var line_command = parse_command(line); warn(" nick: {}\n", line_nick); warn(" command: {}\n", line_command); if (streq(line_command.?, "PING")) { line[1] = 'O'; // @fragile irc_send(sock, line); } if (streq(line_command.?, "MODE")) { irc_send(sock, "PRIVMSG NickServ :IDENTIFY orepassword"); irc_send(sock, "JOIN #openredstone"); } if (strstr(line, "~test")) |_| { irc_send(sock, "PRIVMSG #openredstone :ayy test"); } // var zero_used: usize = 0; // while (buf_view[zero_used] != '\n') : (zero_used += 1) { // buf_view[zero_used] = 0; // } // buf_view[zero_used] = 0; buf_view = buf_view[one_line_end.? + 1 ..]; one_line_end = strchr(buf_view, '\n'); } // warn("recv_buf: 0x{x}\n", @ptrToInt(&recv_buf)); // warn("buf_view: 0x{x}\n", @ptrToInt(buf_view.ptr)); // warn("moving {}\n", recv_buf_len - (@ptrToInt(buf_view.ptr) - @ptrToInt(&recv_buf))); // @memset( // @ptrCast([*]u8, &recv_buf) + buf_view.len, // 0, // recv_buf_len - buf_view.len // ); if (buf_view.len != 0) { @memcpy(&recv_buf, buf_view.ptr, buf_view.len); // var i: usize = buf_view.len; // while (i