diff --git a/src/telegram/api-logging.ts b/src/telegram/api-logging.ts new file mode 100644 index 000000000..110fd4e34 --- /dev/null +++ b/src/telegram/api-logging.ts @@ -0,0 +1,41 @@ +import { danger } from "../globals.js"; +import { formatErrorMessage } from "../infra/errors.js"; +import { createSubsystemLogger } from "../logging/subsystem.js"; +import type { RuntimeEnv } from "../runtime.js"; + +export type TelegramApiLogger = (message: string) => void; + +type TelegramApiLoggingParams = { + operation: string; + fn: () => Promise; + runtime?: RuntimeEnv; + logger?: TelegramApiLogger; + shouldLog?: (err: unknown) => boolean; +}; + +const fallbackLogger = createSubsystemLogger("telegram/api"); + +function resolveTelegramApiLogger(runtime?: RuntimeEnv, logger?: TelegramApiLogger) { + if (logger) return logger; + if (runtime?.error) return runtime.error; + return (message: string) => fallbackLogger.error(message); +} + +export async function withTelegramApiErrorLogging({ + operation, + fn, + runtime, + logger, + shouldLog, +}: TelegramApiLoggingParams): Promise { + try { + return await fn(); + } catch (err) { + if (!shouldLog || shouldLog(err)) { + const errText = formatErrorMessage(err); + const log = resolveTelegramApiLogger(runtime, logger); + log(danger(`telegram ${operation} failed: ${errText}`)); + } + throw err; + } +} diff --git a/src/telegram/bot-handlers.ts b/src/telegram/bot-handlers.ts index 8dfcc5ac1..f7ddb256f 100644 --- a/src/telegram/bot-handlers.ts +++ b/src/telegram/bot-handlers.ts @@ -8,6 +8,7 @@ import { loadConfig } from "../config/config.js"; import { writeConfigFile } from "../config/io.js"; import { danger, logVerbose, warn } from "../globals.js"; import { resolveMedia } from "./bot/delivery.js"; +import { withTelegramApiErrorLogging } from "./api-logging.js"; import { resolveTelegramForumThreadId } from "./bot/helpers.js"; import type { TelegramMessage } from "./bot/types.js"; import { firstDefined, isSenderAllowed, normalizeAllowFromWithStore } from "./bot-access.js"; @@ -180,7 +181,11 @@ export const registerTelegramHandlers = ({ if (!callback) return; if (shouldSkipUpdate(ctx)) return; // Answer immediately to prevent Telegram from retrying while we process - await bot.api.answerCallbackQuery(callback.id).catch(() => {}); + await withTelegramApiErrorLogging({ + operation: "answerCallbackQuery", + runtime, + fn: () => bot.api.answerCallbackQuery(callback.id), + }).catch(() => {}); try { const data = (callback.data ?? "").trim(); const callbackMessage = callback.message; @@ -577,11 +582,14 @@ export const registerTelegramHandlers = ({ const errMsg = String(mediaErr); if (errMsg.includes("exceeds") && errMsg.includes("MB limit")) { const limitMb = Math.round(mediaMaxBytes / (1024 * 1024)); - await bot.api - .sendMessage(chatId, `⚠️ File too large. Maximum size is ${limitMb}MB.`, { - reply_to_message_id: msg.message_id, - }) - .catch(() => {}); + await withTelegramApiErrorLogging({ + operation: "sendMessage", + runtime, + fn: () => + bot.api.sendMessage(chatId, `⚠️ File too large. Maximum size is ${limitMb}MB.`, { + reply_to_message_id: msg.message_id, + }), + }).catch(() => {}); logger.warn({ chatId, error: errMsg }, "media exceeds size limit"); return; } diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index d90b6ffea..a054943a2 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -25,6 +25,7 @@ import { shouldAckReaction as shouldAckReactionGate } from "../channels/ack-reac import { resolveMentionGatingWithBypass } from "../channels/mention-gating.js"; import { resolveControlCommandGate } from "../channels/command-gating.js"; import { logInboundDrop } from "../channels/logging.js"; +import { withTelegramApiErrorLogging } from "./api-logging.js"; import { buildGroupLabel, buildSenderLabel, @@ -165,16 +166,19 @@ export const buildTelegramMessageContext = async ({ } const sendTyping = async () => { - await bot.api.sendChatAction(chatId, "typing", buildTypingThreadParams(resolvedThreadId)); + await withTelegramApiErrorLogging({ + operation: "sendChatAction", + fn: () => bot.api.sendChatAction(chatId, "typing", buildTypingThreadParams(resolvedThreadId)), + }); }; const sendRecordVoice = async () => { try { - await bot.api.sendChatAction( - chatId, - "record_voice", - buildTypingThreadParams(resolvedThreadId), - ); + await withTelegramApiErrorLogging({ + operation: "sendChatAction", + fn: () => + bot.api.sendChatAction(chatId, "record_voice", buildTypingThreadParams(resolvedThreadId)), + }); } catch (err) { logVerbose(`telegram record_voice cue failed for chat ${chatId}: ${String(err)}`); } @@ -227,19 +231,23 @@ export const buildTelegramMessageContext = async ({ }, "telegram pairing request", ); - await bot.api.sendMessage( - chatId, - [ - "Clawdbot: access not configured.", - "", - `Your Telegram user id: ${telegramUserId}`, - "", - `Pairing code: ${code}`, - "", - "Ask the bot owner to approve with:", - formatCliCommand("clawdbot pairing approve telegram "), - ].join("\n"), - ); + await withTelegramApiErrorLogging({ + operation: "sendMessage", + fn: () => + bot.api.sendMessage( + chatId, + [ + "Clawdbot: access not configured.", + "", + `Your Telegram user id: ${telegramUserId}`, + "", + `Pairing code: ${code}`, + "", + "Ask the bot owner to approve with:", + formatCliCommand("clawdbot pairing approve telegram "), + ].join("\n"), + ), + }); } } catch (err) { logVerbose(`telegram pairing reply failed for chat ${chatId}: ${String(err)}`); @@ -408,7 +416,10 @@ export const buildTelegramMessageContext = async ({ typeof api.setMessageReaction === "function" ? api.setMessageReaction.bind(api) : null; const ackReactionPromise = shouldAckReaction() && msg.message_id && reactionApi - ? reactionApi(chatId, msg.message_id, [{ type: "emoji", emoji: ackReaction }]).then( + ? withTelegramApiErrorLogging({ + operation: "setMessageReaction", + fn: () => reactionApi(chatId, msg.message_id, [{ type: "emoji", emoji: ackReaction }]), + }).then( () => true, (err) => { logVerbose(`telegram react failed for chat ${chatId}: ${String(err)}`); diff --git a/src/telegram/bot-native-commands.ts b/src/telegram/bot-native-commands.ts index e9d287d0d..3cdb3d72e 100644 --- a/src/telegram/bot-native-commands.ts +++ b/src/telegram/bot-native-commands.ts @@ -17,6 +17,7 @@ import { dispatchReplyWithBufferedBlockDispatcher } from "../auto-reply/reply/pr import { finalizeInboundContext } from "../auto-reply/reply/inbound-context.js"; import { danger, logVerbose } from "../globals.js"; import { resolveMarkdownTableMode } from "../config/markdown-tables.js"; +import { withTelegramApiErrorLogging } from "./api-logging.js"; import { normalizeTelegramCommandName, TELEGRAM_COMMAND_NAME_PATTERN, @@ -134,11 +135,17 @@ async function resolveTelegramCommandAuth(params: { const senderUsername = msg.from?.username ?? ""; if (isGroup && groupConfig?.enabled === false) { - await bot.api.sendMessage(chatId, "This group is disabled."); + await withTelegramApiErrorLogging({ + operation: "sendMessage", + fn: () => bot.api.sendMessage(chatId, "This group is disabled."), + }); return null; } if (isGroup && topicConfig?.enabled === false) { - await bot.api.sendMessage(chatId, "This topic is disabled."); + await withTelegramApiErrorLogging({ + operation: "sendMessage", + fn: () => bot.api.sendMessage(chatId, "This topic is disabled."), + }); return null; } if (requireAuth && isGroup && hasGroupAllowOverride) { @@ -150,7 +157,10 @@ async function resolveTelegramCommandAuth(params: { senderUsername, }) ) { - await bot.api.sendMessage(chatId, "You are not authorized to use this command."); + await withTelegramApiErrorLogging({ + operation: "sendMessage", + fn: () => bot.api.sendMessage(chatId, "You are not authorized to use this command."), + }); return null; } } @@ -159,7 +169,10 @@ async function resolveTelegramCommandAuth(params: { const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy; const groupPolicy = telegramCfg.groupPolicy ?? defaultGroupPolicy ?? "open"; if (groupPolicy === "disabled") { - await bot.api.sendMessage(chatId, "Telegram group commands are disabled."); + await withTelegramApiErrorLogging({ + operation: "sendMessage", + fn: () => bot.api.sendMessage(chatId, "Telegram group commands are disabled."), + }); return null; } if (groupPolicy === "allowlist" && requireAuth) { @@ -171,13 +184,19 @@ async function resolveTelegramCommandAuth(params: { senderUsername, }) ) { - await bot.api.sendMessage(chatId, "You are not authorized to use this command."); + await withTelegramApiErrorLogging({ + operation: "sendMessage", + fn: () => bot.api.sendMessage(chatId, "You are not authorized to use this command."), + }); return null; } } const groupAllowlist = resolveGroupPolicy(chatId); if (groupAllowlist.allowlistEnabled && !groupAllowlist.allowed) { - await bot.api.sendMessage(chatId, "This group is not allowed."); + await withTelegramApiErrorLogging({ + operation: "sendMessage", + fn: () => bot.api.sendMessage(chatId, "This group is not allowed."), + }); return null; } } @@ -197,7 +216,10 @@ async function resolveTelegramCommandAuth(params: { modeWhenAccessGroupsOff: "configured", }); if (requireAuth && !commandAuthorized) { - await bot.api.sendMessage(chatId, "You are not authorized to use this command."); + await withTelegramApiErrorLogging({ + operation: "sendMessage", + fn: () => bot.api.sendMessage(chatId, "You are not authorized to use this command."), + }); return null; } @@ -300,9 +322,11 @@ export const registerTelegramNativeCommands = ({ ]; if (allCommands.length > 0) { - bot.api.setMyCommands(allCommands).catch((err) => { - runtime.error?.(danger(`telegram setMyCommands failed: ${String(err)}`)); - }); + void withTelegramApiErrorLogging({ + operation: "setMyCommands", + runtime, + fn: () => bot.api.setMyCommands(allCommands), + }).catch(() => {}); if (typeof (bot as unknown as { command?: unknown }).command !== "function") { logVerbose("telegram: bot.command unavailable; skipping native handlers"); @@ -376,9 +400,14 @@ export const registerTelegramNativeCommands = ({ ); } const replyMarkup = buildInlineKeyboard(rows); - await bot.api.sendMessage(chatId, title, { - ...(replyMarkup ? { reply_markup: replyMarkup } : {}), - ...(resolvedThreadId != null ? { message_thread_id: resolvedThreadId } : {}), + await withTelegramApiErrorLogging({ + operation: "sendMessage", + runtime, + fn: () => + bot.api.sendMessage(chatId, title, { + ...(replyMarkup ? { reply_markup: replyMarkup } : {}), + ...(resolvedThreadId != null ? { message_thread_id: resolvedThreadId } : {}), + }), }); return; } @@ -492,7 +521,11 @@ export const registerTelegramNativeCommands = ({ const commandBody = `/${pluginCommand.command}${rawText ? ` ${rawText}` : ""}`; const match = matchPluginCommand(commandBody); if (!match) { - await bot.api.sendMessage(chatId, "Command not found."); + await withTelegramApiErrorLogging({ + operation: "sendMessage", + runtime, + fn: () => bot.api.sendMessage(chatId, "Command not found."), + }); return; } const auth = await resolveTelegramCommandAuth({ @@ -543,8 +576,10 @@ export const registerTelegramNativeCommands = ({ } } } else if (nativeDisabledExplicit) { - bot.api.setMyCommands([]).catch((err) => { - runtime.error?.(danger(`telegram clear commands failed: ${String(err)}`)); - }); + void withTelegramApiErrorLogging({ + operation: "setMyCommands", + runtime, + fn: () => bot.api.setMyCommands([]), + }).catch(() => {}); } }; diff --git a/src/telegram/bot.ts b/src/telegram/bot.ts index 6705d359f..d855554d0 100644 --- a/src/telegram/bot.ts +++ b/src/telegram/bot.ts @@ -24,6 +24,7 @@ import { createSubsystemLogger } from "../logging/subsystem.js"; import { formatUncaughtError } from "../infra/errors.js"; import { enqueueSystemEvent } from "../infra/system-events.js"; import { getChildLogger } from "../logging.js"; +import { withTelegramApiErrorLogging } from "./api-logging.js"; import { resolveAgentRoute } from "../routing/resolve-route.js"; import { resolveThreadSessionKeys } from "../routing/session-key.js"; import type { RuntimeEnv } from "../runtime.js"; @@ -261,7 +262,11 @@ export function createTelegramBot(opts: TelegramBotOptions) { } if (typeof botHasTopicsEnabled === "boolean") return botHasTopicsEnabled; try { - const me = (await bot.api.getMe()) as { has_topics_enabled?: boolean }; + const me = (await withTelegramApiErrorLogging({ + operation: "getMe", + runtime, + fn: () => bot.api.getMe(), + })) as { has_topics_enabled?: boolean }; botHasTopicsEnabled = Boolean(me?.has_topics_enabled); } catch (err) { logVerbose(`telegram getMe failed: ${String(err)}`); diff --git a/src/telegram/bot/delivery.ts b/src/telegram/bot/delivery.ts index 7a3748e5b..c2489300c 100644 --- a/src/telegram/bot/delivery.ts +++ b/src/telegram/bot/delivery.ts @@ -4,6 +4,7 @@ import { markdownToTelegramHtml, renderTelegramHtmlText, } from "../format.js"; +import { withTelegramApiErrorLogging } from "../api-logging.js"; import { chunkMarkdownTextWithMode, type ChunkMode } from "../../auto-reply/chunk.js"; import { splitTelegramCaption } from "../caption.js"; import type { ReplyPayload } from "../../auto-reply/types.js"; @@ -25,24 +26,6 @@ import type { TelegramContext } from "./types.js"; const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i; const VOICE_FORBIDDEN_RE = /VOICE_MESSAGES_FORBIDDEN/; -/** - * Wraps a Telegram API call with error logging. Ensures network failures are - * logged with context before propagating, preventing silent unhandled rejections. - */ -async function withMediaErrorHandler( - operation: string, - runtime: RuntimeEnv, - fn: () => Promise, -): Promise { - try { - return await fn(); - } catch (err) { - const errText = formatErrorMessage(err); - runtime.error?.(danger(`telegram ${operation} failed: ${errText}`)); - throw err; - } -} - export async function deliverReplies(params: { replies: ReplyPayload[]; chatId: string; @@ -164,17 +147,23 @@ export async function deliverReplies(params: { mediaParams.message_thread_id = threadParams.message_thread_id; } if (isGif) { - await withMediaErrorHandler("sendAnimation", runtime, () => - bot.api.sendAnimation(chatId, file, { ...mediaParams }), - ); + await withTelegramApiErrorLogging({ + operation: "sendAnimation", + runtime, + fn: () => bot.api.sendAnimation(chatId, file, { ...mediaParams }), + }); } else if (kind === "image") { - await withMediaErrorHandler("sendPhoto", runtime, () => - bot.api.sendPhoto(chatId, file, { ...mediaParams }), - ); + await withTelegramApiErrorLogging({ + operation: "sendPhoto", + runtime, + fn: () => bot.api.sendPhoto(chatId, file, { ...mediaParams }), + }); } else if (kind === "video") { - await withMediaErrorHandler("sendVideo", runtime, () => - bot.api.sendVideo(chatId, file, { ...mediaParams }), - ); + await withTelegramApiErrorLogging({ + operation: "sendVideo", + runtime, + fn: () => bot.api.sendVideo(chatId, file, { ...mediaParams }), + }); } else if (kind === "audio") { const { useVoice } = resolveTelegramVoiceSend({ wantsVoice: reply.audioAsVoice === true, // default false (backward compatible) @@ -187,9 +176,12 @@ export async function deliverReplies(params: { // Switch typing indicator to record_voice before sending. await params.onVoiceRecording?.(); try { - await withMediaErrorHandler("sendVoice", runtime, () => - bot.api.sendVoice(chatId, file, { ...mediaParams }), - ); + await withTelegramApiErrorLogging({ + operation: "sendVoice", + runtime, + shouldLog: (err) => !isVoiceMessagesForbidden(err), + fn: () => bot.api.sendVoice(chatId, file, { ...mediaParams }), + }); } catch (voiceErr) { // Fall back to text if voice messages are forbidden in this chat. // This happens when the recipient has Telegram Premium privacy settings @@ -222,14 +214,18 @@ export async function deliverReplies(params: { } } else { // Audio file - displays with metadata (title, duration) - DEFAULT - await withMediaErrorHandler("sendAudio", runtime, () => - bot.api.sendAudio(chatId, file, { ...mediaParams }), - ); + await withTelegramApiErrorLogging({ + operation: "sendAudio", + runtime, + fn: () => bot.api.sendAudio(chatId, file, { ...mediaParams }), + }); } } else { - await withMediaErrorHandler("sendDocument", runtime, () => - bot.api.sendDocument(chatId, file, { ...mediaParams }), - ); + await withTelegramApiErrorLogging({ + operation: "sendDocument", + runtime, + fn: () => bot.api.sendDocument(chatId, file, { ...mediaParams }), + }); } if (replyToId && !hasReplied) { hasReplied = true; @@ -371,11 +367,17 @@ async function sendTelegramText( const textMode = opts?.textMode ?? "markdown"; const htmlText = textMode === "html" ? text : markdownToTelegramHtml(text); try { - const res = await bot.api.sendMessage(chatId, htmlText, { - parse_mode: "HTML", - ...(linkPreviewOptions ? { link_preview_options: linkPreviewOptions } : {}), - ...(opts?.replyMarkup ? { reply_markup: opts.replyMarkup } : {}), - ...baseParams, + const res = await withTelegramApiErrorLogging({ + operation: "sendMessage", + runtime, + shouldLog: (err) => !PARSE_ERR_RE.test(formatErrorMessage(err)), + fn: () => + bot.api.sendMessage(chatId, htmlText, { + parse_mode: "HTML", + ...(linkPreviewOptions ? { link_preview_options: linkPreviewOptions } : {}), + ...(opts?.replyMarkup ? { reply_markup: opts.replyMarkup } : {}), + ...baseParams, + }), }); return res.message_id; } catch (err) { @@ -383,10 +385,15 @@ async function sendTelegramText( if (PARSE_ERR_RE.test(errText)) { runtime.log?.(`telegram HTML parse failed; retrying without formatting: ${errText}`); const fallbackText = opts?.plainText ?? text; - const res = await bot.api.sendMessage(chatId, fallbackText, { - ...(linkPreviewOptions ? { link_preview_options: linkPreviewOptions } : {}), - ...(opts?.replyMarkup ? { reply_markup: opts.replyMarkup } : {}), - ...baseParams, + const res = await withTelegramApiErrorLogging({ + operation: "sendMessage", + runtime, + fn: () => + bot.api.sendMessage(chatId, fallbackText, { + ...(linkPreviewOptions ? { link_preview_options: linkPreviewOptions } : {}), + ...(opts?.replyMarkup ? { reply_markup: opts.replyMarkup } : {}), + ...baseParams, + }), }); return res.message_id; } diff --git a/src/telegram/send.ts b/src/telegram/send.ts index d28cff55e..92cd3ddc1 100644 --- a/src/telegram/send.ts +++ b/src/telegram/send.ts @@ -8,6 +8,7 @@ import { type ApiClientOptions, Bot, HttpError, InputFile } from "grammy"; import { loadConfig } from "../config/config.js"; import { logVerbose } from "../globals.js"; import { recordChannelActivity } from "../infra/channel-activity.js"; +import { withTelegramApiErrorLogging } from "./api-logging.js"; import { formatErrorMessage, formatUncaughtError } from "../infra/errors.js"; import { isDiagnosticFlagEnabled } from "../infra/diagnostic-flags.js"; import type { RetryConfig } from "../infra/retry.js"; @@ -210,7 +211,10 @@ export async function sendMessageTelegram( }); const logHttpError = createTelegramHttpLogger(cfg); const requestWithDiag = (fn: () => Promise, label?: string) => - request(fn, label).catch((err) => { + withTelegramApiErrorLogging({ + operation: label ?? "request", + fn: () => request(fn, label), + }).catch((err) => { logHttpError(label ?? "request", err); throw err; }); @@ -442,7 +446,10 @@ export async function reactMessageTelegram( }); const logHttpError = createTelegramHttpLogger(cfg); const requestWithDiag = (fn: () => Promise, label?: string) => - request(fn, label).catch((err) => { + withTelegramApiErrorLogging({ + operation: label ?? "request", + fn: () => request(fn, label), + }).catch((err) => { logHttpError(label ?? "request", err); throw err; }); @@ -492,7 +499,10 @@ export async function deleteMessageTelegram( }); const logHttpError = createTelegramHttpLogger(cfg); const requestWithDiag = (fn: () => Promise, label?: string) => - request(fn, label).catch((err) => { + withTelegramApiErrorLogging({ + operation: label ?? "request", + fn: () => request(fn, label), + }).catch((err) => { logHttpError(label ?? "request", err); throw err; }); @@ -537,7 +547,10 @@ export async function editMessageTelegram( }); const logHttpError = createTelegramHttpLogger(cfg); const requestWithDiag = (fn: () => Promise, label?: string) => - request(fn, label).catch((err) => { + withTelegramApiErrorLogging({ + operation: label ?? "request", + fn: () => request(fn, label), + }).catch((err) => { logHttpError(label ?? "request", err); throw err; }); diff --git a/src/telegram/webhook-set.ts b/src/telegram/webhook-set.ts index 2880c8254..0d2e815fc 100644 --- a/src/telegram/webhook-set.ts +++ b/src/telegram/webhook-set.ts @@ -1,6 +1,7 @@ import { type ApiClientOptions, Bot } from "grammy"; import type { TelegramNetworkConfig } from "../config/types.telegram.js"; import { resolveTelegramFetch } from "./fetch.js"; +import { withTelegramApiErrorLogging } from "./api-logging.js"; export async function setTelegramWebhook(opts: { token: string; @@ -14,9 +15,13 @@ export async function setTelegramWebhook(opts: { ? { fetch: fetchImpl as unknown as ApiClientOptions["fetch"] } : undefined; const bot = new Bot(opts.token, client ? { client } : undefined); - await bot.api.setWebhook(opts.url, { - secret_token: opts.secret, - drop_pending_updates: opts.dropPendingUpdates ?? false, + await withTelegramApiErrorLogging({ + operation: "setWebhook", + fn: () => + bot.api.setWebhook(opts.url, { + secret_token: opts.secret, + drop_pending_updates: opts.dropPendingUpdates ?? false, + }), }); } @@ -29,5 +34,8 @@ export async function deleteTelegramWebhook(opts: { ? { fetch: fetchImpl as unknown as ApiClientOptions["fetch"] } : undefined; const bot = new Bot(opts.token, client ? { client } : undefined); - await bot.api.deleteWebhook(); + await withTelegramApiErrorLogging({ + operation: "deleteWebhook", + fn: () => bot.api.deleteWebhook(), + }); } diff --git a/src/telegram/webhook.ts b/src/telegram/webhook.ts index 4d341bb88..d8c0a30f0 100644 --- a/src/telegram/webhook.ts +++ b/src/telegram/webhook.ts @@ -15,6 +15,7 @@ import { } from "../logging/diagnostic.js"; import { resolveTelegramAllowedUpdates } from "./allowed-updates.js"; import { createTelegramBot } from "./bot.js"; +import { withTelegramApiErrorLogging } from "./api-logging.js"; export async function startTelegramWebhook(opts: { token: string; @@ -97,9 +98,14 @@ export async function startTelegramWebhook(opts: { const publicUrl = opts.publicUrl ?? `http://${host === "0.0.0.0" ? "localhost" : host}:${port}${path}`; - await bot.api.setWebhook(publicUrl, { - secret_token: opts.secret, - allowed_updates: resolveTelegramAllowedUpdates(), + await withTelegramApiErrorLogging({ + operation: "setWebhook", + runtime, + fn: () => + bot.api.setWebhook(publicUrl, { + secret_token: opts.secret, + allowed_updates: resolveTelegramAllowedUpdates(), + }), }); await new Promise((resolve) => server.listen(port, host, resolve));