chore: make stuff work
This commit is contained in:
parent
936bf470c7
commit
1e4c90822a
4 changed files with 267 additions and 117 deletions
33
build.zig
33
build.zig
|
|
@ -21,6 +21,12 @@ pub fn build(b: *std.Build) void {
|
|||
// target and optimize options) will be listed when running `zig build --help`
|
||||
// in this directory.
|
||||
|
||||
// Dependencies
|
||||
const zircon = b.dependency("zircon", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
// This creates a module, which represents a collection of source files alongside
|
||||
// some compilation options, such as optimization mode and linked system libraries.
|
||||
// Zig modules are the preferred way of making Zig code available to consumers.
|
||||
|
|
@ -39,6 +45,9 @@ pub fn build(b: *std.Build) void {
|
|||
// Later on we'll use this module as the root module of a test executable
|
||||
// which requires us to specify a target.
|
||||
.target = target,
|
||||
.imports = &.{
|
||||
.{ .name = "zircon", .module = zircon.module("zircon") },
|
||||
},
|
||||
});
|
||||
|
||||
// Here we define an executable. An executable needs to have a root module
|
||||
|
|
@ -79,17 +88,11 @@ pub fn build(b: *std.Build) void {
|
|||
// can be extremely useful in case of collisions (which can happen
|
||||
// importing modules from different packages).
|
||||
.{ .name = "zigeru", .module = mod },
|
||||
.{ .name = "zircon", .module = zircon.module("zircon") },
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const zircon = b.dependency("zircon", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
exe.root_module.addImport("zircon", zircon.module("zircon"));
|
||||
mod.addImport("zircon", zircon.module("zircon"));
|
||||
exe.linkLibC();
|
||||
|
||||
// This declares intent for the executable to be installed into the
|
||||
|
|
@ -134,6 +137,21 @@ pub fn build(b: *std.Build) void {
|
|||
// A run step that will run the test executable.
|
||||
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||
|
||||
const bot_tests = b.addTest(.{
|
||||
.root_module = b.addModule("bot", .{
|
||||
.target = target,
|
||||
.root_source_file = b.path("src/bot.zig"),
|
||||
.imports = &.{
|
||||
.{
|
||||
.name = "zircon",
|
||||
.module = zircon.module("zircon"),
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const run_bot_tests = b.addRunArtifact(bot_tests);
|
||||
|
||||
// Creates an executable that will run `test` blocks from the executable's
|
||||
// root module. Note that test executables only test one module at a time,
|
||||
// hence why we have to create two separate ones.
|
||||
|
|
@ -150,6 +168,7 @@ pub fn build(b: *std.Build) void {
|
|||
const test_step = b.step("test", "Run tests");
|
||||
test_step.dependOn(&run_mod_tests.step);
|
||||
test_step.dependOn(&run_exe_tests.step);
|
||||
test_step.dependOn(&run_bot_tests.step);
|
||||
|
||||
// Just like flags, top level steps are also listed in the `--help` menu.
|
||||
//
|
||||
|
|
|
|||
235
src/bot.zig
235
src/bot.zig
|
|
@ -1,62 +1,101 @@
|
|||
const std = @import("std");
|
||||
const zircon = @import("zircon");
|
||||
|
||||
pub const Command = union(enum) {
|
||||
substitute: struct { author: []const u8, needle: []const u8, replacement: []const u8, all: bool },
|
||||
help: void,
|
||||
/// AdminCommand are commands useful for debugging zigeru, since they
|
||||
/// are more spammy than others they are separated and only sent to
|
||||
/// #eru-admin.
|
||||
pub const AdminCommand = union(enum) {
|
||||
backlog: struct { history: u16 },
|
||||
status: void,
|
||||
|
||||
pub fn parse(nick: []const u8, text: []const u8) ?Command {
|
||||
pub fn parse(text: []const u8) ?AdminCommand {
|
||||
if (text.len < 2) return null;
|
||||
|
||||
if (text[0] == 's') {
|
||||
if (text.len == 1) return null;
|
||||
const delim = text[1];
|
||||
var parts = std.mem.splitScalar(u8, text, delim);
|
||||
_ = parts.next() orelse return null; // skip 's'
|
||||
const needle = parts.next() orelse return null;
|
||||
const replacement = parts.next().?;
|
||||
const flags = parts.next();
|
||||
const all = if (flags == null) false else (std.mem.eql(u8, flags.?, "g"));
|
||||
return Command{
|
||||
.substitute = .{
|
||||
.author = nick,
|
||||
.needle = needle,
|
||||
.replacement = replacement,
|
||||
.all = all,
|
||||
},
|
||||
if (std.mem.eql(u8, text, "!status")) {
|
||||
return .status;
|
||||
}
|
||||
|
||||
if (text.len > 8 and std.mem.eql(u8, text[0..8], "!backlog")) {
|
||||
const history = std.fmt.parseInt(u16, text[9..], 10) catch |err| {
|
||||
std.debug.print("failed to parse int ('{s}') with error: {}\n", .{ text[8..], err });
|
||||
return null;
|
||||
};
|
||||
return .{ .backlog = .{ .history = history } };
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, text, "help")) {
|
||||
return Command.help;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const HELP_MESSAGE: []const u8 =
|
||||
\\Welcome to zigeru!
|
||||
\\
|
||||
\\Commands:
|
||||
\\!help:\tSee this message
|
||||
\\s/TYPO/CORRECTION/\tCorrect previous message by replacing TYPO with CORRECTION.
|
||||
;
|
||||
/// Command represents the commands that ordinary IRC users can use.
|
||||
pub const Command = union(enum) {
|
||||
/// `s/<old-word>/<new-word>/`
|
||||
substitute: struct { author: []const u8, needle: []const u8, replacement: []const u8, all: bool = false },
|
||||
/// !help
|
||||
help: void,
|
||||
|
||||
pub const Error = error{
|
||||
OutOfMemory,
|
||||
NoMessage,
|
||||
pub fn parse(nick: []const u8, text: []const u8) ?Command {
|
||||
if (std.mem.eql(u8, text, "!help")) {
|
||||
return .help;
|
||||
}
|
||||
|
||||
if (text[0] == 's') {
|
||||
if (text.len == 1) return null;
|
||||
const delim = switch (text[1]) {
|
||||
'/', '|', '#' => text[1],
|
||||
else => {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
if (std.mem.count(u8, text, &.{delim}) != 3) {
|
||||
// invalid format, we expect three delimiters
|
||||
return null;
|
||||
}
|
||||
var parts = std.mem.splitScalar(u8, text, delim);
|
||||
_ = parts.next() orelse return null;
|
||||
const needle = parts.next().?;
|
||||
const replacement = parts.next().?;
|
||||
return .{
|
||||
.substitute = .{
|
||||
.author = nick,
|
||||
.needle = needle,
|
||||
.replacement = replacement,
|
||||
.all = if (parts.next()) |flags| std.mem.eql(u8, flags, "g") else false,
|
||||
},
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const HELP_MESSAGE: []const u8 = "Skicka `s/TYPO/CORRECTION/` för att ersätta TYPO med CORRECTION i ditt senaste meddelande.";
|
||||
|
||||
pub const Error = error{ OutOfMemory, NoMessage, WriteFailed };
|
||||
|
||||
pub const Message = struct {
|
||||
timestamp: i64,
|
||||
targets: []const u8,
|
||||
author: []const u8,
|
||||
content: []const u8,
|
||||
|
||||
pub fn new_owned(allocator: std.mem.Allocator, timestamp: i64, author: []const u8, targets: []const u8, content: []const u8) Error!Message {
|
||||
return .{
|
||||
.timestamp = timestamp,
|
||||
.targets = try allocator.dupe(u8, targets),
|
||||
.author = try allocator.dupe(u8, author),
|
||||
.content = try allocator.dupe(u8, content),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Message, allocator: std.mem.Allocator) void {
|
||||
allocator.free(self.author);
|
||||
allocator.free(self.content);
|
||||
allocator.free(self.targets);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Bot = struct {
|
||||
backlog: [1024]?Message,
|
||||
sent_messages: std.ArrayList(u8),
|
||||
sent_messages: std.ArrayList([]u8),
|
||||
top: usize,
|
||||
bottom: usize,
|
||||
allocator: std.mem.Allocator,
|
||||
|
|
@ -64,7 +103,7 @@ pub const Bot = struct {
|
|||
pub fn init(allocator: std.mem.Allocator) Error!Bot {
|
||||
return Bot{
|
||||
.backlog = .{null} ** 1024,
|
||||
.sent_messages = try std.ArrayList(u8).initCapacity(allocator, 10),
|
||||
.sent_messages = try std.ArrayList([]u8).initCapacity(allocator, 10),
|
||||
.top = 0,
|
||||
.bottom = 0,
|
||||
.allocator = allocator,
|
||||
|
|
@ -72,28 +111,90 @@ pub const Bot = struct {
|
|||
}
|
||||
|
||||
pub fn deinit(self: *Bot) void {
|
||||
for (self.sent_messages.items) |item| {
|
||||
self.allocator.free(item);
|
||||
}
|
||||
self.sent_messages.deinit(self.allocator);
|
||||
|
||||
var idx = self.previous_idx(self.top);
|
||||
while (idx != self.bottom) : (idx = self.previous_idx(idx)) {
|
||||
if (self.backlog[idx]) |message| {
|
||||
message.deinit(self.allocator);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(self: *Bot, cmd: *const Command, prefix: ?zircon.Prefix, targets: []const u8) Error!zircon.Message {
|
||||
switch (cmd.*) {
|
||||
.substitute => |command| {
|
||||
const prev_msg = self.previous_message_by_author(command.author) orelse return Error.NoMessage;
|
||||
const size = std.mem.replacementSize(u8, prev_msg.content, command.needle, command.replacement);
|
||||
const output = try self.sent_messages.addManyAsSlice(self.allocator, size);
|
||||
_ = std.mem.replace(u8, prev_msg.content, command.needle, command.replacement, output);
|
||||
return zircon.Message{ .PRIVMSG = .{ .targets = targets, .prefix = prefix, .text = output } };
|
||||
const prev_msg = self.previous_message_by_author(command.author, targets) orelse return Error.NoMessage;
|
||||
const output = try std.mem.replaceOwned(
|
||||
u8,
|
||||
self.allocator,
|
||||
prev_msg.content,
|
||||
command.needle,
|
||||
command.replacement,
|
||||
);
|
||||
defer self.allocator.free(output);
|
||||
const quoted_output = try std.fmt.allocPrint(self.allocator, "{s}: \"{s}\"", .{ command.author, output });
|
||||
try self.sent_messages.append(self.allocator, quoted_output);
|
||||
return zircon.Message{
|
||||
.PRIVMSG = .{ .targets = targets, .prefix = prefix, .text = quoted_output },
|
||||
};
|
||||
},
|
||||
.help => {
|
||||
return zircon.Message{ .PRIVMSG = .{ .targets = targets, .prefix = prefix, .text = HELP_MESSAGE } };
|
||||
return zircon.Message{
|
||||
.PRIVMSG = .{ .targets = targets, .prefix = prefix, .text = HELP_MESSAGE },
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute_admin(self: *Bot, cmd: *const AdminCommand, prefix: ?zircon.Prefix, targets: []const u8) Error!zircon.Message {
|
||||
switch (cmd.*) {
|
||||
.status => {
|
||||
const msg = try std.fmt.allocPrint(
|
||||
self.allocator,
|
||||
"heard messages: {}, sent messages: {}, top: {}, bottom: {}",
|
||||
.{ self.no_messages(), self.sent_messages.items.len, self.top, self.bottom },
|
||||
);
|
||||
return .{ .PRIVMSG = .{ .targets = targets, .prefix = prefix, .text = msg } };
|
||||
},
|
||||
.backlog => |backlog| {
|
||||
if (self.top == self.bottom) {
|
||||
return Error.NoMessage;
|
||||
}
|
||||
if (backlog.history > self.no_messages()) {
|
||||
return Error.NoMessage;
|
||||
}
|
||||
const idx = self.previous_idx(self.top - backlog.history);
|
||||
if (self.backlog[idx]) |message| {
|
||||
const quoted_output = try std.fmt.allocPrint(
|
||||
self.allocator,
|
||||
"backlog {}: author: \"{s}\", content: \"{s}\"",
|
||||
.{ backlog.history, message.author, message.content },
|
||||
);
|
||||
try self.sent_messages.append(self.allocator, quoted_output);
|
||||
return .{
|
||||
.PRIVMSG = .{ .targets = targets, .prefix = prefix, .text = quoted_output },
|
||||
};
|
||||
} else return Error.NoMessage;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hear(self: *Bot, msg: Message) void {
|
||||
self.backlog[self.top] = msg;
|
||||
self.top = (self.top + 1) % 1024;
|
||||
if (self.top == self.bottom) self.bottom = (self.bottom + 1) % self.backlog.len;
|
||||
self.top = (self.top + 1) % self.backlog.len;
|
||||
if (self.top == self.bottom) {
|
||||
self.bottom = (self.bottom + 1) % self.backlog.len;
|
||||
// free old message
|
||||
self.allocator.free(self.backlog[self.top].?.author);
|
||||
self.allocator.free(self.backlog[self.top].?.content);
|
||||
self.backlog[self.top] = null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_messages(self: *Bot) usize {
|
||||
|
|
@ -108,7 +209,7 @@ pub const Bot = struct {
|
|||
if (idx == 0) {
|
||||
return self.backlog.len - 1;
|
||||
}
|
||||
return idx - 1;
|
||||
return (idx - 1) % self.backlog.len;
|
||||
}
|
||||
|
||||
fn previous_message(self: *Bot, comptime pred: *const fn (Message) bool) ?Message {
|
||||
|
|
@ -129,14 +230,14 @@ pub const Bot = struct {
|
|||
return null;
|
||||
}
|
||||
|
||||
fn previous_message_by_author(self: *Bot, author: []const u8) ?Message {
|
||||
fn previous_message_by_author(self: *Bot, author: []const u8, targets: []const u8) ?Message {
|
||||
var idx = self.previous_idx(self.top);
|
||||
while (true) : (idx = self.previous_idx(idx)) {
|
||||
if (self.backlog[idx] == null) {
|
||||
return null;
|
||||
}
|
||||
const message = self.backlog[idx] orelse unreachable;
|
||||
if (std.mem.eql(u8, message.author, author)) {
|
||||
if (std.mem.eql(u8, message.author, author) and std.mem.eql(u8, message.targets, targets)) {
|
||||
return message;
|
||||
}
|
||||
if (idx == self.bottom) {
|
||||
|
|
@ -149,7 +250,9 @@ pub const Bot = struct {
|
|||
};
|
||||
|
||||
test "hear_wraps" {
|
||||
var bot = Bot.new(std.testing.allocator);
|
||||
var bot = try Bot.init(std.testing.allocator);
|
||||
defer bot.deinit();
|
||||
|
||||
const testMessage = Message{
|
||||
.author = "Jassob",
|
||||
.timestamp = 12345,
|
||||
|
|
@ -166,7 +269,8 @@ test "hear_wraps" {
|
|||
}
|
||||
|
||||
test "previous_message" {
|
||||
var bot = Bot.new(std.testing.allocator);
|
||||
var bot = try Bot.init(std.testing.allocator);
|
||||
defer bot.deinit();
|
||||
|
||||
const callback = struct {
|
||||
fn callback(_: Message) bool {
|
||||
|
|
@ -179,7 +283,8 @@ test "previous_message" {
|
|||
}
|
||||
|
||||
test "previous_message1" {
|
||||
var bot = Bot.new(std.testing.allocator);
|
||||
var bot = try Bot.init(std.testing.allocator);
|
||||
defer bot.deinit();
|
||||
var contents: [10][]const u8 = .{undefined} ** 10;
|
||||
|
||||
for (0..10) |i| {
|
||||
|
|
@ -207,20 +312,30 @@ test "previous_message1" {
|
|||
}
|
||||
|
||||
test "execute_substitution_no_previous_message" {
|
||||
var bot = Bot.new(std.testing.allocator);
|
||||
var bot = try Bot.init(std.testing.allocator);
|
||||
defer bot.deinit();
|
||||
const cmd = Command{ .substitute = .{ .author = "jassob", .needle = "What", .replacement = "what" } };
|
||||
try std.testing.expectError(Error.NoMessage, bot.execute(&cmd));
|
||||
try std.testing.expectError(Error.NoMessage, bot.execute(&cmd, null, "#test"));
|
||||
}
|
||||
|
||||
test "execute_substitution" {
|
||||
var bot = Bot.new(std.testing.allocator);
|
||||
var bot = try Bot.init(std.testing.allocator);
|
||||
defer bot.deinit();
|
||||
|
||||
// hear original message with typo
|
||||
bot.hear(Message{ .timestamp = 1234, .author = "jassob", .content = "What" });
|
||||
const cmd = Command{ .substitute = .{ .author = "jassob", .needle = "What", .replacement = "what" } };
|
||||
const result = try bot.execute(&cmd);
|
||||
switch (result) {
|
||||
.post_message => |message| {
|
||||
try std.testing.expectEqualDeep(message.content, "what");
|
||||
std.testing.allocator.free(message.content);
|
||||
|
||||
// execute substitution
|
||||
const cmd = Command{
|
||||
.substitute = .{ .author = "jassob", .needle = "What", .replacement = "what" },
|
||||
};
|
||||
const response = try bot.execute(&cmd, null, "#test");
|
||||
|
||||
// expect response matching the correct message
|
||||
switch (response) {
|
||||
.PRIVMSG => |message| {
|
||||
try std.testing.expectEqualDeep(message.text, "jassob: \"what\"");
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
114
src/main.zig
114
src/main.zig
|
|
@ -1,9 +1,13 @@
|
|||
const std = @import("std");
|
||||
const zigeru = @import("zigeru");
|
||||
|
||||
const zircon = @import("zircon");
|
||||
const zigeru = @import("zigeru");
|
||||
|
||||
const Bot = zigeru.bot.Bot;
|
||||
const Error = zigeru.bot.Error;
|
||||
const BotCommand = zigeru.bot.Command;
|
||||
const AdminCommand = zigeru.bot.AdminCommand;
|
||||
const BotMessage = zigeru.bot.Message;
|
||||
|
||||
var debug_allocator = std.heap.DebugAllocator(.{}).init;
|
||||
|
||||
|
|
@ -26,30 +30,23 @@ pub fn main() !void {
|
|||
defer client.deinit();
|
||||
|
||||
var bot_adapter = try BotAdapter.init(allocator);
|
||||
const adapter = bot_adapter.adapter();
|
||||
|
||||
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");
|
||||
|
||||
// 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});
|
||||
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,
|
||||
ptr: *anyopaque,
|
||||
callbackFn: *const fn (*anyopaque, zircon.Message) ?zircon.Message,
|
||||
};
|
||||
|
||||
pub const BotAdapter = struct {
|
||||
|
|
@ -70,47 +67,66 @@ pub const BotAdapter = struct {
|
|||
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);
|
||||
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 (BotCommand.parse(nick, msg.text)) |command| {
|
||||
return self.bot.execute(&command, msg.prefix, msg.targets) catch |err| {
|
||||
const err_msg = switch (err) {
|
||||
Error.NoMessage => "no matching message",
|
||||
Error.OutOfMemory => "out of memory",
|
||||
Error.WriteFailed => "write failed",
|
||||
};
|
||||
return .{ .PRIVMSG = .{ .prefix = msg.prefix, .targets = "#eru-admin", .text = err_msg } };
|
||||
};
|
||||
}
|
||||
self.bot.hear(zigeru.bot.Message{
|
||||
.author = msg.prefix.?.nick orelse "unknown",
|
||||
.timestamp = std.time.timestamp(),
|
||||
.content = msg.text,
|
||||
if (AdminCommand.parse(msg.text)) |command| {
|
||||
return self.bot.execute_admin(&command, msg.prefix, "#eru-admin") catch |err| {
|
||||
const err_msg = switch (err) {
|
||||
Error.NoMessage => "no matching message",
|
||||
Error.OutOfMemory => "out of memory",
|
||||
Error.WriteFailed => "write failed",
|
||||
};
|
||||
return .{ .PRIVMSG = .{ .prefix = msg.prefix, .targets = "#eru-admin", .text = err_msg } };
|
||||
};
|
||||
}
|
||||
self.bot.hear(BotMessage.new_owned(
|
||||
self.allocator,
|
||||
std.time.timestamp(),
|
||||
msg.prefix.?.nick orelse "unknown",
|
||||
msg.targets,
|
||||
msg.text,
|
||||
) catch |err| {
|
||||
const error_msg = switch (err) {
|
||||
Error.OutOfMemory => "eru failed to listen to a message with error: no memory",
|
||||
else => unreachable,
|
||||
};
|
||||
return zircon.Message{
|
||||
.PRIVMSG = .{ .targets = "jassob", .text = error_msg },
|
||||
};
|
||||
});
|
||||
},
|
||||
else => {},
|
||||
else => {
|
||||
std.log.debug("received unknown message {}", .{message});
|
||||
},
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn adapter(self: *BotAdapter) Adapter {
|
||||
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 = 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;
|
||||
.callbackFn = BotAdapter.erased_callback,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
@ -136,5 +152,5 @@ test "substitute" {
|
|||
};
|
||||
const response = bot_adapter.callback(cmd_msg);
|
||||
try std.testing.expect(response != null);
|
||||
try std.testing.expectEqualStrings("hello zig", response.?.PRIVMSG.text);
|
||||
try std.testing.expectEqualStrings("jassob: \"hello zig\"", response.?.PRIVMSG.text);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
//! By convention, root.zig is the root source file when making a library.
|
||||
const std = @import("std");
|
||||
|
||||
pub const bot = @import("bot.zig");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue