From 3fc84a75b3778a8d5b44704a97f82981f9305a47 Mon Sep 17 00:00:00 2001 From: Tommaso Morganti Date: Sat, 11 Apr 2026 23:12:49 +0200 Subject: [PATCH 1/2] fix: offload message deletion with raw api call --- src/commands/index.ts | 4 ++-- src/commands/invite.ts | 5 ++--- src/commands/link-admin-dashboard.ts | 2 +- src/commands/moderation/ban.ts | 14 ++++++-------- src/commands/moderation/del.ts | 2 +- src/commands/moderation/kick.ts | 2 +- src/commands/moderation/mute.ts | 14 ++++++-------- src/commands/pin.ts | 17 ++++++++--------- src/lib/managed-commands/index.ts | 6 ++---- src/utils/messages.ts | 8 ++++---- src/utils/types.ts | 5 +++++ src/utils/users.ts | 1 - 12 files changed, 38 insertions(+), 42 deletions(-) diff --git a/src/commands/index.ts b/src/commands/index.ts index a7b4faa..21e757e 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -39,7 +39,7 @@ export const commands = new ManagedCommands @@ -55,7 +55,7 @@ export const commands = new ManagedCommands n`You are not allowed to execute this command`))) + void ephemeral(context.reply(fmt(({ n }) => n`You are not allowed to execute this command`))) }, conversationBegin: async ({ context, command, conversation }) => { const now = await conversation.now() diff --git a/src/commands/invite.ts b/src/commands/invite.ts index b13e7d2..bcf82ec 100644 --- a/src/commands/invite.ts +++ b/src/commands/invite.ts @@ -13,9 +13,8 @@ export const invite = new CommandsCollection().createCommand({ const inviteLink = chat.invite_link ?? (await api.tg.groups.getById.query({ telegramId: context.chatId }).catch(() => null))?.link - if (!inviteLink) - return await ephemeral(context.reply(fmt(({ n }) => n`❌ Cannot retrieve the invite link`)), 10_000) + if (!inviteLink) return void ephemeral(context.reply(fmt(({ n }) => n`❌ Cannot retrieve the invite link`)), 10_000) - await ephemeral(context.reply(fmt(({ n }) => n`🔗 ${inviteLink}`))) + void ephemeral(context.reply(fmt(({ n }) => n`🔗 ${inviteLink}`))) }, }) diff --git a/src/commands/link-admin-dashboard.ts b/src/commands/link-admin-dashboard.ts index f48c344..30db51f 100644 --- a/src/commands/link-admin-dashboard.ts +++ b/src/commands/link-admin-dashboard.ts @@ -109,6 +109,6 @@ export const linkAdminDashboard = new CommandsCollection().createCommand({ ) } - await ephemeral(msg) + void ephemeral(msg) }, }) diff --git a/src/commands/moderation/ban.ts b/src/commands/moderation/ban.ts index 00d4254..46e66af 100644 --- a/src/commands/moderation/ban.ts +++ b/src/commands/moderation/ban.ts @@ -32,7 +32,7 @@ export const ban = new CommandsCollection("Banning") handler: async ({ args, context, repliedTo }) => { const userOverload = await getOverloadUser(context, repliedTo, args.reasonOrUser, args.reason) if (userOverload.isErr()) { - await ephemeral( + void ephemeral( context.reply( repliedTo ? fmt(({ n }) => n`There was an error`) @@ -53,7 +53,7 @@ export const ban = new CommandsCollection("Banning") repliedTo ? [repliedTo] : undefined, reason ) - if (res.isErr()) await ephemeral(context.reply(res.error.fmtError)) + if (res.isErr()) void ephemeral(context.reply(res.error.fmtError)) }, }) .createCommand({ @@ -89,7 +89,7 @@ export const ban = new CommandsCollection("Banning") [repliedTo], args.reason ) - if (res.isErr()) await ephemeral(context.reply(res.error.fmtError)) + if (res.isErr()) void ephemeral(context.reply(res.error.fmtError)) }, }) .createCommand({ @@ -108,18 +108,16 @@ export const ban = new CommandsCollection("Banning") if (!userId) { logger.debug(`unban: no userId for username ${args.username}`) - await ephemeral(context.reply(fmt(({ b }) => b`@${context.from.username} user not found`))) - return + return void ephemeral(context.reply(fmt(({ b }) => b`@${context.from.username} user not found`))) } const user = await getUser(userId, context) if (!user) { logger.error({ userId }, "UNBAN: cannot retrieve the user") - await ephemeral(context.reply(fmt(({ n }) => [n`Error: cannot find this user`]))) - return + return void ephemeral(context.reply(fmt(({ n }) => [n`Error: cannot find this user`]))) } const res = await Moderation.unban(user, context.chat, context.from) - if (res.isErr()) await ephemeral(context.reply(res.error.fmtError)) + if (res.isErr()) void ephemeral(context.reply(res.error.fmtError)) }, }) diff --git a/src/commands/moderation/del.ts b/src/commands/moderation/del.ts index bba4061..9567973 100644 --- a/src/commands/moderation/del.ts +++ b/src/commands/moderation/del.ts @@ -24,6 +24,6 @@ export const del = new CommandsCollection("Deletion").createCommand({ }) const res = await Moderation.deleteMessages([repliedTo], context.from, "Command /del") - if (res.isErr()) await ephemeral(context.reply(fmt(({ n }) => n`Cannot delete the message`))) + if (res.isErr()) void ephemeral(context.reply(fmt(({ n }) => n`Cannot delete the message`))) }, }) diff --git a/src/commands/moderation/kick.ts b/src/commands/moderation/kick.ts index c965999..dc7bb0e 100644 --- a/src/commands/moderation/kick.ts +++ b/src/commands/moderation/kick.ts @@ -22,6 +22,6 @@ export const kick = new CommandsCollection("Kicking").createCommand({ } const res = await Moderation.kick(repliedTo.from, context.chat, context.from, [repliedTo], args.reason) - if (res.isErr()) await ephemeral(context.reply(res.error.fmtError)) + if (res.isErr()) void ephemeral(context.reply(res.error.fmtError)) }, }) diff --git a/src/commands/moderation/mute.ts b/src/commands/moderation/mute.ts index 00ac7f1..97fd62c 100644 --- a/src/commands/moderation/mute.ts +++ b/src/commands/moderation/mute.ts @@ -42,7 +42,7 @@ export const mute = new CommandsCollection("Muting") [repliedTo], args.reason ) - if (res.isErr()) await ephemeral(context.reply(res.error.fmtError)) + if (res.isErr()) void ephemeral(context.reply(res.error.fmtError)) }, }) .createCommand({ @@ -68,7 +68,7 @@ export const mute = new CommandsCollection("Muting") handler: async ({ args, context, repliedTo }) => { const userOverload = await getOverloadUser(context, repliedTo, args.reasonOrUser, args.reason) if (userOverload.isErr()) { - await ephemeral( + void ephemeral( context.reply( repliedTo ? fmt(({ n }) => n`There was an error`) @@ -89,7 +89,7 @@ export const mute = new CommandsCollection("Muting") repliedTo ? [repliedTo] : undefined, reason ) - if (res.isErr()) await ephemeral(context.reply(res.error.fmtError)) + if (res.isErr()) void ephemeral(context.reply(res.error.fmtError)) }, }) .createCommand({ @@ -108,19 +108,17 @@ export const mute = new CommandsCollection("Muting") if (!userId) { logger.debug(`unmute: no userId for username ${args.username}`) const msg = await context.reply(fmt(({ b }) => b`@${context.from.username} user not found`)) - await ephemeral(msg) - return + return void ephemeral(msg) } const user = await getUser(userId, context) if (!user) { const msg = await context.reply(fmt(({ n }) => n`Error: cannot find this user`)) logger.error({ userId }, "UNMUTE: cannot retrieve the user") - await ephemeral(msg) - return + return void ephemeral(msg) } const res = await Moderation.unmute(user, context.chat, context.from) - if (res.isErr()) await ephemeral(context.reply(res.error.fmtError)) + if (res.isErr()) void ephemeral(context.reply(res.error.fmtError)) }, }) diff --git a/src/commands/pin.ts b/src/commands/pin.ts index ccc76de..11ddc15 100644 --- a/src/commands/pin.ts +++ b/src/commands/pin.ts @@ -1,5 +1,4 @@ import { CommandsCollection } from "@/lib/managed-commands" -import { logger } from "@/logger" import { fmt } from "@/utils/format" import { ephemeral } from "@/utils/messages" import type { Role } from "@/utils/types" @@ -17,18 +16,18 @@ export const pin = new CommandsCollection() handler: async ({ context, repliedTo }) => { const member = await context.getChatMember(context.me.id) if (member.status !== "administrator") - return await ephemeral(context.reply(fmt(({ n }) => n`❌ The bot is not an admin`)), 10_000) + return void ephemeral(context.reply(fmt(({ n }) => n`❌ The bot is not an admin`)), 10_000) if (!member.can_pin_messages) - return await ephemeral( + return void ephemeral( context.reply(fmt(({ n, code }) => n`❌ The bot is missing the ${code`Pin messages`} permission.`)), 10_000 ) const res = await context.pinChatMessage(repliedTo.message_id).catch(() => false) - if (!res) return await ephemeral(context.reply(fmt(({ n }) => n`❌ Cannot pin the message`)), 10_000) + if (!res) return void ephemeral(context.reply(fmt(({ n }) => n`❌ Cannot pin the message`)), 10_000) - await ephemeral(context.reply(fmt(({ n }) => n`✅ Message pinned`)), 10_000) + void ephemeral(context.reply(fmt(({ n }) => n`✅ Message pinned`)), 10_000) }, }) .createCommand({ @@ -43,17 +42,17 @@ export const pin = new CommandsCollection() handler: async ({ context, repliedTo }) => { const member = await context.getChatMember(context.me.id) if (member.status !== "administrator") - return await ephemeral(context.reply(fmt(({ n }) => n`❌ The bot is not an admin`)), 10_000) + return void ephemeral(context.reply(fmt(({ n }) => n`❌ The bot is not an admin`)), 10_000) if (!member.can_pin_messages) - return await ephemeral( + return void ephemeral( context.reply(fmt(({ n, code }) => n`❌ The bot is missing the ${code`Pin messages`} permission.`)), 10_000 ) const res = await context.unpinChatMessage(repliedTo.message_id).catch(() => false) - if (!res) return await ephemeral(context.reply(fmt(({ n }) => n`❌ Cannot unpin the message`)), 10_000) + if (!res) return void ephemeral(context.reply(fmt(({ n }) => n`❌ Cannot unpin the message`)), 10_000) - await ephemeral(context.reply(fmt(({ n }) => n`✅ Message unpinned`)), 10_000) + void ephemeral(context.reply(fmt(({ n }) => n`✅ Message unpinned`)), 10_000) }, }) diff --git a/src/lib/managed-commands/index.ts b/src/lib/managed-commands/index.ts index 2999bdc..951c8c3 100644 --- a/src/lib/managed-commands/index.ts +++ b/src/lib/managed-commands/index.ts @@ -365,13 +365,11 @@ export class ManagedCommands< ) this.composer.command("help", async (ctx) => { - if (ctx.chat.type !== "private") { - await ephemeral( + if (ctx.chat.type !== "private") + return void ephemeral( ctx.reply(fmt(({ n, code }) => n`You can only send ${code`/help`} in private chat with the bot.`)), 10_000 ) - return - } const text = ctx.message?.text ?? "" diff --git a/src/utils/messages.ts b/src/utils/messages.ts index e90b8e9..c0ef50a 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -1,6 +1,6 @@ -import type { MessageXFragment } from "@grammyjs/hydrate/out/data/message" import type { Message, User } from "grammy/types" -import type { MaybePromise } from "./types" +import { modules } from "@/modules" +import type { MaybePromise, PartialMessage } from "./types" import { wait } from "./wait" type TextReturn = M extends { text: string } @@ -47,9 +47,9 @@ export function createFakeMessage(chatId: number, messageId: number, from: User, * @param timeout Timeout in ms, defaults to 20 seconds * @returns a void promise that resolves after the message is deleted (or if the deletion fails) */ -export async function ephemeral(message: MaybePromise, timeout = 20000): Promise { +export async function ephemeral(message: MaybePromise, timeout = 20000): Promise { const msg = await Promise.resolve(message) await wait(timeout) - .then(() => msg.delete()) + .then(() => modules.shared.api.deleteMessage(msg.chat.id, msg.message_id)) .catch(() => {}) } diff --git a/src/utils/types.ts b/src/utils/types.ts index 461e762..b6ccd85 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -42,3 +42,8 @@ export const toGrammyUser = (apiUser: Exclude Date: Sat, 11 Apr 2026 23:19:19 +0200 Subject: [PATCH 2/2] fix: avoid throwing if message promise rejects --- src/utils/messages.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/messages.ts b/src/utils/messages.ts index c0ef50a..208c7c3 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -48,7 +48,8 @@ export function createFakeMessage(chatId: number, messageId: number, from: User, * @returns a void promise that resolves after the message is deleted (or if the deletion fails) */ export async function ephemeral(message: MaybePromise, timeout = 20000): Promise { - const msg = await Promise.resolve(message) + const msg = await Promise.resolve(message).catch(() => null) + if (!msg) return await wait(timeout) .then(() => modules.shared.api.deleteMessage(msg.chat.id, msg.message_id)) .catch(() => {})