const types = @import("types.zig");
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const Bot = @import("Bot.zig");
const Config = @import("Config.zig");
const GPA = std.heap.GeneralPurposeAllocator(.{});
pub fn main() !void {
defer std.log.info("We're done", .{});
var gpa = GPA{};
const allocator = gpa.allocator();
defer _ = gpa.deinit();
// Load config
var config = try Config.load(allocator, "config.default.json");
defer config.deinit();
try config.merge("config.json");
var bot = try Bot.init(allocator, config.config);
defer bot.deinit();
// TODO: Catch fatal errors, report them
try wrappedMain(&bot);
}
fn loadConfig(allocator: Allocator, filename: []const u8) !std.json.Parsed(Config) {
const file = try std.fs.cwd().openFile(filename, .{});
defer file.close();
var reader = std.json.reader(allocator, file.reader());
defer reader.deinit();
return try std.json.parseFromTokenSource(
Config,
allocator,
&reader,
.{
.duplicate_field_behavior = .use_last,
.ignore_unknown_fields = true,
.allocate = .alloc_always,
},
);
}
fn wrappedMain(bot: *Bot) !void {
try bot.sendMessage_(.{
.chat_id = bot.config.dev_group,
.text = "Initializing...",
});
try bot.setMyName(.{ .name = "Ukko's bot" });
var gup = types.GetUpdatesParams{ .timeout = 60 };
while (bot.poweron) {
// TODO: Catch major errors, report them (and crash after 5 of them or so)
const updates = try bot.getUpdates(gup);
defer updates.deinit();
for (updates.value) |update| {
defer gup.offset = update.update_id + 1;
if (update.message) |message| {
// TODO: Catch minor errors, report them
try onMessage(bot, message);
}
}
}
// one last getUpdates to make sure offset is saved
gup.timeout = 0;
gup.limit = 1;
(try bot.getUpdates(gup)).deinit();
try bot.sendMessage_(.{
.chat_id = bot.config.dev_group,
.text = "Shutting down...",
});
}
fn onMessage(bot: *Bot, msg: types.Message) !void {
if (msg.text) |text| {
try onTextMessage(bot, msg, text);
}
if (msg.new_chat_members) |new_chat_members| {
for (new_chat_members) |new_chat_member| {
try onNewMember(bot, msg, new_chat_member);
}
}
}
fn onNewMember(bot: *Bot, msg: types.Message, new_member: types.User) !void {
var sb = ArrayList(u8).init(bot.allocator);
defer sb.deinit();
const w = sb.writer();
try w.writeAll("Hello there, ");
try new_member.writeFormattedName(w);
try w.writeAll("! Be on your bestest behaviour now!!");
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = sb.items,
.parse_mode = .html,
.reply_parameters = .{
.allow_sending_without_reply = true,
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
}
fn isBadText(text: []const u8) bool {
_ = text;
return false;
}
fn onTextMessage(bot: *Bot, msg: types.Message, text: []const u8) !void {
if (isBadText(text)) {
// TODO: Delete message, mute & warn user
// 0 current warns: 5 minute mute, +1 warn
// 1 current warn : 10 minute mute, +1 warn
// 2 current warns: 30 minute mute, +1 warn
// 3 current warns: 1 hour mute, +1 warn
// 4 current warns: 1 day mute, +1 warn
// 5 current warns: Ban
//
// warn gets removed after a month of no warns
//
// Lines to say in response:
// Your head will be my new trophy!
// Your cursed bloodline ends here!
// This is the end of you, s'wit!
// Your life's end is approaching.
// Surrender your life to me and I will end your pain!
// Your pain is nearing an end.
// May our Lords be merciful!
// You have sealed your fate!
// You cannot escape the righteous!
// You will pay with your blood!
// You will die.
// There is no escape.
// Die, fetcher.
// I shall enjoy watching you take your last breath.
// You'll soon be nothing more than a bad memory!
// You will die in disgrace.
// I'll see you dead.
// One of us will die here and it won't be me.
// You don't deserve to live.
// Surrender now and I might let you live!
// I will bathe in your blood.
// Your bones will be my dinner.
// So small and tasty. I will enjoy eating you.
return;
}
if (msg.entities) |entities| {
for (entities) |entity| {
if (entity.type == .bot_command and entity.offset == 0) {
const cmd = try entity.extract(text);
try onTextCommand(bot, msg, text, cmd);
}
}
}
if (std.mem.eql(u8, text, ":3")) {
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = ">:3",
.reply_parameters = .{
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
} else if (std.mem.eql(u8, text, ">:3")) {
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = ">:3",
.parse_mode = .html,
.reply_parameters = .{
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
} else if (std.ascii.startsWithIgnoreCase(text, "big ")) {
var output = try bot.allocator.alloc(u8, text.len + 3);
defer bot.allocator.free(output);
std.mem.copyForwards(u8, output, "");
_ = std.ascii.upperString(output[3..], text[4..]);
std.mem.copyForwards(u8, output[output.len - 4 ..], "");
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = output,
.parse_mode = .html,
.reply_parameters = .{
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
} else if (std.ascii.eqlIgnoreCase(text, "forgor")) {
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = "💀",
.reply_parameters = .{
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
} else if (std.ascii.eqlIgnoreCase(text, "huh")) {
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = "idgi",
.reply_parameters = .{
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
} else if (std.mem.eql(u8, text, "H")) {
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = "Randomly selected reminder that h > H.",
.parse_mode = .html,
.reply_parameters = .{
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
} else if (std.ascii.startsWithIgnoreCase(text, "say ")) {
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = text[4..],
.reply_parameters = .{
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
} else if (std.ascii.eqlIgnoreCase(text, "uwu")) {
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = "OwO",
.reply_parameters = .{
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
} else if (std.ascii.eqlIgnoreCase(text, "waow")) {
const reply_to = if (msg.reply_to_message) |r|
r.message_id
else
msg.message_id;
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = "BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED",
.reply_parameters = .{
.message_id = reply_to,
.chat_id = msg.chat.id,
},
});
} else if (std.ascii.eqlIgnoreCase(text, "what")) {
var sb = try ArrayList(u8).initCapacity(bot.allocator, 9);
defer sb.deinit();
if (text[0] == 'w') {
sb.appendSliceAssumeCapacity("g");
} else {
sb.appendSliceAssumeCapacity("G");
}
if (text[1] == 'h') {
sb.appendSliceAssumeCapacity("ood ");
} else {
sb.appendSliceAssumeCapacity("OOD ");
}
if (text[2] == 'a') {
sb.appendSliceAssumeCapacity("gir");
} else {
sb.appendSliceAssumeCapacity("GIR");
}
if (text[3] == 't') {
sb.appendSliceAssumeCapacity("l");
} else {
sb.appendSliceAssumeCapacity("L");
}
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = sb.items,
.reply_parameters = .{
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
}
}
fn onTextCommand(bot: *Bot, msg: types.Message, text: []const u8, cmd: []const u8) !void {
_ = text;
const simple_cmd = if (std.mem.indexOfScalar(u8, cmd, '@')) |idx| blk: {
const cmd_username = cmd[idx + 1 ..];
if (!std.mem.eql(u8, cmd_username, try bot.getUsername())) {
return;
}
break :blk cmd[1..idx];
} else cmd[1..];
// TODO: StaticStringMap :)
if (std.mem.eql(u8, simple_cmd, "chatid")) {
var sb = ArrayList(u8).init(bot.allocator);
defer sb.deinit();
try sb.writer().print("{}", .{msg.chat.id});
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = sb.items,
.parse_mode = .html,
.reply_parameters = .{
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
} else if (std.mem.eql(u8, simple_cmd, "msginfo")) {
if (msg.reply_to_message) |replied| {
const str_data = try std.json.stringifyAlloc(bot.allocator, replied.*, .{
.whitespace = .indent_2,
.emit_null_optional_fields = false,
});
defer bot.allocator.free(str_data);
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = str_data,
.reply_parameters = .{
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
}
} else if (std.mem.eql(u8, simple_cmd, "ping")) {
var timer = try std.time.Timer.start();
var sb = ArrayList(u8).init(bot.allocator);
defer sb.deinit();
try sb.writer().print("Pong!\nSend time: ...", .{});
const reply = try bot.sendMessage(.{
.chat_id = msg.chat.id,
.text = sb.items,
.reply_parameters = .{
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
defer reply.deinit();
const send = @as(f64, @floatFromInt(timer.read())) / std.time.ns_per_ms;
sb.clearRetainingCapacity();
try sb.writer().print("Pong!\n\nSend time: {d}ms", .{send});
try bot.editMessageText_(.{
.chat_id = reply.value.chat.id,
.message_id = reply.value.message_id,
.text = sb.items,
});
} else if (std.mem.eql(u8, simple_cmd, "shutdown")) blk: {
if (msg.from == null or msg.from.?.id != bot.config.owner) {
break :blk;
}
bot.poweron = false;
try bot.sendMessage_(.{
.chat_id = msg.chat.id,
.text = "Initialising shutdown...",
.reply_parameters = .{
.allow_sending_without_reply = true,
.message_id = msg.message_id,
.chat_id = msg.chat.id,
},
});
}
}