From 1d1df7b540721d58db174764a0f720e3c638a67b Mon Sep 17 00:00:00 2001 From: Uko Kokņevičs Date: Tue, 22 Jul 2025 08:23:22 +0300 Subject: Whitelist allowed inline bots Also update to Zig 0.14.1 Also don't send the animations rn --- src/Bot.zig | 12 ++-- src/Config.zig | 8 +-- src/json.zig | 6 +- src/main.zig | 120 +++++++++++++++++++++++++++++++++----- src/types.zig | 1 + src/types/DeleteMessageParams.zig | 3 + src/utils.zig | 40 ++++++------- 7 files changed, 145 insertions(+), 45 deletions(-) create mode 100644 src/types/DeleteMessageParams.zig (limited to 'src') 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 { self.* = undefined; } +pub inline fn deleteMessage(self: *Bot, args: types.DeleteMessageParams) !void { + (try self.post(bool, "deleteMessage", args)).deinit(); +} + pub inline fn editMessageText(self: *Bot, args: types.EditMessageTextParams) !Parsed(types.Message) { return self.post(types.Message, "editMessageText", args); } @@ -142,7 +146,7 @@ fn call( return res; } else |err| { std.log.warn("error when performing call: {}", .{err}); - if (tries == 20) { + if (tries == 4) { return err; } } @@ -218,9 +222,9 @@ fn wrappedCall( fn intoQueryString(allocator: Allocator, data: anytype) !?[]u8 { return switch (@typeInfo(@TypeOf(data))) { - .Null => null, - .Optional => if (data) |d| intoQueryString(allocator, d) else null, - .Struct => |s| { + .null => null, + .optional => if (data) |d| intoQueryString(allocator, d) else null, + .@"struct" => |s| { var sb = ArrayList(u8).init(allocator); defer sb.deinit(); 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 { const Nullable = blk: { const template = @typeInfo(Config); - var fields: [template.Struct.fields.len]std.builtin.Type.StructField = undefined; - for (template.Struct.fields, 0..) |template_field, field_idx| { + var fields: [template.@"struct".fields.len]std.builtin.Type.StructField = undefined; + for (template.@"struct".fields, 0..) |template_field, field_idx| { fields[field_idx] = template_field; fields[field_idx].type = ?template_field.type; } var new = template; - new.Struct.fields = &fields; - new.Struct.decls = &.{}; + new.@"struct".fields = &fields; + new.@"struct".decls = &.{}; break :blk @Type(new); }; 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) { pub fn makeJsonParseFromValueWithTag(T: type, comptime tag: [:0]const u8) JsonParseFromValue(T) { const union_info = switch (@typeInfo(T)) { - .Union => |info| blk: { + .@"union" => |info| blk: { if (info.tag_type == null) { @compileError("Only tagged unions supported, got '" ++ @typeName(T) ++ "'"); } @@ -62,12 +62,12 @@ pub fn makeJsonParseFromValueWithTag(T: type, comptime tag: [:0]const u8) JsonPa const TagType = union_info.tag_type.?; const Base = blk: { - const info = std.builtin.Type{ .Struct = .{ + const info = std.builtin.Type{ .@"struct" = .{ .layout = .auto, .fields = &.{.{ .name = tag, .type = TagType, - .default_value = null, + .default_value_ptr = null, .is_comptime = false, .alignment = 0, }}, 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 ); } +fn reportError(bot: *Bot, msg: types.Message, err: anyerror) !void { + std.log.err("While handling {}: {}", .{ msg, err }); + const msgStr = try std.json.stringifyAlloc(bot.allocator, msg, .{ + .whitespace = .indent_2, + .emit_null_optional_fields = false, + }); + defer bot.allocator.free(msgStr); + + const devMsg = try std.fmt.allocPrint(bot.allocator, "{} while handling\n
{s}
", .{ err, msgStr }); + defer bot.allocator.free(devMsg); + + bot.sendMessage_(.{ + .chat_id = bot.config.dev_group, + .text = devMsg, + .parse_mode = .html, + }) catch |err2| { + std.log.err("While trying to report the error: {}", .{err2}); + return err2; + }; +} + fn wrappedMain(bot: *Bot) !void { try bot.sendMessage_(.{ .chat_id = bot.config.dev_group, @@ -63,8 +84,9 @@ fn wrappedMain(bot: *Bot) !void { defer gup.offset = update.update_id + 1; if (update.message) |message| { - // TODO: Catch minor errors, report them - try onMessage(bot, message); + onMessage(bot, message) catch |err| { + try reportError(bot, message, err); + }; } } } @@ -80,7 +102,63 @@ fn wrappedMain(bot: *Bot) !void { }); } +const allowed_inline_bots = [_]i64{ + 90832338, // @vid + 109158646, // @bing + 114528005, // @pic + 140267078, // @gif + 154595593, // @wiki + 184730458, // @UnitConversionBot + 296635833, // @lastfmrobot + 595898211, // @DeezerMusicBot + 870410041, // @HowGayBot + 7904498194, // @tanstiktokbot +}; + +comptime { + std.testing.expect(std.sort.isSorted( + i64, + &allowed_inline_bots, + {}, + std.sort.asc(i64), + )) catch unreachable; +} + +fn orderI64(a: i64, b: i64) std.math.Order { + return std.math.order(a, b); +} + +fn isAllowedInlineBot(id: i64) bool { + return std.sort.binarySearch( + i64, + &allowed_inline_bots, + id, + orderI64, + ) != null; +} + fn onMessage(bot: *Bot, msg: types.Message) !void { + if (msg.via_bot) |via| { + if (!isAllowedInlineBot(via.id)) { + std.log.info("Deleting an unallowed inline bot message from {?s} {}", .{ via.username, via.id }); + try bot.deleteMessage(.{ + .chat_id = msg.chat.id, + .message_id = msg.message_id, + }); + + const text = try std.fmt.allocPrint(bot.allocator, "Deleted a message sent by inline bot @{?s} {}", .{ via.username, via.id }); + defer bot.allocator.free(text); + + try bot.sendMessage_(.{ + .chat_id = bot.config.dev_group, + .text = text, + .parse_mode = .html, + }); + + return; + } + } + if (msg.text) |text| { try onTextMessage(bot, msg, text); } @@ -94,12 +172,14 @@ fn onMessage(bot: *Bot, msg: types.Message) !void { fn onNewMember(bot: *Bot, msg: types.Message, new_member: types.User) !void { if (new_member.id == try bot.getId()) { + return; + // TODO: // Bot is added to a new group - return bot.sendAnimation_(.{ - .chat_id = msg.chat.id, - // TODO: lol - .animation = "CgACAgQAAx0CVcPEEgACDC5mo7YHMgOE2n3qo3e9UOyd4N-uxQACNAMAAlbuDFMRWj9LxNLBkDUE", - }); + // return bot.sendAnimation_(.{ + // .chat_id = msg.chat.id, + // // TODO: lol + // .animation = "CgACAgQAAx0CVcPEEgACDC5mo7YHMgOE2n3qo3e9UOyd4N-uxQACNAMAAlbuDFMRWj9LxNLBkDUE", + // }); } var sb = ArrayList(u8).init(bot.allocator); @@ -111,13 +191,25 @@ fn onNewMember(bot: *Bot, msg: types.Message, new_member: types.User) !void { try new_member.writeFormattedName(w); try w.writeAll("! Be on your bestest behaviour now!!"); - try bot.sendAnimation_(.{ + // TODO: + // try bot.sendAnimation_(.{ + // .chat_id = msg.chat.id, + // // TODO: lol + // .animation = "CgACAgQAAx0CVcPEEgACC9Vmo6_zCxMp3ZNXSMM1nI6nMkIhgwACNwMAAtDmDFMop6BHmV7lUTUE", + // .caption = sb.items, + // .parse_mode = .html, + // .show_caption_above_media = true, + // .reply_parameters = .{ + // .allow_sending_without_reply = true, + // .message_id = msg.message_id, + // .chat_id = msg.chat.id, + // }, + // }); + + try bot.sendMessage_(.{ .chat_id = msg.chat.id, - // TODO: lol - .animation = "CgACAgQAAx0CVcPEEgACC9Vmo6_zCxMp3ZNXSMM1nI6nMkIhgwACNwMAAtDmDFMop6BHmV7lUTUE", - .caption = sb.items, + .text = sb.items, .parse_mode = .html, - .show_caption_above_media = true, .reply_parameters = .{ .allow_sending_without_reply = true, .message_id = msg.message_id, @@ -201,8 +293,8 @@ fn onTextMessage(bot: *Bot, msg: types.Message, text: []const u8) !void { } else if (std.ascii.startsWithIgnoreCase(text, "big ")) { const the_text = text[4..]; if (!try utils.isTgWhitespaceStr(the_text)) { - const cd = try utils.getCD(); - const uppercased = try cd.toUpperStr(bot.allocator, the_text); + const lc = try utils.getLetterCasing(); + const uppercased = try lc.toUpperStr(bot.allocator, the_text); defer bot.allocator.free(uppercased); 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"); pub const ChatShared = @import("types/ChatShared.zig"); pub const ChosenInlineResult = @import("types/ChosenInlineResult.zig"); pub const Contact = @import("types/Contact.zig"); +pub const DeleteMessageParams = @import("types/DeleteMessageParams.zig"); pub const Dice = @import("types/Dice.zig"); pub const Document = @import("types/Document.zig"); pub 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 @@ +// TODO: Integer or String +chat_id: i64, +message_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"); const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; -const CaseData = @import("CaseData"); -const GenCatData = @import("GenCatData"); +const GeneralCategories = @import("GeneralCategories"); +const LetterCasing = @import("LetterCasing"); const Utf8View = std.unicode.Utf8View; pub fn escapeXml(writer: anytype, text: []const u8) !void { @@ -18,41 +18,41 @@ pub fn escapeXml(writer: anytype, text: []const u8) !void { } } -var gcd_global: ?GenCatData = null; +var gc_global: ?GeneralCategories = null; -pub fn getGCD() !GenCatData { - if (gcd_global) |gcd| { - return gcd; +pub fn getGC() !GeneralCategories { + if (gc_global) |gc| { + return gc; } - gcd_global = try GenCatData.init(std.heap.page_allocator); - return gcd_global.?; + gc_global = try GeneralCategories.init(std.heap.page_allocator); + return gc_global.?; } -var cd_global: ?CaseData = null; +var lc_global: ?LetterCasing = null; -pub fn getCD() !CaseData { - if (cd_global) |cd| { - return cd; +pub fn getLetterCasing() !LetterCasing { + if (lc_global) |lc| { + return lc; } - cd_global = try CaseData.init(std.heap.page_allocator); - return cd_global.?; + lc_global = try LetterCasing.init(std.heap.page_allocator); + return lc_global.?; } pub inline fn isNull(value: anytype) bool { return switch (@typeInfo(@TypeOf(value))) { - .Null => true, - .Optional => value == null, + .null => true, + .optional => value == null, else => false, }; } pub fn isTgWhitespaceStr(str: []const u8) !bool { const view = try Utf8View.init(str); - const gcd = try getGCD(); + const gc = try getGC(); var it = view.iterator(); while (it.nextCodepoint()) |cp| { - if (!isTgWhitespace(gcd, cp)) { + if (!isTgWhitespace(gc, cp)) { return false; } } @@ -60,7 +60,7 @@ pub fn isTgWhitespaceStr(str: []const u8) !bool { return true; } -inline fn isTgWhitespace(gcd: GenCatData, cp: u21) bool { - return gcd.isSeparator(cp) or gcd.isControl(cp) or cp == 0x2800 // BRAILLE PATTERN BLANK, telegram treats messages with just this as invalid messages +inline fn isTgWhitespace(gc: GeneralCategories, cp: u21) bool { + return gc.isSeparator(cp) or gc.isControl(cp) or cp == 0x2800 // BRAILLE PATTERN BLANK, telegram treats messages with just this as invalid messages ; } -- cgit v1.2.3