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"; } /**