summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
authorGravatar Uko Kokņevičs2024-07-20 17:22:25 +0300
committerGravatar Uko Kokņevičs2024-07-20 17:22:25 +0300
commitc70ffd095a6de5cd5b872796a0d82a8c5afc1511 (patch)
tree56183274b05a294e357bad4d06b523472a1c4a4a /src/main.zig
downloadukkobot-c70ffd095a6de5cd5b872796a0d82a8c5afc1511.tar.gz
ukkobot-c70ffd095a6de5cd5b872796a0d82a8c5afc1511.tar.xz
ukkobot-c70ffd095a6de5cd5b872796a0d82a8c5afc1511.zip
Initial commit
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig353
1 files changed, 353 insertions, 0 deletions
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..0c524a7
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,353 @@
1const types = @import("types.zig");
2const std = @import("std");
3
4const Allocator = std.mem.Allocator;
5const ArrayList = std.ArrayList;
6const Bot = @import("Bot.zig");
7const Config = @import("Config.zig");
8const GPA = std.heap.GeneralPurposeAllocator(.{});
9
10pub fn main() !void {
11 defer std.log.info("We're done", .{});
12
13 var gpa = GPA{};
14 const allocator = gpa.allocator();
15 defer _ = gpa.deinit();
16
17 // Load config
18 var config = try Config.load(allocator, "config.default.json");
19 defer config.deinit();
20 try config.merge("config.json");
21
22 var bot = try Bot.init(allocator, config.config);
23 defer bot.deinit();
24
25 // TODO: Catch fatal errors, report them
26 try wrappedMain(&bot);
27}
28
29fn loadConfig(allocator: Allocator, filename: []const u8) !std.json.Parsed(Config) {
30 const file = try std.fs.cwd().openFile(filename, .{});
31 defer file.close();
32
33 var reader = std.json.reader(allocator, file.reader());
34 defer reader.deinit();
35
36 return try std.json.parseFromTokenSource(
37 Config,
38 allocator,
39 &reader,
40 .{
41 .duplicate_field_behavior = .use_last,
42 .ignore_unknown_fields = true,
43 .allocate = .alloc_always,
44 },
45 );
46}
47
48fn wrappedMain(bot: *Bot) !void {
49 try bot.sendMessage_(.{
50 .chat_id = bot.config.dev_group,
51 .text = "Initializing...",
52 });
53
54 try bot.setMyName(.{ .name = "Ukko's bot" });
55
56 var gup = types.GetUpdatesParams{ .timeout = 60 };
57 while (bot.poweron) {
58 // TODO: Catch major errors, report them (and crash after 5 of them or so)
59 const updates = try bot.getUpdates(gup);
60 defer updates.deinit();
61 for (updates.value) |update| {
62 defer gup.offset = update.update_id + 1;
63
64 if (update.message) |message| {
65 // TODO: Catch minor errors, report them
66 try onMessage(bot, message);
67 }
68 }
69 }
70
71 // one last getUpdates to make sure offset is saved
72 gup.timeout = 0;
73 gup.limit = 1;
74 (try bot.getUpdates(gup)).deinit();
75
76 try bot.sendMessage_(.{
77 .chat_id = bot.config.dev_group,
78 .text = "Shutting down...",
79 });
80}
81
82fn onMessage(bot: *Bot, msg: types.Message) !void {
83 if (msg.text) |text| {
84 try onTextMessage(bot, msg, text);
85 }
86
87 if (msg.new_chat_members) |new_chat_members| {
88 for (new_chat_members) |new_chat_member| {
89 try onNewMember(bot, msg, new_chat_member);
90 }
91 }
92}
93
94fn onNewMember(bot: *Bot, msg: types.Message, new_member: types.User) !void {
95 var sb = ArrayList(u8).init(bot.allocator);
96 defer sb.deinit();
97
98 const w = sb.writer();
99
100 try w.writeAll("Hello there, ");
101 try new_member.writeFormattedName(w);
102 try w.writeAll("! Be on your bestest behaviour now!!");
103
104 try bot.sendMessage_(.{
105 .chat_id = msg.chat.id,
106 .text = sb.items,
107 .parse_mode = .html,
108 .reply_parameters = .{
109 .allow_sending_without_reply = true,
110 .message_id = msg.message_id,
111 .chat_id = msg.chat.id,
112 },
113 });
114}
115
116fn isBadText(text: []const u8) bool {
117 _ = text;
118 return false;
119}
120
121fn onTextMessage(bot: *Bot, msg: types.Message, text: []const u8) !void {
122 if (isBadText(text)) {
123 // TODO: Delete message, mute & warn user
124 // 0 current warns: 5 minute mute, +1 warn
125 // 1 current warn : 10 minute mute, +1 warn
126 // 2 current warns: 30 minute mute, +1 warn
127 // 3 current warns: 1 hour mute, +1 warn
128 // 4 current warns: 1 day mute, +1 warn
129 // 5 current warns: Ban
130 //
131 // warn gets removed after a month of no warns
132 //
133 // Lines to say in response:
134 // Your head will be my new trophy!
135 // Your cursed bloodline ends here!
136 // This is the end of you, s'wit!
137 // Your life's end is approaching.
138 // Surrender your life to me and I will end your pain!
139 // Your pain is nearing an end.
140 // May our Lords be merciful!
141 // You have sealed your fate!
142 // You cannot escape the righteous!
143 // You will pay with your blood!
144 // You will die.
145 // There is no escape.
146 // Die, fetcher.
147 // I shall enjoy watching you take your last breath.
148 // You'll soon be nothing more than a bad memory!
149 // You will die in disgrace.
150 // I'll see you dead.
151 // One of us will die here and it won't be me.
152 // You don't deserve to live.
153 // Surrender now and I might let you live!
154 // I will bathe in your blood.
155 // Your bones will be my dinner.
156 // So small and tasty. I will enjoy eating you.
157 return;
158 }
159
160 if (msg.entities) |entities| {
161 for (entities) |entity| {
162 if (entity.type == .bot_command and entity.offset == 0) {
163 const cmd = try entity.extract(text);
164 try onTextCommand(bot, msg, text, cmd);
165 }
166 }
167 }
168
169 if (std.mem.eql(u8, text, ":3")) {
170 try bot.sendMessage_(.{
171 .chat_id = msg.chat.id,
172 .text = ">:3",
173 .reply_parameters = .{
174 .message_id = msg.message_id,
175 .chat_id = msg.chat.id,
176 },
177 });
178 } else if (std.mem.eql(u8, text, ">:3")) {
179 try bot.sendMessage_(.{
180 .chat_id = msg.chat.id,
181 .text = "<b>&gt;:3</b>",
182 .parse_mode = .html,
183 .reply_parameters = .{
184 .message_id = msg.message_id,
185 .chat_id = msg.chat.id,
186 },
187 });
188 } else if (std.ascii.startsWithIgnoreCase(text, "big ")) {
189 var output = try bot.allocator.alloc(u8, text.len + 3);
190 defer bot.allocator.free(output);
191
192 std.mem.copyForwards(u8, output, "<b>");
193 _ = std.ascii.upperString(output[3..], text[4..]);
194 std.mem.copyForwards(u8, output[output.len - 4 ..], "</b>");
195
196 try bot.sendMessage_(.{
197 .chat_id = msg.chat.id,
198 .text = output,
199 .parse_mode = .html,
200 .reply_parameters = .{
201 .message_id = msg.message_id,
202 .chat_id = msg.chat.id,
203 },
204 });
205 } else if (std.mem.eql(u8, text, "H")) {
206 try bot.sendMessage_(.{
207 .chat_id = msg.chat.id,
208 .text = "<code>Randomly selected reminder that h &gt; H.</code>",
209 .parse_mode = .html,
210 .reply_parameters = .{
211 .message_id = msg.message_id,
212 .chat_id = msg.chat.id,
213 },
214 });
215 } else if (std.ascii.startsWithIgnoreCase(text, "say ")) {
216 try bot.sendMessage_(.{
217 .chat_id = msg.chat.id,
218 .text = text[4..],
219 .reply_parameters = .{
220 .message_id = msg.message_id,
221 .chat_id = msg.chat.id,
222 },
223 });
224 } else if (std.ascii.eqlIgnoreCase(text, "uwu")) {
225 try bot.sendMessage_(.{
226 .chat_id = msg.chat.id,
227 .text = "OwO",
228 .reply_parameters = .{
229 .message_id = msg.message_id,
230 .chat_id = msg.chat.id,
231 },
232 });
233 } else if (std.ascii.eqlIgnoreCase(text, "waow")) {
234 const reply_to = if (msg.reply_to_message) |r|
235 r.message_id
236 else
237 msg.message_id;
238
239 try bot.sendMessage_(.{
240 .chat_id = msg.chat.id,
241 .text = "BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED",
242 .reply_parameters = .{
243 .message_id = reply_to,
244 .chat_id = msg.chat.id,
245 },
246 });
247 } else if (std.ascii.eqlIgnoreCase(text, "what")) {
248 var sb = try ArrayList(u8).initCapacity(bot.allocator, 9);
249 defer sb.deinit();
250
251 if (text[0] == 'w') {
252 sb.appendSliceAssumeCapacity("g");
253 } else {
254 sb.appendSliceAssumeCapacity("G");
255 }
256 if (text[1] == 'h') {
257 sb.appendSliceAssumeCapacity("ood ");
258 } else {
259 sb.appendSliceAssumeCapacity("OOD ");
260 }
261 if (text[2] == 'a') {
262 sb.appendSliceAssumeCapacity("gir");
263 } else {
264 sb.appendSliceAssumeCapacity("GIR");
265 }
266 if (text[3] == 't') {
267 sb.appendSliceAssumeCapacity("l");
268 } else {
269 sb.appendSliceAssumeCapacity("L");
270 }
271
272 try bot.sendMessage_(.{
273 .chat_id = msg.chat.id,
274 .text = sb.items,
275 .reply_parameters = .{
276 .message_id = msg.message_id,
277 .chat_id = msg.chat.id,
278 },
279 });
280 }
281}
282
283fn onTextCommand(bot: *Bot, msg: types.Message, text: []const u8, cmd: []const u8) !void {
284 _ = text;
285
286 const simple_cmd = if (std.mem.indexOfScalar(u8, cmd, '@')) |idx| blk: {
287 const cmd_username = cmd[idx + 1 ..];
288 if (!std.mem.eql(u8, cmd_username, try bot.getUsername())) {
289 return;
290 }
291 break :blk cmd[1..idx];
292 } else cmd[1..];
293
294 // TODO: StaticStringMap :)
295 if (std.mem.eql(u8, simple_cmd, "chatid")) {
296 var sb = ArrayList(u8).init(bot.allocator);
297 defer sb.deinit();
298 try sb.writer().print("<code>{}</code>", .{msg.chat.id});
299
300 try bot.sendMessage_(.{
301 .chat_id = msg.chat.id,
302 .text = sb.items,
303 .parse_mode = .html,
304 .reply_parameters = .{
305 .message_id = msg.message_id,
306 .chat_id = msg.chat.id,
307 },
308 });
309 } else if (std.mem.eql(u8, simple_cmd, "ping")) {
310 var timer = try std.time.Timer.start();
311
312 const recv = msg.date - @as(u64, @intCast(std.time.timestamp()));
313
314 var sb = ArrayList(u8).init(bot.allocator);
315 defer sb.deinit();
316 try sb.writer().print("Pong!\nReceive time: {}s\nSend time: ...", .{recv});
317
318 const reply = try bot.sendMessage(.{
319 .chat_id = msg.chat.id,
320 .text = sb.items,
321 .reply_parameters = .{
322 .message_id = msg.message_id,
323 .chat_id = msg.chat.id,
324 },
325 });
326 defer reply.deinit();
327
328 const send = @as(f64, @floatFromInt(timer.read())) / std.time.ns_per_ms;
329 sb.clearRetainingCapacity();
330 try sb.writer().print("Pong!\nReceive time: {}s\nSend time: {d}ms", .{ recv, send });
331
332 try bot.editMessageText_(.{
333 .chat_id = reply.value.chat.id,
334 .message_id = reply.value.message_id,
335 .text = sb.items,
336 });
337 } else if (std.mem.eql(u8, simple_cmd, "shutdown")) blk: {
338 if (msg.from == null or msg.from.?.id != bot.config.owner) {
339 break :blk;
340 }
341
342 bot.poweron = false;
343 try bot.sendMessage_(.{
344 .chat_id = msg.chat.id,
345 .text = "Initialising shutdown...",
346 .reply_parameters = .{
347 .allow_sending_without_reply = true,
348 .message_id = msg.message_id,
349 .chat_id = msg.chat.id,
350 },
351 });
352 }
353}