From 6da26f3f21faa2fdbdabd4ce2c0723282380058d Mon Sep 17 00:00:00 2001 From: Arne Moor Date: Sat, 6 Dec 2025 02:34:44 +0100 Subject: [PATCH] fix: critical Telegram integration issues - Add Telegram handling to relay command when selected via pickProvider - Add Telegram delivery support to agent --deliver command - Add telegram: prefix to sender identifiers to prevent session ID collisions - Update detectProvider to properly handle telegram: prefixed identifiers - Document provider detection logic and session ID collision prevention Fixes session ID collisions where Telegram users without usernames (numeric IDs or phone numbers) were being misidentified as WhatsApp users, causing cross- provider session leakage. --- src/cli/program.ts | 5 +++++ src/commands/agent.ts | 39 ++++++++++++++++++++++++++++++++++++++- src/config/sessions.ts | 15 +++++++++++++-- src/telegram/inbound.ts | 15 ++++++++++----- 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/cli/program.ts b/src/cli/program.ts index 05b185942..be1f45f81 100644 --- a/src/cli/program.ts +++ b/src/cli/program.ts @@ -587,6 +587,11 @@ Examples: } } + if (provider === "telegram") { + await monitorTelegramProvider(Boolean(opts.verbose), defaultRuntime); + return; + } + ensureTwilioEnv(); logTwilioFrom(); await monitorTwilio(intervalSeconds, lookbackMinutes); diff --git a/src/commands/agent.ts b/src/commands/agent.ts index 6b108560b..28401cad3 100644 --- a/src/commands/agent.ts +++ b/src/commands/agent.ts @@ -22,9 +22,12 @@ import { type SessionEntry, saveSessionStore, } from "../config/sessions.js"; +import { readEnv } from "../env.js"; import { ensureTwilioEnv } from "../env.js"; import { runCommandWithTimeout } from "../process/exec.js"; import { pickProvider } from "../provider-web.js"; +import { createInitializedProvider } from "../providers/factory.js"; +import type { TelegramProviderConfig } from "../providers/base/types.js"; import { defaultRuntime, type RuntimeEnv } from "../runtime.js"; import type { Provider } from "../utils.js"; import { sendViaIpc } from "../web/ipc.js"; @@ -349,7 +352,9 @@ export async function agentCommand( provider = provider === "twilio" ? "twilio" - : await pickProvider((provider ?? "auto") as Provider | "auto"); + : provider === "telegram" + ? "telegram" + : await pickProvider((provider ?? "auto") as Provider | "auto"); if (provider === "twilio") ensureTwilioEnv(); } @@ -392,6 +397,38 @@ export async function agentCommand( }); } } + } else if (provider === "telegram") { + const env = readEnv(runtime); + if (!env.telegram?.apiId || !env.telegram?.apiHash) { + throw new Error( + "Telegram not configured. Set TELEGRAM_API_ID and TELEGRAM_API_HASH in .env", + ); + } + const telegramConfig: TelegramProviderConfig = { + kind: "telegram", + apiId: Number.parseInt(env.telegram.apiId, 10), + apiHash: env.telegram.apiHash, + sessionDir: undefined, + verbose: false, + }; + const telegramProvider = + await createInitializedProvider("telegram", telegramConfig); + const chunks = chunkText(text, 4096); + if (chunks.length > 0 || media.length > 0) { + const firstChunk = chunks.length > 0 ? chunks[0] : ""; + const firstMedia = media[0]; + await telegramProvider.send(opts.to, firstChunk, { + media: firstMedia ? [{ type: "image", url: firstMedia }] : undefined, + }); + } + for (let i = 1; i < chunks.length; i++) { + await telegramProvider.send(opts.to, chunks[i]); + } + for (let i = 1; i < media.length; i++) { + await telegramProvider.send(opts.to, "", { + media: [{ type: "image", url: media[i] }], + }); + } } else { const chunks = chunkText(text, 1600); const resolvedMedia = await Promise.all( diff --git a/src/config/sessions.ts b/src/config/sessions.ts index aa36f4f4d..f6146de13 100644 --- a/src/config/sessions.ts +++ b/src/config/sessions.ts @@ -58,12 +58,23 @@ export async function saveSessionStore( /** * Detect provider from message context. + * + * IMPORTANT: This function is only called when the provider isn't explicitly known. + * For messages coming through the Provider interface, the provider should already + * be indicated via the message context (e.g., From field with telegram: prefix). + * + * Telegram messages should ALWAYS use "telegram:" prefix in the From field to avoid + * being misidentified as WhatsApp. */ function detectProvider(from: string): "whatsapp" | "telegram" | "twilio" { - // Telegram format: "telegram:123456789" or "@username" - if (from.startsWith("telegram:") || from.startsWith("@")) { + // Telegram format: "telegram:123456789" or "telegram:@username" + if (from.startsWith("telegram:")) { return "telegram"; } + // WhatsApp username format: "@username" (but NOT telegram:@username) + if (from.startsWith("@")) { + return "whatsapp"; // WhatsApp also supports @username format + } // WhatsApp/Twilio use E.164 phone numbers // Default to whatsapp for phone numbers return "whatsapp"; diff --git a/src/telegram/inbound.ts b/src/telegram/inbound.ts index a9d440ff1..ecceebe52 100644 --- a/src/telegram/inbound.ts +++ b/src/telegram/inbound.ts @@ -57,19 +57,24 @@ export async function convertTelegramMessage( } /** - * Extract sender identifier (@username or phone). + * Extract sender identifier with telegram: prefix to prevent session ID collisions. + * + * Returns: + * - telegram:@username (if username available) + * - telegram:+phone (if phone available) + * - telegram:id (numeric Telegram ID as fallback) */ function extractSenderIdentifier(sender: Api.User | Api.Chat): string { if ("username" in sender && sender.username) { - return `@${sender.username}`; + return `telegram:@${sender.username}`; } if ("phone" in sender && sender.phone) { - return sender.phone; + return `telegram:${sender.phone}`; } if ("id" in sender && sender.id) { - return sender.id.toString(); + return `telegram:${sender.id.toString()}`; } - return "unknown"; + return "telegram:unknown"; } /**