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`
|
// target and optimize options) will be listed when running `zig build --help`
|
||||||
// in this directory.
|
// 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
|
// This creates a module, which represents a collection of source files alongside
|
||||||
// some compilation options, such as optimization mode and linked system libraries.
|
// some compilation options, such as optimization mode and linked system libraries.
|
||||||
// Zig modules are the preferred way of making Zig code available to consumers.
|
// 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
|
// Later on we'll use this module as the root module of a test executable
|
||||||
// which requires us to specify a target.
|
// which requires us to specify a target.
|
||||||
.target = target,
|
.target = target,
|
||||||
|
.imports = &.{
|
||||||
|
.{ .name = "zircon", .module = zircon.module("zircon") },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Here we define an executable. An executable needs to have a root module
|
// 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
|
// can be extremely useful in case of collisions (which can happen
|
||||||
// importing modules from different packages).
|
// importing modules from different packages).
|
||||||
.{ .name = "zigeru", .module = mod },
|
.{ .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();
|
exe.linkLibC();
|
||||||
|
|
||||||
// This declares intent for the executable to be installed into the
|
// 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.
|
// A run step that will run the test executable.
|
||||||
const run_mod_tests = b.addRunArtifact(mod_tests);
|
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
|
// 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,
|
// root module. Note that test executables only test one module at a time,
|
||||||
// hence why we have to create two separate ones.
|
// 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");
|
const test_step = b.step("test", "Run tests");
|
||||||
test_step.dependOn(&run_mod_tests.step);
|
test_step.dependOn(&run_mod_tests.step);
|
||||||
test_step.dependOn(&run_exe_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.
|
// 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 std = @import("std");
|
||||||
const zircon = @import("zircon");
|
const zircon = @import("zircon");
|
||||||
|
|
||||||
pub const Command = union(enum) {
|
/// AdminCommand are commands useful for debugging zigeru, since they
|
||||||
substitute: struct { author: []const u8, needle: []const u8, replacement: []const u8, all: bool },
|
/// are more spammy than others they are separated and only sent to
|
||||||
help: void,
|
/// #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.len < 2) return null;
|
||||||
|
|
||||||
if (text[0] == 's') {
|
if (std.mem.eql(u8, text, "!status")) {
|
||||||
if (text.len == 1) return null;
|
return .status;
|
||||||
const delim = text[1];
|
}
|
||||||
var parts = std.mem.splitScalar(u8, text, delim);
|
|
||||||
_ = parts.next() orelse return null; // skip 's'
|
if (text.len > 8 and std.mem.eql(u8, text[0..8], "!backlog")) {
|
||||||
const needle = parts.next() orelse return null;
|
const history = std.fmt.parseInt(u16, text[9..], 10) catch |err| {
|
||||||
const replacement = parts.next().?;
|
std.debug.print("failed to parse int ('{s}') with error: {}\n", .{ text[8..], err });
|
||||||
const flags = parts.next();
|
return null;
|
||||||
const all = if (flags == null) false else (std.mem.eql(u8, flags.?, "g"));
|
|
||||||
return Command{
|
|
||||||
.substitute = .{
|
|
||||||
.author = nick,
|
|
||||||
.needle = needle,
|
|
||||||
.replacement = replacement,
|
|
||||||
.all = all,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
return .{ .backlog = .{ .history = history } };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std.mem.eql(u8, text, "help")) {
|
|
||||||
return Command.help;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const HELP_MESSAGE: []const u8 =
|
/// Command represents the commands that ordinary IRC users can use.
|
||||||
\\Welcome to zigeru!
|
pub const Command = union(enum) {
|
||||||
\\
|
/// `s/<old-word>/<new-word>/`
|
||||||
\\Commands:
|
substitute: struct { author: []const u8, needle: []const u8, replacement: []const u8, all: bool = false },
|
||||||
\\!help:\tSee this message
|
/// !help
|
||||||
\\s/TYPO/CORRECTION/\tCorrect previous message by replacing TYPO with CORRECTION.
|
help: void,
|
||||||
;
|
|
||||||
|
|
||||||
pub const Error = error{
|
pub fn parse(nick: []const u8, text: []const u8) ?Command {
|
||||||
OutOfMemory,
|
if (std.mem.eql(u8, text, "!help")) {
|
||||||
NoMessage,
|
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 {
|
pub const Message = struct {
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
|
targets: []const u8,
|
||||||
author: []const u8,
|
author: []const u8,
|
||||||
content: []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 {
|
pub const Bot = struct {
|
||||||
backlog: [1024]?Message,
|
backlog: [1024]?Message,
|
||||||
sent_messages: std.ArrayList(u8),
|
sent_messages: std.ArrayList([]u8),
|
||||||
top: usize,
|
top: usize,
|
||||||
bottom: usize,
|
bottom: usize,
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
@ -64,7 +103,7 @@ pub const Bot = struct {
|
||||||
pub fn init(allocator: std.mem.Allocator) Error!Bot {
|
pub fn init(allocator: std.mem.Allocator) Error!Bot {
|
||||||
return Bot{
|
return Bot{
|
||||||
.backlog = .{null} ** 1024,
|
.backlog = .{null} ** 1024,
|
||||||
.sent_messages = try std.ArrayList(u8).initCapacity(allocator, 10),
|
.sent_messages = try std.ArrayList([]u8).initCapacity(allocator, 10),
|
||||||
.top = 0,
|
.top = 0,
|
||||||
.bottom = 0,
|
.bottom = 0,
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
|
|
@ -72,28 +111,90 @@ pub const Bot = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Bot) void {
|
pub fn deinit(self: *Bot) void {
|
||||||
|
for (self.sent_messages.items) |item| {
|
||||||
|
self.allocator.free(item);
|
||||||
|
}
|
||||||
self.sent_messages.deinit(self.allocator);
|
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 {
|
pub fn execute(self: *Bot, cmd: *const Command, 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(command.author) orelse return Error.NoMessage;
|
const prev_msg = self.previous_message_by_author(command.author, targets) orelse return Error.NoMessage;
|
||||||
const size = std.mem.replacementSize(u8, prev_msg.content, command.needle, command.replacement);
|
const output = try std.mem.replaceOwned(
|
||||||
const output = try self.sent_messages.addManyAsSlice(self.allocator, size);
|
u8,
|
||||||
_ = std.mem.replace(u8, prev_msg.content, command.needle, command.replacement, output);
|
self.allocator,
|
||||||
return zircon.Message{ .PRIVMSG = .{ .targets = targets, .prefix = prefix, .text = output } };
|
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 => {
|
.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 {
|
pub fn hear(self: *Bot, msg: Message) void {
|
||||||
self.backlog[self.top] = msg;
|
self.backlog[self.top] = msg;
|
||||||
self.top = (self.top + 1) % 1024;
|
self.top = (self.top + 1) % self.backlog.len;
|
||||||
if (self.top == self.bottom) self.bottom = (self.bottom + 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 {
|
pub fn no_messages(self: *Bot) usize {
|
||||||
|
|
@ -108,7 +209,7 @@ pub const Bot = struct {
|
||||||
if (idx == 0) {
|
if (idx == 0) {
|
||||||
return self.backlog.len - 1;
|
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 {
|
fn previous_message(self: *Bot, comptime pred: *const fn (Message) bool) ?Message {
|
||||||
|
|
@ -129,14 +230,14 @@ pub const Bot = struct {
|
||||||
return null;
|
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);
|
var idx = self.previous_idx(self.top);
|
||||||
while (true) : (idx = self.previous_idx(idx)) {
|
while (true) : (idx = self.previous_idx(idx)) {
|
||||||
if (self.backlog[idx] == null) {
|
if (self.backlog[idx] == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const message = self.backlog[idx] orelse unreachable;
|
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;
|
return message;
|
||||||
}
|
}
|
||||||
if (idx == self.bottom) {
|
if (idx == self.bottom) {
|
||||||
|
|
@ -149,7 +250,9 @@ pub const Bot = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
test "hear_wraps" {
|
test "hear_wraps" {
|
||||||
var bot = Bot.new(std.testing.allocator);
|
var bot = try Bot.init(std.testing.allocator);
|
||||||
|
defer bot.deinit();
|
||||||
|
|
||||||
const testMessage = Message{
|
const testMessage = Message{
|
||||||
.author = "Jassob",
|
.author = "Jassob",
|
||||||
.timestamp = 12345,
|
.timestamp = 12345,
|
||||||
|
|
@ -166,7 +269,8 @@ test "hear_wraps" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "previous_message" {
|
test "previous_message" {
|
||||||
var bot = Bot.new(std.testing.allocator);
|
var bot = try Bot.init(std.testing.allocator);
|
||||||
|
defer bot.deinit();
|
||||||
|
|
||||||
const callback = struct {
|
const callback = struct {
|
||||||
fn callback(_: Message) bool {
|
fn callback(_: Message) bool {
|
||||||
|
|
@ -179,7 +283,8 @@ test "previous_message" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "previous_message1" {
|
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;
|
var contents: [10][]const u8 = .{undefined} ** 10;
|
||||||
|
|
||||||
for (0..10) |i| {
|
for (0..10) |i| {
|
||||||
|
|
@ -207,20 +312,30 @@ test "previous_message1" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "execute_substitution_no_previous_message" {
|
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" } };
|
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" {
|
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" });
|
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);
|
// execute substitution
|
||||||
switch (result) {
|
const cmd = Command{
|
||||||
.post_message => |message| {
|
.substitute = .{ .author = "jassob", .needle = "What", .replacement = "what" },
|
||||||
try std.testing.expectEqualDeep(message.content, "what");
|
};
|
||||||
std.testing.allocator.free(message.content);
|
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 std = @import("std");
|
||||||
const zigeru = @import("zigeru");
|
|
||||||
const zircon = @import("zircon");
|
const zircon = @import("zircon");
|
||||||
|
const zigeru = @import("zigeru");
|
||||||
|
|
||||||
const Bot = zigeru.bot.Bot;
|
const Bot = zigeru.bot.Bot;
|
||||||
|
const Error = zigeru.bot.Error;
|
||||||
const BotCommand = zigeru.bot.Command;
|
const BotCommand = zigeru.bot.Command;
|
||||||
|
const AdminCommand = zigeru.bot.AdminCommand;
|
||||||
|
const BotMessage = zigeru.bot.Message;
|
||||||
|
|
||||||
var debug_allocator = std.heap.DebugAllocator(.{}).init;
|
var debug_allocator = std.heap.DebugAllocator(.{}).init;
|
||||||
|
|
||||||
|
|
@ -26,30 +30,23 @@ pub fn main() !void {
|
||||||
defer client.deinit();
|
defer client.deinit();
|
||||||
|
|
||||||
var bot_adapter = try BotAdapter.init(allocator);
|
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.
|
// Connect to the IRC server and perform registration.
|
||||||
try client.connect();
|
try client.connect();
|
||||||
try client.register();
|
try client.register();
|
||||||
try client.join("#eru-tests");
|
try client.join("#eru-tests");
|
||||||
|
try client.join("#eru-admin");
|
||||||
|
|
||||||
// Enter the main loop that keeps reading incoming IRC messages forever.
|
client.loop(.{}) catch |err| {
|
||||||
// The client loop accepts a LoopConfig struct with two optional fields.
|
std.debug.print("eru exited with error: {}\n", .{err});
|
||||||
//
|
return;
|
||||||
// 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 {
|
pub const Adapter = struct {
|
||||||
ptr: ?*anyopaque,
|
ptr: *anyopaque,
|
||||||
callbackFn: *const fn (?*anyopaque, zircon.Message) ?zircon.Message,
|
callbackFn: *const fn (*anyopaque, zircon.Message) ?zircon.Message,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const BotAdapter = struct {
|
pub const BotAdapter = struct {
|
||||||
|
|
@ -70,47 +67,66 @@ pub const BotAdapter = struct {
|
||||||
pub fn callback(self: *BotAdapter, message: zircon.Message) ?zircon.Message {
|
pub fn callback(self: *BotAdapter, message: zircon.Message) ?zircon.Message {
|
||||||
switch (message) {
|
switch (message) {
|
||||||
.PRIVMSG => |msg| {
|
.PRIVMSG => |msg| {
|
||||||
if (Command.parse(msg.prefix, msg.targets, msg.text)) |command| {
|
std.log.debug("received message: nick {?s}, user: {?s}, host: {?s}, targets: {s}, text: {s}", .{
|
||||||
return command.handle(&self.bot);
|
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{
|
if (AdminCommand.parse(msg.text)) |command| {
|
||||||
.author = msg.prefix.?.nick orelse "unknown",
|
return self.bot.execute_admin(&command, msg.prefix, "#eru-admin") catch |err| {
|
||||||
.timestamp = std.time.timestamp(),
|
const err_msg = switch (err) {
|
||||||
.content = msg.text,
|
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;
|
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 .{
|
return .{
|
||||||
.ptr = self,
|
.ptr = self,
|
||||||
.callbackFn = self.callback,
|
.callbackFn = BotAdapter.erased_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;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -136,5 +152,5 @@ test "substitute" {
|
||||||
};
|
};
|
||||||
const response = bot_adapter.callback(cmd_msg);
|
const response = bot_adapter.callback(cmd_msg);
|
||||||
try std.testing.expect(response != null);
|
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.
|
//! By convention, root.zig is the root source file when making a library.
|
||||||
const std = @import("std");
|
|
||||||
pub const bot = @import("bot.zig");
|
pub const bot = @import("bot.zig");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue