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