summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Uko Kokņevičs2025-07-22 08:23:22 +0300
committerGravatar Uko Kokņevičs2025-07-22 08:23:22 +0300
commit1d1df7b540721d58db174764a0f720e3c638a67b (patch)
tree44bbfc972dac04e32729a3065c90dea0334149d8 /src
parentreplace trimming with just checking if the string is whitespace (diff)
downloadukkobot-1d1df7b540721d58db174764a0f720e3c638a67b.tar.gz
ukkobot-1d1df7b540721d58db174764a0f720e3c638a67b.tar.xz
ukkobot-1d1df7b540721d58db174764a0f720e3c638a67b.zip
Whitelist allowed inline bots
Also update to Zig 0.14.1 Also don't send the animations rn
Diffstat (limited to 'src')
-rw-r--r--src/Bot.zig12
-rw-r--r--src/Config.zig8
-rw-r--r--src/json.zig6
-rw-r--r--src/main.zig120
-rw-r--r--src/types.zig1
-rw-r--r--src/types/DeleteMessageParams.zig3
-rw-r--r--src/utils.zig40
7 files changed, 145 insertions, 45 deletions
diff --git a/src/Bot.zig b/src/Bot.zig
index 8e69579..b0eb972 100644
--- a/src/Bot.zig
+++ b/src/Bot.zig
@@ -47,6 +47,10 @@ pub fn deinit(self: *Bot) void {
47 self.* = undefined; 47 self.* = undefined;
48} 48}
49 49
50pub inline fn deleteMessage(self: *Bot, args: types.DeleteMessageParams) !void {
51 (try self.post(bool, "deleteMessage", args)).deinit();
52}
53
50pub inline fn editMessageText(self: *Bot, args: types.EditMessageTextParams) !Parsed(types.Message) { 54pub inline fn editMessageText(self: *Bot, args: types.EditMessageTextParams) !Parsed(types.Message) {
51 return self.post(types.Message, "editMessageText", args); 55 return self.post(types.Message, "editMessageText", args);
52} 56}
@@ -142,7 +146,7 @@ fn call(
142 return res; 146 return res;
143 } else |err| { 147 } else |err| {
144 std.log.warn("error when performing call: {}", .{err}); 148 std.log.warn("error when performing call: {}", .{err});
145 if (tries == 20) { 149 if (tries == 4) {
146 return err; 150 return err;
147 } 151 }
148 } 152 }
@@ -218,9 +222,9 @@ fn wrappedCall(
218 222
219fn intoQueryString(allocator: Allocator, data: anytype) !?[]u8 { 223fn intoQueryString(allocator: Allocator, data: anytype) !?[]u8 {
220 return switch (@typeInfo(@TypeOf(data))) { 224 return switch (@typeInfo(@TypeOf(data))) {
221 .Null => null, 225 .null => null,
222 .Optional => if (data) |d| intoQueryString(allocator, d) else null, 226 .optional => if (data) |d| intoQueryString(allocator, d) else null,
223 .Struct => |s| { 227 .@"struct" => |s| {
224 var sb = ArrayList(u8).init(allocator); 228 var sb = ArrayList(u8).init(allocator);
225 defer sb.deinit(); 229 defer sb.deinit();
226 230
diff --git a/src/Config.zig b/src/Config.zig
index 4deebbc..f9d6dab 100644
--- a/src/Config.zig
+++ b/src/Config.zig
@@ -71,13 +71,13 @@ pub fn load(parent_allocator: Allocator, filename: []const u8) !Wrapper {
71 71
72const Nullable = blk: { 72const Nullable = blk: {
73 const template = @typeInfo(Config); 73 const template = @typeInfo(Config);
74 var fields: [template.Struct.fields.len]std.builtin.Type.StructField = undefined; 74 var fields: [template.@"struct".fields.len]std.builtin.Type.StructField = undefined;
75 for (template.Struct.fields, 0..) |template_field, field_idx| { 75 for (template.@"struct".fields, 0..) |template_field, field_idx| {
76 fields[field_idx] = template_field; 76 fields[field_idx] = template_field;
77 fields[field_idx].type = ?template_field.type; 77 fields[field_idx].type = ?template_field.type;
78 } 78 }
79 var new = template; 79 var new = template;
80 new.Struct.fields = &fields; 80 new.@"struct".fields = &fields;
81 new.Struct.decls = &.{}; 81 new.@"struct".decls = &.{};
82 break :blk @Type(new); 82 break :blk @Type(new);
83}; 83};
diff --git a/src/json.zig b/src/json.zig
index 9252344..ff07531 100644
--- a/src/json.zig
+++ b/src/json.zig
@@ -50,7 +50,7 @@ pub fn makeJsonParseFromValue(T: type) JsonParseFromValue(T) {
50 50
51pub fn makeJsonParseFromValueWithTag(T: type, comptime tag: [:0]const u8) JsonParseFromValue(T) { 51pub fn makeJsonParseFromValueWithTag(T: type, comptime tag: [:0]const u8) JsonParseFromValue(T) {
52 const union_info = switch (@typeInfo(T)) { 52 const union_info = switch (@typeInfo(T)) {
53 .Union => |info| blk: { 53 .@"union" => |info| blk: {
54 if (info.tag_type == null) { 54 if (info.tag_type == null) {
55 @compileError("Only tagged unions supported, got '" ++ @typeName(T) ++ "'"); 55 @compileError("Only tagged unions supported, got '" ++ @typeName(T) ++ "'");
56 } 56 }
@@ -62,12 +62,12 @@ pub fn makeJsonParseFromValueWithTag(T: type, comptime tag: [:0]const u8) JsonPa
62 const TagType = union_info.tag_type.?; 62 const TagType = union_info.tag_type.?;
63 63
64 const Base = blk: { 64 const Base = blk: {
65 const info = std.builtin.Type{ .Struct = .{ 65 const info = std.builtin.Type{ .@"struct" = .{
66 .layout = .auto, 66 .layout = .auto,
67 .fields = &.{.{ 67 .fields = &.{.{
68 .name = tag, 68 .name = tag,
69 .type = TagType, 69 .type = TagType,
70 .default_value = null, 70 .default_value_ptr = null,
71 .is_comptime = false, 71 .is_comptime = false,
72 .alignment = 0, 72 .alignment = 0,
73 }}, 73 }},
diff --git a/src/main.zig b/src/main.zig
index b164284..9e1de47 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -46,6 +46,27 @@ fn loadConfig(allocator: Allocator, filename: []const u8) !std.json.Parsed(Confi
46 ); 46 );
47} 47}
48 48
49fn reportError(bot: *Bot, msg: types.Message, err: anyerror) !void {
50 std.log.err("While handling {}: {}", .{ msg, err });
51 const msgStr = try std.json.stringifyAlloc(bot.allocator, msg, .{
52 .whitespace = .indent_2,
53 .emit_null_optional_fields = false,
54 });
55 defer bot.allocator.free(msgStr);
56
57 const devMsg = try std.fmt.allocPrint(bot.allocator, "<code>{}</code> while handling\n<pre>{s}</pre>", .{ err, msgStr });
58 defer bot.allocator.free(devMsg);
59
60 bot.sendMessage_(.{
61 .chat_id = bot.config.dev_group,
62 .text = devMsg,
63 .parse_mode = .html,
64 }) catch |err2| {
65 std.log.err("While trying to report the error: {}", .{err2});
66 return err2;
67 };
68}
69
49fn wrappedMain(bot: *Bot) !void { 70fn wrappedMain(bot: *Bot) !void {
50 try bot.sendMessage_(.{ 71 try bot.sendMessage_(.{
51 .chat_id = bot.config.dev_group, 72 .chat_id = bot.config.dev_group,
@@ -63,8 +84,9 @@ fn wrappedMain(bot: *Bot) !void {
63 defer gup.offset = update.update_id + 1; 84 defer gup.offset = update.update_id + 1;
64 85
65 if (update.message) |message| { 86 if (update.message) |message| {
66 // TODO: Catch minor errors, report them 87 onMessage(bot, message) catch |err| {
67 try onMessage(bot, message); 88 try reportError(bot, message, err);
89 };
68 } 90 }
69 } 91 }
70 } 92 }
@@ -80,7 +102,63 @@ fn wrappedMain(bot: *Bot) !void {
80 }); 102 });
81} 103}
82 104
105const allowed_inline_bots = [_]i64{
106 90832338, // @vid
107 109158646, // @bing
108 114528005, // @pic
109 140267078, // @gif
110 154595593, // @wiki
111 184730458, // @UnitConversionBot
112 296635833, // @lastfmrobot
113 595898211, // @DeezerMusicBot
114 870410041, // @HowGayBot
115 7904498194, // @tanstiktokbot
116};
117
118comptime {
119 std.testing.expect(std.sort.isSorted(
120 i64,
121 &allowed_inline_bots,
122 {},
123 std.sort.asc(i64),
124 )) catch unreachable;
125}
126
127fn orderI64(a: i64, b: i64) std.math.Order {
128 return std.math.order(a, b);
129}
130
131fn isAllowedInlineBot(id: i64) bool {
132 return std.sort.binarySearch(
133 i64,
134 &allowed_inline_bots,
135 id,
136 orderI64,
137 ) != null;
138}
139
83fn onMessage(bot: *Bot, msg: types.Message) !void { 140fn onMessage(bot: *Bot, msg: types.Message) !void {
141 if (msg.via_bot) |via| {
142 if (!isAllowedInlineBot(via.id)) {
143 std.log.info("Deleting an unallowed inline bot message from {?s} {}", .{ via.username, via.id });
144 try bot.deleteMessage(.{
145 .chat_id = msg.chat.id,
146 .message_id = msg.message_id,
147 });
148
149 const text = try std.fmt.allocPrint(bot.allocator, "Deleted a message sent by inline bot @{?s} <code>{}</code>", .{ via.username, via.id });
150 defer bot.allocator.free(text);
151
152 try bot.sendMessage_(.{
153 .chat_id = bot.config.dev_group,
154 .text = text,
155 .parse_mode = .html,
156 });
157
158 return;
159 }
160 }
161
84 if (msg.text) |text| { 162 if (msg.text) |text| {
85 try onTextMessage(bot, msg, text); 163 try onTextMessage(bot, msg, text);
86 } 164 }
@@ -94,12 +172,14 @@ fn onMessage(bot: *Bot, msg: types.Message) !void {
94 172
95fn onNewMember(bot: *Bot, msg: types.Message, new_member: types.User) !void { 173fn onNewMember(bot: *Bot, msg: types.Message, new_member: types.User) !void {
96 if (new_member.id == try bot.getId()) { 174 if (new_member.id == try bot.getId()) {
175 return;
176 // TODO:
97 // Bot is added to a new group 177 // Bot is added to a new group
98 return bot.sendAnimation_(.{ 178 // return bot.sendAnimation_(.{
99 .chat_id = msg.chat.id, 179 // .chat_id = msg.chat.id,
100 // TODO: lol 180 // // TODO: lol
101 .animation = "CgACAgQAAx0CVcPEEgACDC5mo7YHMgOE2n3qo3e9UOyd4N-uxQACNAMAAlbuDFMRWj9LxNLBkDUE", 181 // .animation = "CgACAgQAAx0CVcPEEgACDC5mo7YHMgOE2n3qo3e9UOyd4N-uxQACNAMAAlbuDFMRWj9LxNLBkDUE",
102 }); 182 // });
103 } 183 }
104 184
105 var sb = ArrayList(u8).init(bot.allocator); 185 var sb = ArrayList(u8).init(bot.allocator);
@@ -111,13 +191,25 @@ fn onNewMember(bot: *Bot, msg: types.Message, new_member: types.User) !void {
111 try new_member.writeFormattedName(w); 191 try new_member.writeFormattedName(w);
112 try w.writeAll("! Be on your bestest behaviour now!!"); 192 try w.writeAll("! Be on your bestest behaviour now!!");
113 193
114 try bot.sendAnimation_(.{ 194 // TODO:
195 // try bot.sendAnimation_(.{
196 // .chat_id = msg.chat.id,
197 // // TODO: lol
198 // .animation = "CgACAgQAAx0CVcPEEgACC9Vmo6_zCxMp3ZNXSMM1nI6nMkIhgwACNwMAAtDmDFMop6BHmV7lUTUE",
199 // .caption = sb.items,
200 // .parse_mode = .html,
201 // .show_caption_above_media = true,
202 // .reply_parameters = .{
203 // .allow_sending_without_reply = true,
204 // .message_id = msg.message_id,
205 // .chat_id = msg.chat.id,
206 // },
207 // });
208
209 try bot.sendMessage_(.{
115 .chat_id = msg.chat.id, 210 .chat_id = msg.chat.id,
116 // TODO: lol 211 .text = sb.items,
117 .animation = "CgACAgQAAx0CVcPEEgACC9Vmo6_zCxMp3ZNXSMM1nI6nMkIhgwACNwMAAtDmDFMop6BHmV7lUTUE",
118 .caption = sb.items,
119 .parse_mode = .html, 212 .parse_mode = .html,
120 .show_caption_above_media = true,
121 .reply_parameters = .{ 213 .reply_parameters = .{
122 .allow_sending_without_reply = true, 214 .allow_sending_without_reply = true,
123 .message_id = msg.message_id, 215 .message_id = msg.message_id,
@@ -201,8 +293,8 @@ fn onTextMessage(bot: *Bot, msg: types.Message, text: []const u8) !void {
201 } else if (std.ascii.startsWithIgnoreCase(text, "big ")) { 293 } else if (std.ascii.startsWithIgnoreCase(text, "big ")) {
202 const the_text = text[4..]; 294 const the_text = text[4..];
203 if (!try utils.isTgWhitespaceStr(the_text)) { 295 if (!try utils.isTgWhitespaceStr(the_text)) {
204 const cd = try utils.getCD(); 296 const lc = try utils.getLetterCasing();
205 const uppercased = try cd.toUpperStr(bot.allocator, the_text); 297 const uppercased = try lc.toUpperStr(bot.allocator, the_text);
206 defer bot.allocator.free(uppercased); 298 defer bot.allocator.free(uppercased);
207 299
208 var output = ArrayList(u8).init(bot.allocator); 300 var output = ArrayList(u8).init(bot.allocator);
diff --git a/src/types.zig b/src/types.zig
index c4c0578..b99d24e 100644
--- a/src/types.zig
+++ b/src/types.zig
@@ -21,6 +21,7 @@ pub const ChatMemberUpdated = @import("types/ChatMemberUpdated.zig");
21pub const ChatShared = @import("types/ChatShared.zig"); 21pub const ChatShared = @import("types/ChatShared.zig");
22pub const ChosenInlineResult = @import("types/ChosenInlineResult.zig"); 22pub const ChosenInlineResult = @import("types/ChosenInlineResult.zig");
23pub const Contact = @import("types/Contact.zig"); 23pub const Contact = @import("types/Contact.zig");
24pub const DeleteMessageParams = @import("types/DeleteMessageParams.zig");
24pub const Dice = @import("types/Dice.zig"); 25pub const Dice = @import("types/Dice.zig");
25pub const Document = @import("types/Document.zig"); 26pub const Document = @import("types/Document.zig");
26pub const EncryptedCredentials = @import("types/EncryptedCredentials.zig"); 27pub const EncryptedCredentials = @import("types/EncryptedCredentials.zig");
diff --git a/src/types/DeleteMessageParams.zig b/src/types/DeleteMessageParams.zig
new file mode 100644
index 0000000..9453f83
--- /dev/null
+++ b/src/types/DeleteMessageParams.zig
@@ -0,0 +1,3 @@
1// TODO: Integer or String
2chat_id: i64,
3message_id: u64,
diff --git a/src/utils.zig b/src/utils.zig
index b739f6f..b5d9f19 100644
--- a/src/utils.zig
+++ b/src/utils.zig
@@ -2,8 +2,8 @@ const std = @import("std");
2 2
3const Allocator = std.mem.Allocator; 3const Allocator = std.mem.Allocator;
4const ArrayList = std.ArrayList; 4const ArrayList = std.ArrayList;
5const CaseData = @import("CaseData"); 5const GeneralCategories = @import("GeneralCategories");
6const GenCatData = @import("GenCatData"); 6const LetterCasing = @import("LetterCasing");
7const Utf8View = std.unicode.Utf8View; 7const Utf8View = std.unicode.Utf8View;
8 8
9pub fn escapeXml(writer: anytype, text: []const u8) !void { 9pub fn escapeXml(writer: anytype, text: []const u8) !void {
@@ -18,41 +18,41 @@ pub fn escapeXml(writer: anytype, text: []const u8) !void {
18 } 18 }
19} 19}
20 20
21var gcd_global: ?GenCatData = null; 21var gc_global: ?GeneralCategories = null;
22 22
23pub fn getGCD() !GenCatData { 23pub fn getGC() !GeneralCategories {
24 if (gcd_global) |gcd| { 24 if (gc_global) |gc| {
25 return gcd; 25 return gc;
26 } 26 }
27 gcd_global = try GenCatData.init(std.heap.page_allocator); 27 gc_global = try GeneralCategories.init(std.heap.page_allocator);
28 return gcd_global.?; 28 return gc_global.?;
29} 29}
30 30
31var cd_global: ?CaseData = null; 31var lc_global: ?LetterCasing = null;
32 32
33pub fn getCD() !CaseData { 33pub fn getLetterCasing() !LetterCasing {
34 if (cd_global) |cd| { 34 if (lc_global) |lc| {
35 return cd; 35 return lc;
36 } 36 }
37 cd_global = try CaseData.init(std.heap.page_allocator); 37 lc_global = try LetterCasing.init(std.heap.page_allocator);
38 return cd_global.?; 38 return lc_global.?;
39} 39}
40 40
41pub inline fn isNull(value: anytype) bool { 41pub inline fn isNull(value: anytype) bool {
42 return switch (@typeInfo(@TypeOf(value))) { 42 return switch (@typeInfo(@TypeOf(value))) {
43 .Null => true, 43 .null => true,
44 .Optional => value == null, 44 .optional => value == null,
45 else => false, 45 else => false,
46 }; 46 };
47} 47}
48 48
49pub fn isTgWhitespaceStr(str: []const u8) !bool { 49pub fn isTgWhitespaceStr(str: []const u8) !bool {
50 const view = try Utf8View.init(str); 50 const view = try Utf8View.init(str);
51 const gcd = try getGCD(); 51 const gc = try getGC();
52 52
53 var it = view.iterator(); 53 var it = view.iterator();
54 while (it.nextCodepoint()) |cp| { 54 while (it.nextCodepoint()) |cp| {
55 if (!isTgWhitespace(gcd, cp)) { 55 if (!isTgWhitespace(gc, cp)) {
56 return false; 56 return false;
57 } 57 }
58 } 58 }
@@ -60,7 +60,7 @@ pub fn isTgWhitespaceStr(str: []const u8) !bool {
60 return true; 60 return true;
61} 61}
62 62
63inline fn isTgWhitespace(gcd: GenCatData, cp: u21) bool { 63inline fn isTgWhitespace(gc: GeneralCategories, cp: u21) bool {
64 return gcd.isSeparator(cp) or gcd.isControl(cp) or cp == 0x2800 // BRAILLE PATTERN BLANK, telegram treats messages with just this as invalid messages 64 return gc.isSeparator(cp) or gc.isControl(cp) or cp == 0x2800 // BRAILLE PATTERN BLANK, telegram treats messages with just this as invalid messages
65 ; 65 ;
66} 66}