zigeru/src/main.zig
2025-11-29 01:05:09 +01:00

140 lines
4.2 KiB
Zig

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);
}