refactor(bot): introduce response type
The response type holds represents the way a bot can respond to a message it executes and allows us to move the IRC dependency out of bot and into only the main module.
This commit is contained in:
parent
4f2b9cbce2
commit
4d1f22194c
3 changed files with 50 additions and 54 deletions
|
|
@ -152,12 +152,7 @@ pub fn build(b: *std.Build) !void {
|
||||||
})),
|
})),
|
||||||
|
|
||||||
// Our bot tests needs zircon module import,
|
// Our bot tests needs zircon module import,
|
||||||
try testRunWithImports(b, target, "bot", &.{
|
try testRun(b, target, "bot"),
|
||||||
.{
|
|
||||||
.name = "zircon",
|
|
||||||
.module = zircon.module("zircon"),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
// Our module tests for each module we want to add. If
|
// Our module tests for each module we want to add. If
|
||||||
// breaking out new functionality to a module, remember to
|
// breaking out new functionality to a module, remember to
|
||||||
// bump the length of the array above.
|
// bump the length of the array above.
|
||||||
|
|
|
||||||
80
src/bot.zig
80
src/bot.zig
|
|
@ -1,5 +1,4 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const zircon = @import("zircon");
|
|
||||||
|
|
||||||
const zigeru = @import("root.zig");
|
const zigeru = @import("root.zig");
|
||||||
const Buffer = zigeru.buffer.Buffer;
|
const Buffer = zigeru.buffer.Buffer;
|
||||||
|
|
@ -10,6 +9,10 @@ const HELP_MESSAGE: []const u8 = "Send `s/TYPO/CORRECTION/` to replace TYPO with
|
||||||
|
|
||||||
pub const Error = error{ OutOfMemory, NoMessage, WriteFailed };
|
pub const Error = error{ OutOfMemory, NoMessage, WriteFailed };
|
||||||
|
|
||||||
|
/// Message represents a message received by the bot.
|
||||||
|
///
|
||||||
|
/// It is what we store in the backlog and what the substitutions are
|
||||||
|
/// run on.
|
||||||
pub const Message = struct {
|
pub const Message = struct {
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
targets: []const u8,
|
targets: []const u8,
|
||||||
|
|
@ -39,6 +42,19 @@ pub const Message = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Responses from hearing a Message.
|
||||||
|
///
|
||||||
|
/// Response represents the kind of responses the bot can make when
|
||||||
|
/// hearing a message.
|
||||||
|
///
|
||||||
|
/// Responses can be both administrative (i.e. a join-request for a
|
||||||
|
/// channel), or actual responses to a message (e.g. the result of a
|
||||||
|
/// substitution).
|
||||||
|
pub const Response = union(enum) {
|
||||||
|
join: struct { channels: []const u8 },
|
||||||
|
privmsg: struct { targets: []const u8, text: []const u8 },
|
||||||
|
};
|
||||||
|
|
||||||
pub const Bot = struct {
|
pub const Bot = struct {
|
||||||
backlog: Buffer(*const Message, 1024),
|
backlog: Buffer(*const Message, 1024),
|
||||||
outbox: Buffer([]u8, 1024),
|
outbox: Buffer([]u8, 1024),
|
||||||
|
|
@ -78,12 +94,7 @@ pub const Bot = struct {
|
||||||
self.backlog.deinit();
|
self.backlog.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(
|
pub fn execute(self: *Bot, cmd: *const UserCommand, targets: []const u8) Error!Response {
|
||||||
self: *Bot,
|
|
||||||
cmd: *const UserCommand,
|
|
||||||
prefix: ?zircon.Prefix,
|
|
||||||
targets: []const u8,
|
|
||||||
) Error!zircon.Message {
|
|
||||||
switch (cmd.*) {
|
switch (cmd.*) {
|
||||||
.substitute => |command| {
|
.substitute => |command| {
|
||||||
const prev_msg = self.previous_message_by_author(
|
const prev_msg = self.previous_message_by_author(
|
||||||
|
|
@ -107,28 +118,21 @@ pub const Bot = struct {
|
||||||
.{ command.author, output },
|
.{ command.author, output },
|
||||||
);
|
);
|
||||||
self.outbox.append(quoted_output);
|
self.outbox.append(quoted_output);
|
||||||
return zircon.Message{ .PRIVMSG = .{
|
return .{ .privmsg = .{
|
||||||
.targets = targets,
|
.targets = targets,
|
||||||
.prefix = prefix,
|
|
||||||
.text = quoted_output,
|
.text = quoted_output,
|
||||||
} };
|
} };
|
||||||
},
|
},
|
||||||
.help => {
|
.help => {
|
||||||
return zircon.Message{ .PRIVMSG = .{
|
return .{ .privmsg = .{
|
||||||
.targets = targets,
|
.targets = targets,
|
||||||
.prefix = prefix,
|
|
||||||
.text = HELP_MESSAGE,
|
.text = HELP_MESSAGE,
|
||||||
} };
|
} };
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_admin(
|
pub fn execute_admin(self: *Bot, cmd: *const AdminCommand, targets: []const u8) Error!Response {
|
||||||
self: *Bot,
|
|
||||||
cmd: *const AdminCommand,
|
|
||||||
prefix: ?zircon.Prefix,
|
|
||||||
targets: []const u8,
|
|
||||||
) Error!zircon.Message {
|
|
||||||
switch (cmd.*) {
|
switch (cmd.*) {
|
||||||
.status => {
|
.status => {
|
||||||
const msg = try std.fmt.allocPrint(
|
const msg = try std.fmt.allocPrint(
|
||||||
|
|
@ -136,11 +140,11 @@ pub const Bot = struct {
|
||||||
"heard messages: {}, sent messages: {}",
|
"heard messages: {}, sent messages: {}",
|
||||||
.{ self.backlog.len(), self.outbox.len() },
|
.{ self.backlog.len(), self.outbox.len() },
|
||||||
);
|
);
|
||||||
return .{ .PRIVMSG = .{ .targets = targets, .prefix = prefix, .text = msg } };
|
return .{ .privmsg = .{ .targets = targets, .text = msg } };
|
||||||
},
|
},
|
||||||
.join => |msg| {
|
.join => |msg| {
|
||||||
std.log.debug("received join request: channel \"{s}\"", .{msg.channel});
|
std.log.debug("received join request: channel \"{s}\"", .{msg.channel});
|
||||||
return .{ .JOIN = .{ .prefix = prefix, .channels = msg.channel } };
|
return .{ .join = .{ .channels = msg.channel } };
|
||||||
},
|
},
|
||||||
.backlog => |backlog| {
|
.backlog => |backlog| {
|
||||||
if (self.backlog.len() == 0) {
|
if (self.backlog.len() == 0) {
|
||||||
|
|
@ -157,18 +161,18 @@ pub const Bot = struct {
|
||||||
);
|
);
|
||||||
self.outbox.append(quoted_output);
|
self.outbox.append(quoted_output);
|
||||||
return .{
|
return .{
|
||||||
.PRIVMSG = .{ .targets = targets, .prefix = prefix, .text = quoted_output },
|
.privmsg = .{ .targets = targets, .text = quoted_output },
|
||||||
};
|
};
|
||||||
} else return Error.NoMessage;
|
} else return Error.NoMessage;
|
||||||
},
|
},
|
||||||
.err => |err| {
|
.err => |err| {
|
||||||
return .{ .PRIVMSG = .{ .targets = targets, .prefix = prefix, .text = err.message } };
|
return .{ .privmsg = .{ .targets = targets, .text = err.message } };
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hear a message and store it in backlog, potentially overwriting oldest message.
|
// store a message in backlog, potentially overwriting oldest message.
|
||||||
pub fn hear(self: *Bot, msg: *const Message) void {
|
pub fn store(self: *Bot, msg: *const Message) void {
|
||||||
self.backlog.append(msg);
|
self.backlog.append(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -252,11 +256,7 @@ test "execute substitution no previous message" {
|
||||||
.needle = "What",
|
.needle = "What",
|
||||||
.replacement = "what",
|
.replacement = "what",
|
||||||
} };
|
} };
|
||||||
try std.testing.expectError(Error.NoMessage, bot.execute(
|
try std.testing.expectError(Error.NoMessage, bot.execute(&cmd, "#test"));
|
||||||
&cmd,
|
|
||||||
null,
|
|
||||||
"#test",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "execute substitution" {
|
test "execute substitution" {
|
||||||
|
|
@ -270,11 +270,11 @@ test "execute substitution" {
|
||||||
|
|
||||||
// execute substitution
|
// execute substitution
|
||||||
const cmd = UserCommand.init_substitute("jassob", "What", "what", false);
|
const cmd = UserCommand.init_substitute("jassob", "What", "what", false);
|
||||||
const response = try bot.execute(&cmd, null, "#test");
|
const response = try bot.execute(&cmd, "#test");
|
||||||
|
|
||||||
// expect response matching the correct message
|
// expect response matching the correct message
|
||||||
switch (response) {
|
switch (response) {
|
||||||
.PRIVMSG => |message| {
|
.privmsg => |message| {
|
||||||
try std.testing.expectEqualDeep(message.text, "jassob: \"what\"");
|
try std.testing.expectEqualDeep(message.text, "jassob: \"what\"");
|
||||||
},
|
},
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
|
|
@ -291,11 +291,7 @@ test "execute substitution with no matching needle" {
|
||||||
|
|
||||||
// execute substitution
|
// execute substitution
|
||||||
const cmd = UserCommand.init_substitute("jassob", "something else", "weird", false);
|
const cmd = UserCommand.init_substitute("jassob", "something else", "weird", false);
|
||||||
try std.testing.expectError(Error.NoMessage, bot.execute(
|
try std.testing.expectError(Error.NoMessage, bot.execute(&cmd, "#test"));
|
||||||
&cmd,
|
|
||||||
null,
|
|
||||||
"#test",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "recursive substitutions does not cause issues" {
|
test "recursive substitutions does not cause issues" {
|
||||||
|
|
@ -308,12 +304,8 @@ test "recursive substitutions does not cause issues" {
|
||||||
|
|
||||||
// execute substitution
|
// execute substitution
|
||||||
const cmd = UserCommand.init_substitute("jassob", "original", "something else", false);
|
const cmd = UserCommand.init_substitute("jassob", "original", "something else", false);
|
||||||
switch (try bot.execute(
|
switch (try bot.execute(&cmd, "#test")) {
|
||||||
&cmd,
|
.privmsg => |message| {
|
||||||
null,
|
|
||||||
"#test",
|
|
||||||
)) {
|
|
||||||
.PRIVMSG => |message| {
|
|
||||||
try std.testing.expectEqualDeep("jassob: \"something else\"", message.text);
|
try std.testing.expectEqualDeep("jassob: \"something else\"", message.text);
|
||||||
},
|
},
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
|
|
@ -321,9 +313,5 @@ test "recursive substitutions does not cause issues" {
|
||||||
|
|
||||||
// execute second substitution
|
// execute second substitution
|
||||||
const cmd2 = UserCommand.init_substitute("jassob", "s/original/something else/", "something else", false);
|
const cmd2 = UserCommand.init_substitute("jassob", "s/original/something else/", "something else", false);
|
||||||
try std.testing.expectError(Error.NoMessage, bot.execute(
|
try std.testing.expectError(Error.NoMessage, bot.execute(&cmd2, "#test"));
|
||||||
&cmd2,
|
|
||||||
null,
|
|
||||||
"#test",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
src/main.zig
17
src/main.zig
|
|
@ -6,6 +6,7 @@ const Error = zigeru.bot.Error;
|
||||||
const UserCommand = zigeru.commands.UserCommand;
|
const UserCommand = zigeru.commands.UserCommand;
|
||||||
const AdminCommand = zigeru.commands.AdminCommand;
|
const AdminCommand = zigeru.commands.AdminCommand;
|
||||||
const BotMessage = zigeru.bot.Message;
|
const BotMessage = zigeru.bot.Message;
|
||||||
|
const BotResponse = zigeru.bot.Response;
|
||||||
const zircon = @import("zircon");
|
const zircon = @import("zircon");
|
||||||
|
|
||||||
var debug_allocator = std.heap.DebugAllocator(.{}).init;
|
var debug_allocator = std.heap.DebugAllocator(.{}).init;
|
||||||
|
|
@ -72,10 +73,10 @@ pub const BotAdapter = struct {
|
||||||
);
|
);
|
||||||
const nick = if (msg.prefix) |prefix| if (prefix.nick) |nick| nick else "unknown" else "unknown";
|
const nick = if (msg.prefix) |prefix| if (prefix.nick) |nick| nick else "unknown" else "unknown";
|
||||||
if (UserCommand.parse(nick, msg.text)) |cmd| {
|
if (UserCommand.parse(nick, msg.text)) |cmd| {
|
||||||
return self.bot.execute(&cmd, msg.prefix, msg.targets) catch |err| return report_error(err);
|
return toIRC(self.bot.execute(&cmd, msg.targets) catch |err| return report_error(err));
|
||||||
}
|
}
|
||||||
if (AdminCommand.parse(msg.text)) |cmd| {
|
if (AdminCommand.parse(msg.text)) |cmd| {
|
||||||
return self.bot.execute_admin(&cmd, msg.prefix, "#eru-admin") catch |err| return report_error(err);
|
return toIRC(self.bot.execute_admin(&cmd, "#eru-admin") catch |err| return report_error(err));
|
||||||
}
|
}
|
||||||
const bot_msg = BotMessage.init_owned(
|
const bot_msg = BotMessage.init_owned(
|
||||||
self.allocator,
|
self.allocator,
|
||||||
|
|
@ -120,6 +121,18 @@ pub const BotAdapter = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// 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" {
|
test "substitute" {
|
||||||
var bot_adapter = try BotAdapter.init(std.testing.allocator);
|
var bot_adapter = try BotAdapter.init(std.testing.allocator);
|
||||||
defer bot_adapter.deinit();
|
defer bot_adapter.deinit();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue