Telegram: remove @ts-nocheck from bot.ts, fix duplicate error handler, harden sticker caching (#9077)

* Telegram: remove @ts-nocheck from bot.ts and bot-message-dispatch.ts

- bot/types.ts: TelegramContext.me uses UserFromGetMe (Grammy) instead of manual inline type
- bot.ts: remove 6 unsafe casts (as any, as unknown, as object), use Grammy types directly
- bot.ts: remove dead message_thread_id access on reactions (not in Telegram Bot API)
- bot.ts: remove resolveThreadSessionKeys import (no longer needed for reactions)
- bot-message-dispatch.ts: replace ': any' with DispatchTelegramMessageParams type
- bot-message-dispatch.ts: add sticker.fileId guard before cache access
- bot.test.ts: update reaction tests, remove dead DM thread-reaction test

* Telegram: remove duplicate bot.catch handler (only the last one runs in Grammy)

* Telegram: remove @ts-nocheck from bot.ts, fix duplicate error handler, harden sticker caching (#9077)
This commit is contained in:
Christian Klotz
2026-02-04 22:35:51 +00:00
committed by GitHub
parent 38e6da1fe0
commit 96abc1c864
5 changed files with 28 additions and 84 deletions

View File

@@ -1,8 +1,7 @@
import type { ApiClientOptions } from "grammy";
// @ts-nocheck
import { sequentialize } from "@grammyjs/runner";
import { apiThrottler } from "@grammyjs/transformer-throttler";
import { type Message, ReactionTypeEmoji } from "@grammyjs/types";
import { type Message, type UserFromGetMe, ReactionTypeEmoji } from "@grammyjs/types";
import { Bot, webhookCallback } from "grammy";
import type { OpenClawConfig, ReplyToMode } from "../config/config.js";
import type { RuntimeEnv } from "../runtime.js";
@@ -28,7 +27,6 @@ import { enqueueSystemEvent } from "../infra/system-events.js";
import { getChildLogger } from "../logging.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { resolveAgentRoute } from "../routing/resolve-route.js";
import { resolveThreadSessionKeys } from "../routing/session-key.js";
import { resolveTelegramAccount } from "./accounts.js";
import { withTelegramApiErrorLogging } from "./api-logging.js";
import { registerTelegramHandlers } from "./bot-handlers.js";
@@ -67,6 +65,7 @@ export type TelegramBotOptions = {
export function getTelegramSequentialKey(ctx: {
chat?: { id?: number };
me?: UserFromGetMe;
message?: Message;
update?: {
message?: Message;
@@ -87,7 +86,7 @@ export function getTelegramSequentialKey(ctx: {
ctx.update?.callback_query?.message;
const chatId = msg?.chat?.id ?? ctx.chat?.id;
const rawText = msg?.text ?? msg?.caption;
const botUsername = (ctx as { me?: { username?: string } }).me?.username;
const botUsername = ctx.me?.username;
if (
rawText &&
isControlCommandMessage(rawText, undefined, botUsername ? { botUsername } : undefined)
@@ -99,7 +98,7 @@ export function getTelegramSequentialKey(ctx: {
}
const isGroup = msg?.chat?.type === "group" || msg?.chat?.type === "supergroup";
const messageThreadId = msg?.message_thread_id;
const isForum = (msg?.chat as { is_forum?: boolean } | undefined)?.is_forum;
const isForum = msg?.chat?.is_forum;
const threadId = isGroup
? resolveTelegramForumThreadId({ isForum, messageThreadId })
: messageThreadId;
@@ -135,9 +134,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
const client: ApiClientOptions | undefined =
shouldProvideFetch || timeoutSeconds
? {
...(shouldProvideFetch && fetchImpl
? { fetch: fetchImpl as unknown as ApiClientOptions["fetch"] }
: {}),
...(shouldProvideFetch && fetchImpl ? { fetch: fetchImpl } : {}),
...(timeoutSeconds ? { timeoutSeconds } : {}),
}
: undefined;
@@ -145,14 +142,9 @@ export function createTelegramBot(opts: TelegramBotOptions) {
const bot = new Bot(opts.token, client ? { client } : undefined);
bot.api.config.use(apiThrottler());
bot.use(sequentialize(getTelegramSequentialKey));
bot.catch((err) => {
runtime.error?.(danger(`telegram bot error: ${formatUncaughtError(err)}`));
});
// Catch all errors from bot middleware to prevent unhandled rejections
bot.catch((err) => {
const message = err instanceof Error ? err.message : String(err);
runtime.error?.(danger(`telegram bot error: ${message}`));
runtime.error?.(danger(`telegram bot error: ${formatUncaughtError(err)}`));
});
const recentUpdates = createTelegramUpdateDedupe();
@@ -203,11 +195,10 @@ export function createTelegramBot(opts: TelegramBotOptions) {
];
}
if (value && typeof value === "object") {
const obj = value as object;
if (seen.has(obj)) {
if (seen.has(value)) {
return "[Circular]";
}
seen.add(obj);
seen.add(value);
}
return value;
});
@@ -267,9 +258,8 @@ export function createTelegramBot(opts: TelegramBotOptions) {
const streamMode = resolveTelegramStreamMode(telegramCfg);
let botHasTopicsEnabled: boolean | undefined;
const resolveBotTopicsEnabled = async (ctx?: TelegramContext) => {
const fromCtx = ctx?.me as { has_topics_enabled?: boolean } | undefined;
if (typeof fromCtx?.has_topics_enabled === "boolean") {
botHasTopicsEnabled = fromCtx.has_topics_enabled;
if (typeof ctx?.me?.has_topics_enabled === "boolean") {
botHasTopicsEnabled = ctx.me.has_topics_enabled;
return botHasTopicsEnabled;
}
if (typeof botHasTopicsEnabled === "boolean") {
@@ -280,11 +270,11 @@ export function createTelegramBot(opts: TelegramBotOptions) {
return botHasTopicsEnabled;
}
try {
const me = (await withTelegramApiErrorLogging({
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)}`);
@@ -445,18 +435,14 @@ export function createTelegramBot(opts: TelegramBotOptions) {
}
senderLabel = senderLabel || "unknown";
// Extract forum thread info (similar to message processing)
// oxlint-disable-next-line typescript/no-explicit-any
const messageThreadId = (reaction as any).message_thread_id;
// oxlint-disable-next-line typescript/no-explicit-any
const isForum = (reaction.chat as any).is_forum === true;
const resolvedThreadId = resolveTelegramForumThreadId({
isForum,
messageThreadId,
});
// Resolve agent route for session
// Reactions target a specific message_id; the Telegram Bot API does not include
// message_thread_id on MessageReactionUpdated, so we route to the chat-level
// session (forum topic routing is not available for reactions).
const isGroup = reaction.chat.type === "group" || reaction.chat.type === "supergroup";
const isForum = reaction.chat.is_forum === true;
const resolvedThreadId = isForum
? resolveTelegramForumThreadId({ isForum, messageThreadId: undefined })
: undefined;
const peerId = isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : String(chatId);
const route = resolveAgentRoute({
cfg,
@@ -464,14 +450,7 @@ export function createTelegramBot(opts: TelegramBotOptions) {
accountId: account.accountId,
peer: { kind: isGroup ? "group" : "dm", id: peerId },
});
const baseSessionKey = route.sessionKey;
// DMs: use raw messageThreadId for thread sessions (not resolvedThreadId which is for forums)
const dmThreadId = !isGroup ? messageThreadId : undefined;
const threadKeys =
dmThreadId != null
? resolveThreadSessionKeys({ baseSessionKey, threadId: String(dmThreadId) })
: null;
const sessionKey = threadKeys?.sessionKey ?? baseSessionKey;
const sessionKey = route.sessionKey;
// Enqueue system event for each added reaction
for (const r of addedReactions) {