import { DEFAULT_EMOJIS, type StatusReactionEmojis, } from "../../../src/channels/status-reactions.js"; type StatusReactionEmojiKey = keyof Required; const TELEGRAM_GENERIC_REACTION_FALLBACKS = ["๐Ÿ‘", "๐Ÿ‘€", "๐Ÿ”ฅ"] as const; const TELEGRAM_SUPPORTED_REACTION_EMOJIS = new Set([ "โค", "๐Ÿ‘", "๐Ÿ‘Ž", "๐Ÿ”ฅ", "๐Ÿฅฐ", "๐Ÿ‘", "๐Ÿ˜", "๐Ÿค”", "๐Ÿคฏ", "๐Ÿ˜ฑ", "๐Ÿคฌ", "๐Ÿ˜ข", "๐ŸŽ‰", "๐Ÿคฉ", "๐Ÿคฎ", "๐Ÿ’ฉ", "๐Ÿ™", "๐Ÿ‘Œ", "๐Ÿ•Š", "๐Ÿคก", "๐Ÿฅฑ", "๐Ÿฅด", "๐Ÿ˜", "๐Ÿณ", "โคโ€๐Ÿ”ฅ", "๐ŸŒš", "๐ŸŒญ", "๐Ÿ’ฏ", "๐Ÿคฃ", "โšก", "๐ŸŒ", "๐Ÿ†", "๐Ÿ’”", "๐Ÿคจ", "๐Ÿ˜", "๐Ÿ“", "๐Ÿพ", "๐Ÿ’‹", "๐Ÿ–•", "๐Ÿ˜ˆ", "๐Ÿ˜ด", "๐Ÿ˜ญ", "๐Ÿค“", "๐Ÿ‘ป", "๐Ÿ‘จโ€๐Ÿ’ป", "๐Ÿ‘€", "๐ŸŽƒ", "๐Ÿ™ˆ", "๐Ÿ˜‡", "๐Ÿ˜จ", "๐Ÿค", "โœ", "๐Ÿค—", "๐Ÿซก", "๐ŸŽ…", "๐ŸŽ„", "โ˜ƒ", "๐Ÿ’…", "๐Ÿคช", "๐Ÿ—ฟ", "๐Ÿ†’", "๐Ÿ’˜", "๐Ÿ™‰", "๐Ÿฆ„", "๐Ÿ˜˜", "๐Ÿ’Š", "๐Ÿ™Š", "๐Ÿ˜Ž", "๐Ÿ‘พ", "๐Ÿคทโ€โ™‚", "๐Ÿคท", "๐Ÿคทโ€โ™€", "๐Ÿ˜ก", ]); export const TELEGRAM_STATUS_REACTION_VARIANTS: Record = { queued: ["๐Ÿ‘€", "๐Ÿ‘", "๐Ÿ”ฅ"], thinking: ["๐Ÿค”", "๐Ÿค“", "๐Ÿ‘€"], tool: ["๐Ÿ”ฅ", "โšก", "๐Ÿ‘"], coding: ["๐Ÿ‘จโ€๐Ÿ’ป", "๐Ÿ”ฅ", "โšก"], web: ["โšก", "๐Ÿ”ฅ", "๐Ÿ‘"], done: ["๐Ÿ‘", "๐ŸŽ‰", "๐Ÿ’ฏ"], error: ["๐Ÿ˜ฑ", "๐Ÿ˜จ", "๐Ÿคฏ"], stallSoft: ["๐Ÿฅฑ", "๐Ÿ˜ด", "๐Ÿค”"], stallHard: ["๐Ÿ˜จ", "๐Ÿ˜ฑ", "โšก"], compacting: ["โœ", "๐Ÿค”", "๐Ÿคฏ"], }; const STATUS_REACTION_EMOJI_KEYS: StatusReactionEmojiKey[] = [ "queued", "thinking", "tool", "coding", "web", "done", "error", "stallSoft", "stallHard", "compacting", ]; function normalizeEmoji(value: string | undefined): string | undefined { const trimmed = value?.trim(); return trimmed ? trimmed : undefined; } function toUniqueNonEmpty(values: string[]): string[] { return Array.from(new Set(values.map((value) => value.trim()).filter(Boolean))); } export function resolveTelegramStatusReactionEmojis(params: { initialEmoji: string; overrides?: StatusReactionEmojis; }): Required { const { overrides } = params; const queuedFallback = normalizeEmoji(params.initialEmoji) ?? DEFAULT_EMOJIS.queued; return { queued: normalizeEmoji(overrides?.queued) ?? queuedFallback, thinking: normalizeEmoji(overrides?.thinking) ?? DEFAULT_EMOJIS.thinking, tool: normalizeEmoji(overrides?.tool) ?? DEFAULT_EMOJIS.tool, coding: normalizeEmoji(overrides?.coding) ?? DEFAULT_EMOJIS.coding, web: normalizeEmoji(overrides?.web) ?? DEFAULT_EMOJIS.web, done: normalizeEmoji(overrides?.done) ?? DEFAULT_EMOJIS.done, error: normalizeEmoji(overrides?.error) ?? DEFAULT_EMOJIS.error, stallSoft: normalizeEmoji(overrides?.stallSoft) ?? DEFAULT_EMOJIS.stallSoft, stallHard: normalizeEmoji(overrides?.stallHard) ?? DEFAULT_EMOJIS.stallHard, compacting: normalizeEmoji(overrides?.compacting) ?? DEFAULT_EMOJIS.compacting, }; } export function buildTelegramStatusReactionVariants( emojis: Required, ): Map { const variantsByRequested = new Map(); for (const key of STATUS_REACTION_EMOJI_KEYS) { const requested = normalizeEmoji(emojis[key]); if (!requested) { continue; } const fallbackVariants = TELEGRAM_STATUS_REACTION_VARIANTS[key] ?? []; const candidates = toUniqueNonEmpty([requested, ...fallbackVariants]); variantsByRequested.set(requested, candidates); } return variantsByRequested; } export function isTelegramSupportedReactionEmoji(emoji: string): boolean { return TELEGRAM_SUPPORTED_REACTION_EMOJIS.has(emoji); } export function extractTelegramAllowedEmojiReactions( chat: unknown, ): Set | null | undefined { if (!chat || typeof chat !== "object") { return undefined; } if (!Object.prototype.hasOwnProperty.call(chat, "available_reactions")) { return undefined; } const availableReactions = (chat as { available_reactions?: unknown }).available_reactions; if (availableReactions == null) { // Explicitly omitted/null => all emoji reactions are allowed in this chat. return null; } if (!Array.isArray(availableReactions)) { return new Set(); } const allowed = new Set(); for (const reaction of availableReactions) { if (!reaction || typeof reaction !== "object") { continue; } const typedReaction = reaction as { type?: unknown; emoji?: unknown }; if (typedReaction.type !== "emoji" || typeof typedReaction.emoji !== "string") { continue; } const emoji = typedReaction.emoji.trim(); if (emoji) { allowed.add(emoji); } } return allowed; } export async function resolveTelegramAllowedEmojiReactions(params: { chat: unknown; chatId: string | number; getChat?: (chatId: string | number) => Promise; }): Promise | null> { const fromMessage = extractTelegramAllowedEmojiReactions(params.chat); if (fromMessage !== undefined) { return fromMessage; } if (params.getChat) { try { const chatInfo = await params.getChat(params.chatId); const fromLookup = extractTelegramAllowedEmojiReactions(chatInfo); if (fromLookup !== undefined) { return fromLookup; } } catch { return null; } } // If unavailable, assume no explicit restriction. return null; } export function resolveTelegramReactionVariant(params: { requestedEmoji: string; variantsByRequestedEmoji: Map; allowedEmojiReactions?: Set | null; }): string | undefined { const requestedEmoji = normalizeEmoji(params.requestedEmoji); if (!requestedEmoji) { return undefined; } const configuredVariants = params.variantsByRequestedEmoji.get(requestedEmoji) ?? [ requestedEmoji, ]; const variants = toUniqueNonEmpty([ ...configuredVariants, ...TELEGRAM_GENERIC_REACTION_FALLBACKS, ]); for (const candidate of variants) { const isAllowedByChat = params.allowedEmojiReactions == null || params.allowedEmojiReactions.has(candidate); if (isAllowedByChat && isTelegramSupportedReactionEmoji(candidate)) { return candidate; } } return undefined; }