const std = @import("std"); const zigeru = @import("zigeru"); const Bot = zigeru.bot.Bot; const Error = zigeru.bot.Error; const UserCommand = zigeru.commands.UserCommand; const AdminCommand = zigeru.commands.AdminCommand; const BotMessage = zigeru.bot.Message; const BotResponse = zigeru.bot.Response; const zircon = @import("zircon"); var debug_allocator = std.heap.DebugAllocator(.{}).init; pub fn main() !void { const allocator = debug_allocator.allocator(); defer _ = debug_allocator.deinit(); var channels: [0][]const u8 = .{}; // Create a zircon.Client with a given configuration. var client = try zircon.Client.init(allocator, .{ .user = "eru", .nick = "eru", .real_name = "Eru (zigeru) bot", .server = "irc.dtek.se", .port = 6697, .tls = true, .channels = &channels, }); defer client.deinit(); var bot_adapter = try BotAdapter.init(allocator); defer bot_adapter.deinit(); client.register_message_closure(bot_adapter.closure()); // Connect to the IRC server and perform registration. try client.connect(); try client.register(); try client.join("#eru-tests"); try client.join("#eru-admin"); client.loop(.{}) catch |err| { std.debug.print("eru exited with error: {}\n", .{err}); return; }; } pub const Adapter = struct { ptr: *anyopaque, callbackFn: *const fn (*anyopaque, zircon.Message) ?zircon.Message, }; pub const BotAdapter = struct { bot: Bot, allocator: std.mem.Allocator, pub fn init(allocator: std.mem.Allocator) !BotAdapter { return BotAdapter{ .bot = try Bot.init(allocator), .allocator = allocator, }; } pub fn deinit(self: *BotAdapter) void { self.bot.deinit(); } pub fn callback(self: *BotAdapter, message: zircon.Message) ?zircon.Message { switch (message) { .PRIVMSG => |msg| { std.log.debug( "received message: nick {?s}, user: {?s}, host: {?s}, targets: {s}, text: {s}", .{ msg.prefix.?.nick, msg.prefix.?.user, msg.prefix.?.host, msg.targets, msg.text }, ); const nick = if (msg.prefix) |prefix| if (prefix.nick) |nick| nick else "unknown" else "unknown"; if (UserCommand.parse(nick, msg.text)) |cmd| { return toIRC(self.bot.execute(&cmd, msg.targets) catch |err| return report_error(err)); } if (AdminCommand.parse(msg.text)) |cmd| { return toIRC(self.bot.execute_admin(&cmd, "#eru-admin") catch |err| return report_error(err)); } const bot_msg = BotMessage.init_owned( self.allocator, std.time.timestamp(), nick, msg.targets, msg.text, ) catch |err| return report_error(err); self.bot.store(bot_msg); return null; }, .JOIN => |msg| { std.log.debug("received join message: channels {s}", .{msg.channels}); return null; }, else => { std.log.debug("received unknown message {}", .{message}); return null; }, } } fn report_error(err: Error) zircon.Message { const err_msg = switch (err) { Error.NoMessage => "no matching message", Error.OutOfMemory => "out of memory", Error.WriteFailed => "write failed", }; return .{ .PRIVMSG = .{ .prefix = null, .targets = "#eru-admin", .text = err_msg } }; } pub fn erased_callback(self: *anyopaque, message: zircon.Message) ?zircon.Message { const a: *@This() = @ptrCast(@alignCast(self)); return a.callback(message); } pub fn closure(self: *BotAdapter) zircon.MessageClosure { return .{ .ptr = self, .callbackFn = BotAdapter.erased_callback, }; } }; /// toIRC converts a bot response and converts it to a IRC message. fn toIRC(response: BotResponse) zircon.Message { switch (response) { .join => |join| return .{ .JOIN = .{ .prefix = null, .channels = join.channels }, }, .privmsg => |msg| return .{ .PRIVMSG = .{ .prefix = null, .targets = msg.targets, .text = msg.text }, }, } } test "substitute" { var bot_adapter = try BotAdapter.init(std.testing.allocator); defer bot_adapter.deinit(); const prefix = zircon.Prefix{ .nick = "jassob", .user = "jassob", .host = "localhost" }; const msg = zircon.Message{ .PRIVMSG = .{ .prefix = prefix, .targets = "#test", .text = "hello world", }, }; if (bot_adapter.callback(msg)) |_| { @panic("unexpected response"); } const cmd_msg = zircon.Message{ .PRIVMSG = .{ .prefix = prefix, .targets = "#test", .text = "s/world/zig/", }, }; const response = bot_adapter.callback(cmd_msg); try std.testing.expect(response != null); try std.testing.expectEqualStrings("jassob: \"hello zig\"", response.?.PRIVMSG.text); } test "get empty backlog message" { var bot_adapter = try BotAdapter.init(std.testing.allocator); defer bot_adapter.deinit(); const prefix = zircon.Prefix{ .nick = "jassob", .user = "jassob", .host = "localhost" }; const msg = zircon.Message{ .PRIVMSG = .{ .prefix = prefix, .targets = "#eru-admin", .text = "!backlog 0" }, }; try std.testing.expectEqualDeep("no matching message", bot_adapter.callback(msg).?.PRIVMSG.text); }