From 31246f0a349344ff38c451774b8b86e30699ce68 Mon Sep 17 00:00:00 2001 From: Arne Moor Date: Sat, 6 Dec 2025 10:19:19 +0100 Subject: [PATCH] fix: telegram allowFrom matching and entity resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes three critical bugs in Telegram integration: 1. Case-insensitive username matching - Telegram usernames are case-insensitive, but the API returns them with original casing - Now lowercase all usernames when extracting sender identifier to match config entries - Fixes: messages from @ArneMoor now match config entry telegram:@arnemoor 2. Entity resolution for sending replies - resolveEntity() now strips telegram: prefix before resolving users/chats - Fixes: "Could not resolve Telegram entity: telegram:@username" errors when sending replies 3. Improved skip logging - Messages skipped due to allowFrom filtering now always logged (not just in verbose mode) - Uses clear emoji indicator: ⏭️ Skipped message from... --- src/telegram/inbound.ts | 9 +++++++-- src/telegram/monitor.ts | 9 ++++----- src/telegram/utils.ts | 8 ++++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/telegram/inbound.ts b/src/telegram/inbound.ts index 97c18df5b..c5c377614 100644 --- a/src/telegram/inbound.ts +++ b/src/telegram/inbound.ts @@ -60,13 +60,14 @@ export async function convertTelegramMessage( * Extract sender identifier with telegram: prefix to prevent session ID collisions. * * Returns: - * - telegram:@username (if username available) + * - telegram:@username (if username available, lowercase for case-insensitive matching) * - telegram:+phone (if phone available, normalized to E.164) * - telegram:id (numeric Telegram ID as fallback) */ function extractSenderIdentifier(sender: Api.User | Api.Chat): string { if ("username" in sender && sender.username) { - return `telegram:@${sender.username}`; + // Lowercase username for case-insensitive matching (Telegram usernames are case-insensitive) + return `telegram:@${sender.username.toLowerCase()}`; } if ("phone" in sender && sender.phone) { // Ensure phone has + prefix for E.164 format to match normalized allowFrom entries @@ -209,6 +210,10 @@ export function isAllowedSender( return true; // No whitelist = allow all } + if (allowFrom.includes("*")) { + return true; // Wildcard allows all senders + } + const normalizedFrom = normalizeAllowFromEntry(message.from, "telegram"); const normalizedAllowList = allowFrom.map((e) => normalizeAllowFromEntry(e, "telegram"), diff --git a/src/telegram/monitor.ts b/src/telegram/monitor.ts index 8cccd2a30..1ca499a7c 100644 --- a/src/telegram/monitor.ts +++ b/src/telegram/monitor.ts @@ -170,11 +170,10 @@ async function handleInboundMessage( normalizeAllowFromEntry(e, "telegram"), ); if (!normalizedAllowList.includes(normalizedFrom)) { - if (isVerbose()) { - logVerbose( - `Skipping auto-reply: sender ${message.from} not in allowFrom list`, - ); - } + // Always log skipped messages (not just in verbose mode) + runtime.log( + `⏭️ Skipped message from ${message.from} (not in allowFrom list)`, + ); return; } } diff --git a/src/telegram/utils.ts b/src/telegram/utils.ts index 8428a4ea8..7c3745dee 100644 --- a/src/telegram/utils.ts +++ b/src/telegram/utils.ts @@ -6,13 +6,17 @@ type Entity = Api.User | Api.Chat | Api.Channel; /** * Resolve Telegram entity (user/chat) from identifier. * Supports @username, phone number, or user ID. + * Also handles telegram: prefix (e.g., telegram:@username). */ export async function resolveEntity( client: TelegramClient, identifier: string, ): Promise { - // Clean identifier - const clean = identifier.trim(); + // Clean identifier and strip telegram: prefix if present + let clean = identifier.trim(); + if (clean.startsWith("telegram:")) { + clean = clean.slice("telegram:".length); + } // Try as-is first (handles @username, phone, user ID) try {