const std = @import("std"); const zigeru = @import("zigeru"); const zircon = @import("zircon"); const Bot = zigeru.bot.Bot; const BotCommand = zigeru.bot.Command; 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); const adapter = bot_adapter.adapter(); // Connect to the IRC server and perform registration. try client.connect(); try client.register(); try client.join("#eru-tests"); // Enter the main loop that keeps reading incoming IRC messages forever. // The client loop accepts a LoopConfig struct with two optional fields. // // These two fields, .msg_callback and .spawn_thread are callback pointers. // You set them to custom functions you define to customize the main loop. // // .msg_callback lets you answer any received IRC messages with another one. // // .spawn_thread lets you tweak if you spawn a thread to run .msg_callback. client.loop(.{ .msg_callback = adapter.callbackFn }) catch |err| { std.debug.print("eru exited with error: {}", .{err}); }; } 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| { if (Command.parse(msg.prefix, msg.targets, msg.text)) |command| { return command.handle(&self.bot); } self.bot.hear(zigeru.bot.Message{ .author = msg.prefix.?.nick orelse "unknown", .timestamp = std.time.timestamp(), .content = msg.text, }); }, else => {}, } return null; } pub fn adapter(self: *BotAdapter) Adapter { return .{ .ptr = self, .callbackFn = self.callback, }; } }; pub const Command = struct { command: BotCommand, prefix: ?zircon.Prefix, targets: []const u8, pub fn parse(prefix: ?zircon.Prefix, targets: []const u8, text: []const u8) ?Command { const nick = prefix.?.nick.?; const command = BotCommand.parse(nick, text) orelse return null; return .{ .command = command, .prefix = prefix, .targets = targets, }; } pub fn handle(self: Command, bot: *Bot) ?zircon.Message { return bot.execute(&self.command, self.prefix, self.targets) catch |err| { std.debug.print("Failed to handle {}: {}", .{ self, err }); return null; }; } }; 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", }, }; _ = bot_adapter.callback(msg); 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("hello zig", response.?.PRIVMSG.text); }