From ab52104f4c9481dffd728c833677570f8f463da5 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 28 Jan 2026 07:09:50 +0100 Subject: [PATCH 1/2] Telegram: restore types after removing ts-nocheck --- src/telegram/bot-handlers.ts | 59 ++++++++++++++++++++++------ src/telegram/bot-message-context.ts | 34 ++++++++++++++-- src/telegram/bot-message-dispatch.ts | 45 +++++++++++++++------ src/telegram/bot-message.ts | 57 +++++++++++++++++++++++++-- src/telegram/bot-native-commands.ts | 3 +- src/telegram/bot.ts | 10 +++-- src/telegram/proxy.ts | 7 +++- 7 files changed, 179 insertions(+), 36 deletions(-) diff --git a/src/telegram/bot-handlers.ts b/src/telegram/bot-handlers.ts index 477b98280..e1787a49a 100644 --- a/src/telegram/bot-handlers.ts +++ b/src/telegram/bot-handlers.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import { hasControlCommand } from "../auto-reply/command-detection.js"; import { createInboundDebouncer, @@ -14,14 +13,52 @@ 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 type { TelegramContext, TelegramMessage } from "./bot/types.js"; import { firstDefined, isSenderAllowed, normalizeAllowFromWithStore } from "./bot-access.js"; import { MEDIA_GROUP_TIMEOUT_MS, type MediaGroupEntry } from "./bot-updates.js"; +import type { TelegramUpdateKeyContext } from "./bot-updates.js"; import { migrateTelegramGroupConfig } from "./group-migration.js"; import { resolveTelegramInlineButtonsScope } from "./inline-buttons.js"; import { readTelegramAllowFromStore } from "./pairing-store.js"; import { resolveChannelConfigWrites } from "../channels/plugins/config-writes.js"; import { buildInlineKeyboard } from "./send.js"; +import type { Bot } from "grammy"; +import type { MoltbotConfig } from "../config/types.clawdbot.js"; +import type { + TelegramAccountConfig, + TelegramGroupConfig, + TelegramTopicConfig, +} from "../config/types.telegram.js"; +import type { RuntimeEnv } from "../runtime.js"; +import type { ChannelGroupPolicy } from "../config/group-policy.js"; + +type TelegramHandlersParams = { + cfg: MoltbotConfig; + accountId: string | undefined; + bot: Bot; + opts: { token: string; proxyFetch?: typeof fetch }; + runtime: RuntimeEnv; + mediaMaxBytes: number; + telegramCfg: TelegramAccountConfig; + groupAllowFrom?: Array; + resolveGroupPolicy: (chatId: string | number) => ChannelGroupPolicy; + resolveTelegramGroupConfig: ( + chatId: string | number, + messageThreadId?: number, + ) => { groupConfig?: TelegramGroupConfig; topicConfig?: TelegramTopicConfig }; + shouldSkipUpdate: (ctx: TelegramUpdateKeyContext) => boolean; + processMessage: ( + primaryCtx: TelegramContext, + allMedia: Array<{ + path: string; + contentType?: string; + stickerMetadata?: { emoji?: string; setName?: string; fileId?: string }; + }>, + storeAllowFrom: string[], + options?: { forceWasMentioned?: boolean; messageIdOverride?: string }, + ) => Promise; + logger: { info: (obj: Record, msg: string) => void }; +}; export const registerTelegramHandlers = ({ cfg, @@ -37,7 +74,7 @@ export const registerTelegramHandlers = ({ shouldSkipUpdate, processMessage, logger, -}) => { +}: TelegramHandlersParams) => { const TELEGRAM_TEXT_FRAGMENT_START_THRESHOLD_CHARS = 4000; const TELEGRAM_TEXT_FRAGMENT_MAX_GAP_MS = 1500; const TELEGRAM_TEXT_FRAGMENT_MAX_ID_GAP = 1; @@ -49,7 +86,7 @@ export const registerTelegramHandlers = ({ type TextFragmentEntry = { key: string; - messages: Array<{ msg: TelegramMessage; ctx: unknown; receivedAtMs: number }>; + messages: Array<{ msg: TelegramMessage; ctx: TelegramContext; receivedAtMs: number }>; timer: ReturnType; }; const textFragmentBuffer = new Map(); @@ -57,7 +94,7 @@ export const registerTelegramHandlers = ({ const debounceMs = resolveInboundDebounceMs({ cfg, channel: "telegram" }); type TelegramDebounceEntry = { - ctx: unknown; + ctx: TelegramContext; msg: TelegramMessage; allMedia: Array<{ path: string; contentType?: string }>; storeAllowFrom: string[]; @@ -86,9 +123,8 @@ export const registerTelegramHandlers = ({ .join("\n"); if (!combinedText.trim()) return; const first = entries[0]; - const baseCtx = first.ctx as { me?: unknown; getFile?: unknown } & Record; - const getFile = - typeof baseCtx.getFile === "function" ? baseCtx.getFile.bind(baseCtx) : async () => ({}); + const baseCtx = first.ctx; + const getFile = baseCtx.getFile.bind(baseCtx) ?? (async () => ({})); const syntheticMessage: TelegramMessage = { ...first.msg, text: combinedText, @@ -161,9 +197,8 @@ export const registerTelegramHandlers = ({ }; const storeAllowFrom = await readTelegramAllowFromStore().catch(() => []); - const baseCtx = first.ctx as { me?: unknown; getFile?: unknown } & Record; - const getFile = - typeof baseCtx.getFile === "function" ? baseCtx.getFile.bind(baseCtx) : async () => ({}); + const baseCtx = first.ctx; + const getFile = baseCtx.getFile.bind(baseCtx) ?? (async () => ({})); await processMessage( { message: syntheticMessage, me: baseCtx.me, getFile }, @@ -377,7 +412,7 @@ export const registerTelegramHandlers = ({ caption_entities: undefined, entities: undefined, }; - const getFile = typeof ctx.getFile === "function" ? ctx.getFile.bind(ctx) : async () => ({}); + const getFile = ctx.getFile.bind(ctx) ?? (async () => ({})); await processMessage({ message: syntheticMessage, me: ctx.me, getFile }, [], storeAllowFrom, { forceWasMentioned: true, messageIdOverride: callback.id, diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index 832a4413d..df6c377a9 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -27,6 +27,7 @@ import { logVerbose, shouldLogVerbose } from "../globals.js"; import { recordChannelActivity } from "../infra/channel-activity.js"; import { resolveAgentRoute } from "../routing/resolve-route.js"; import { resolveThreadSessionKeys } from "../routing/session-key.js"; +import type { ResolvedAgentRoute } from "../routing/resolve-route.js"; import { shouldAckReaction as shouldAckReactionGate } from "../channels/ack-reactions.js"; import { resolveMentionGatingWithBypass } from "../channels/mention-gating.js"; import { resolveControlCommandGate } from "../channels/command-gating.js"; @@ -54,6 +55,8 @@ import { } from "./bot-access.js"; import { upsertTelegramPairingRequest } from "./pairing-store.js"; import type { TelegramContext } from "./bot/types.js"; +import type { FinalizedMsgContext } from "../auto-reply/templating.js"; +import type { TelegramMessage } from "./bot/types.js"; type TelegramMediaRef = { path: string; @@ -110,6 +113,33 @@ type BuildTelegramMessageContextParams = { resolveTelegramGroupConfig: ResolveTelegramGroupConfig; }; +export type TelegramMessageContext = { + ctxPayload: FinalizedMsgContext; + primaryCtx: TelegramContext; + msg: TelegramMessage; + chatId: number; + isGroup: boolean; + resolvedThreadId?: number; + isForum: boolean; + historyKey?: string; + historyLimit: number; + groupHistories: Map; + route: ResolvedAgentRoute; + skillFilter?: string[]; + sendTyping: () => Promise; + sendRecordVoice: () => Promise; + ackReactionPromise: Promise | null; + reactionApi: + | (( + chatId: number | string, + messageId: number, + reactions: Array<{ type: "emoji"; emoji: string }>, + ) => Promise) + | null; + removeAckAfterReply: boolean; + accountId: string; +}; + async function resolveStickerVisionSupport(params: { cfg: MoltbotConfig; agentId?: string; @@ -231,9 +261,7 @@ export const buildTelegramMessageContext = async ({ senderId: candidate, senderUsername, }); - const allowMatchMeta = `matchKey=${allowMatch.matchKey ?? "none"} matchSource=${ - allowMatch.matchSource ?? "none" - }`; + const allowMatchMeta = `matchKey=${allowMatch.matchKey ?? "none"} matchSource=${allowMatch.matchSource ?? "none"}`; const allowed = effectiveDmAllow.hasWildcard || (effectiveDmAllow.hasEntries && allowMatch.allowed); if (!allowed) { diff --git a/src/telegram/bot-message-dispatch.ts b/src/telegram/bot-message-dispatch.ts index cead0628a..9a283bd2e 100644 --- a/src/telegram/bot-message-dispatch.ts +++ b/src/telegram/bot-message-dispatch.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import { EmbeddedBlockChunker } from "../agents/pi-embedded-block-chunker.js"; import { findModelInCatalog, @@ -20,8 +19,15 @@ import { resolveTelegramDraftStreamingChunking } from "./draft-chunking.js"; import { createTelegramDraftStream } from "./draft-stream.js"; import { cacheSticker, describeStickerImage } from "./sticker-cache.js"; import { resolveAgentDir } from "../agents/agent-scope.js"; +import type { MoltbotConfig } from "../config/types.clawdbot.js"; +import type { TelegramAccountConfig } from "../config/types.telegram.js"; +import type { ReplyToMode } from "../config/types.base.js"; +import type { Bot } from "grammy"; +import type { RuntimeEnv } from "../runtime.js"; +import type { TelegramContext, TelegramStreamMode } from "./bot/types.js"; +import type { TelegramMessageContext } from "./bot-message-context.js"; -async function resolveStickerVisionSupport(cfg, agentId) { +async function resolveStickerVisionSupport(cfg: MoltbotConfig, agentId: string) { try { const catalog = await loadModelCatalog({ config: cfg }); const defaultModel = resolveDefaultModelForAgent({ cfg, agentId }); @@ -44,6 +50,17 @@ export const dispatchTelegramMessage = async ({ telegramCfg, opts, resolveBotTopicsEnabled, +}: { + context: TelegramMessageContext; + bot: Bot; + cfg: MoltbotConfig; + runtime: RuntimeEnv; + replyToMode: ReplyToMode; + streamMode: TelegramStreamMode; + textLimit: number; + telegramCfg: TelegramAccountConfig; + opts: { token: string }; + resolveBotTopicsEnabled: (ctx: TelegramContext) => Promise; }) => { const { ctxPayload, @@ -185,16 +202,20 @@ export const dispatchTelegramMessage = async ({ } // Cache the description for future encounters - cacheSticker({ - fileId: sticker.fileId, - fileUniqueId: sticker.fileUniqueId, - emoji: sticker.emoji, - setName: sticker.setName, - description, - cachedAt: new Date().toISOString(), - receivedFrom: ctxPayload.From, - }); - logVerbose(`telegram: cached sticker description for ${sticker.fileUniqueId}`); + if (sticker.fileId) { + cacheSticker({ + fileId: sticker.fileId, + fileUniqueId: sticker.fileUniqueId, + emoji: sticker.emoji, + setName: sticker.setName, + description, + cachedAt: new Date().toISOString(), + receivedFrom: ctxPayload.From, + }); + logVerbose(`telegram: cached sticker description for ${sticker.fileUniqueId}`); + } else { + logVerbose(`telegram: skipped sticker cache (missing fileId) ${sticker.fileUniqueId}`); + } } } diff --git a/src/telegram/bot-message.ts b/src/telegram/bot-message.ts index 313296b1d..e085036c2 100644 --- a/src/telegram/bot-message.ts +++ b/src/telegram/bot-message.ts @@ -1,8 +1,50 @@ -// @ts-nocheck +import type { Bot } from "grammy"; import { buildTelegramMessageContext } from "./bot-message-context.js"; import { dispatchTelegramMessage } from "./bot-message-dispatch.js"; +import type { MoltbotConfig } from "../config/types.clawdbot.js"; +import type { + TelegramAccountConfig, + TelegramGroupConfig, + TelegramTopicConfig, +} from "../config/types.telegram.js"; +import type { DmPolicy, ReplyToMode } from "../config/types.base.js"; +import type { HistoryEntry } from "../auto-reply/reply/history.js"; +import type { RuntimeEnv } from "../runtime.js"; +import type { TelegramContext, TelegramStreamMode } from "./bot/types.js"; -export const createTelegramMessageProcessor = (deps) => { +export const createTelegramMessageProcessor = (deps: { + bot: Bot; + cfg: MoltbotConfig; + account: { accountId: string }; + telegramCfg: TelegramAccountConfig; + historyLimit: number; + groupHistories: Map; + dmPolicy: DmPolicy; + allowFrom?: (string | number)[]; + groupAllowFrom?: (string | number)[]; + ackReactionScope: "off" | "group-mentions" | "group-all" | "direct" | "all"; + logger: { info: (obj: Record, msg: string) => void }; + resolveGroupActivation: (params: { + chatId: string | number; + agentId?: string; + messageThreadId?: number; + sessionKey?: string; + }) => boolean | undefined; + resolveGroupRequireMention: (chatId: string | number) => boolean; + resolveTelegramGroupConfig: ( + chatId: string | number, + messageThreadId?: number, + ) => { + groupConfig?: TelegramGroupConfig; + topicConfig?: TelegramTopicConfig; + }; + runtime: RuntimeEnv; + replyToMode: ReplyToMode; + streamMode: TelegramStreamMode; + textLimit: number; + opts: { token: string }; + resolveBotTopicsEnabled: (ctx?: TelegramContext) => Promise; +}) => { const { bot, cfg, @@ -26,7 +68,16 @@ export const createTelegramMessageProcessor = (deps) => { resolveBotTopicsEnabled, } = deps; - return async (primaryCtx, allMedia, storeAllowFrom, options) => { + return async ( + primaryCtx: TelegramContext, + allMedia: Array<{ + path: string; + contentType?: string; + stickerMetadata?: { emoji?: string; setName?: string; fileId?: string }; + }>, + storeAllowFrom: string[], + options?: { forceWasMentioned?: boolean; messageIdOverride?: string }, + ) => { const context = await buildTelegramMessageContext({ primaryCtx, allMedia, diff --git a/src/telegram/bot-native-commands.ts b/src/telegram/bot-native-commands.ts index 3415ea927..59d65f8d2 100644 --- a/src/telegram/bot-native-commands.ts +++ b/src/telegram/bot-native-commands.ts @@ -49,6 +49,7 @@ import { } from "./bot/helpers.js"; import { firstDefined, isSenderAllowed, normalizeAllowFromWithStore } from "./bot-access.js"; import { readTelegramAllowFromStore } from "./pairing-store.js"; +import type { TelegramUpdateKeyContext } from "./bot-updates.js"; type TelegramNativeCommandContext = Context & { match?: string }; @@ -83,7 +84,7 @@ type RegisterTelegramNativeCommandsParams = { chatId: string | number, messageThreadId?: number, ) => { groupConfig?: TelegramGroupConfig; topicConfig?: TelegramTopicConfig }; - shouldSkipUpdate: (ctx: unknown) => boolean; + shouldSkipUpdate: (ctx: TelegramUpdateKeyContext) => boolean; opts: { token: string }; }; diff --git a/src/telegram/bot.ts b/src/telegram/bot.ts index ae21d10da..a4ee450d3 100644 --- a/src/telegram/bot.ts +++ b/src/telegram/bot.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import { sequentialize } from "@grammyjs/runner"; import { apiThrottler } from "@grammyjs/transformer-throttler"; import type { ApiClientOptions } from "grammy"; @@ -46,6 +45,7 @@ import { } from "./bot-updates.js"; import { resolveTelegramFetch } from "./fetch.js"; import { wasSentByBot } from "./sent-message-cache.js"; +import type { ReactionTypeEmoji } from "grammy/types"; export type TelegramBotOptions = { token: string; @@ -385,11 +385,15 @@ export function createTelegramBot(opts: TelegramBotOptions) { // Detect added reactions const oldEmojis = new Set( reaction.old_reaction - .filter((r): r is { type: "emoji"; emoji: string } => r.type === "emoji") + .filter( + (r): r is { type: "emoji"; emoji: ReactionTypeEmoji["emoji"] } => r.type === "emoji", + ) .map((r) => r.emoji), ); const addedReactions = reaction.new_reaction - .filter((r): r is { type: "emoji"; emoji: string } => r.type === "emoji") + .filter( + (r): r is { type: "emoji"; emoji: ReactionTypeEmoji["emoji"] } => r.type === "emoji", + ) .filter((r) => !oldEmojis.has(r.emoji)); if (addedReactions.length === 0) return; diff --git a/src/telegram/proxy.ts b/src/telegram/proxy.ts index 19d53d569..0740cbfa8 100644 --- a/src/telegram/proxy.ts +++ b/src/telegram/proxy.ts @@ -1,11 +1,14 @@ -// @ts-nocheck +import type { Dispatcher } from "undici"; import { ProxyAgent } from "undici"; import { wrapFetchWithAbortSignal } from "../infra/fetch.js"; +type FetchInit = RequestInit & { dispatcher?: Dispatcher }; + export function makeProxyFetch(proxyUrl: string): typeof fetch { const agent = new ProxyAgent(proxyUrl); return wrapFetchWithAbortSignal((input: RequestInfo | URL, init?: RequestInit) => { const base = init ? { ...init } : {}; - return fetch(input, { ...base, dispatcher: agent }); + const nextInit: FetchInit = { ...base, dispatcher: agent }; + return fetch(input, nextInit); }); } From 03842071e314a06c4ac9bab03a8f4253c44f3bd5 Mon Sep 17 00:00:00 2001 From: Yash Date: Wed, 28 Jan 2026 07:30:40 +0100 Subject: [PATCH 2/2] refactor(telegram): update logger type to use tslog for improved type safety --- src/telegram/bot-handlers.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/telegram/bot-handlers.ts b/src/telegram/bot-handlers.ts index e1787a49a..8e042327d 100644 --- a/src/telegram/bot-handlers.ts +++ b/src/telegram/bot-handlers.ts @@ -31,6 +31,7 @@ import type { } from "../config/types.telegram.js"; import type { RuntimeEnv } from "../runtime.js"; import type { ChannelGroupPolicy } from "../config/group-policy.js"; +import { Logger } from "tslog"; type TelegramHandlersParams = { cfg: MoltbotConfig; @@ -57,7 +58,7 @@ type TelegramHandlersParams = { storeAllowFrom: string[], options?: { forceWasMentioned?: boolean; messageIdOverride?: string }, ) => Promise; - logger: { info: (obj: Record, msg: string) => void }; + logger: Logger>; }; export const registerTelegramHandlers = ({