import { Bot } from 'grammy' import { registerHandlers } from './handlers' import { closeConnectionsForPlatform } from '@/remote-control/shared/nova-bridge' import { BotLogger } from '@/remote-control/shared/logger' import { ConfigManager, type ConfigChangeEvent } from '@/remote-control/config/manager' const CONNECTION_TIMEOUT = 30_000 // 30s to establish first connection let bot: Bot | null = null let currentToken: string = '' let isConnected: boolean = false let connectTimer: ReturnType | null = null let startTime: number = 0 let messagesProcessed: number = 0 let lastError: Error | null = null export interface BotStatus { platform: string status: 'connected' | 'disconnected' | 'connecting' uptime?: number messagesProcessed: number error?: string } export async function startBot(token: string): Promise { if (!token) { const msg = 'Telegram Bot Token 为空,跳过启动' console.warn(`[Telegram Bot] ${msg}`) BotLogger.log({ platform: 'telegram', eventType: 'error', severity: 'warning', message: msg }) return } // Idempotent check if (bot && currentToken === token) { console.log('[Telegram Bot] 已有活跃连接,跳过重复启动') return } // If token changed, stop old bot first if (bot) { console.log('[Telegram Bot] Token 已变更,先停止旧连接') await stopBot() } currentToken = token try { bot = new Bot(token) bot.catch((err) => { lastError = err.error instanceof Error ? err.error : new Error(String(err.error)) console.error('[Telegram Bot] 运行时错误:', lastError.message) BotLogger.log({ platform: 'telegram', eventType: 'error', severity: 'error', message: `Telegram Bot 运行时错误: ${lastError.message}`, }) }) registerHandlers(bot, token, () => { messagesProcessed++ }) // Connection timeout — if onStart is not called within CONNECTION_TIMEOUT, // stop the bot and mark as disconnected connectTimer = setTimeout(() => { if (!isConnected && bot) { const msg = '连接超时,请检查 Token 是否正确以及网络是否可达' lastError = new Error(msg) console.error(`[Telegram Bot] ${msg}`) BotLogger.log({ platform: 'telegram', eventType: 'error', severity: 'error', message: msg }) void stopBot() } }, CONNECTION_TIMEOUT) // Start long polling (non-blocking). // bot.start() returns a promise that resolves when bot.stop() is called, // so we must NOT await it. Connection errors surface via bot.catch(). bot.start({ onStart: (botInfo) => { if (connectTimer) { clearTimeout(connectTimer); connectTimer = null } isConnected = true startTime = Date.now() lastError = null console.log(`[Telegram Bot] 已启动: @${botInfo.username}`) BotLogger.log({ platform: 'telegram', eventType: 'connection', severity: 'info', message: `Telegram Bot 已连接,用户名: @${botInfo.username}`, }) }, }).catch((err) => { // Long polling loop ended with error (e.g. invalid token, network issue) if (connectTimer) { clearTimeout(connectTimer); connectTimer = null } const error = err instanceof Error ? err : new Error(String(err)) if (error.message === 'Aborted delay') return // Normal stop, ignore lastError = error isConnected = false console.error('[Telegram Bot] 长轮询异常退出:', error.message) BotLogger.log({ platform: 'telegram', eventType: 'error', severity: 'error', message: `Telegram Bot 长轮询异常: ${error.message}`, }) }) } catch (err) { const error = err instanceof Error ? err : new Error(String(err)) lastError = error bot = null currentToken = '' isConnected = false console.error('[Telegram Bot] 启动失败:', error.message) BotLogger.log({ platform: 'telegram', eventType: 'error', severity: 'error', message: `Telegram Bot 启动失败: ${error.message}`, details: { hint: '请检查 Bot Token 是否正确,以及是否已在 @BotFather 中创建了 Bot' }, }) throw error } } export async function stopBot(): Promise { if (connectTimer) { clearTimeout(connectTimer); connectTimer = null } closeConnectionsForPlatform('telegram') if (bot) { try { await bot.stop() } catch { // "Aborted delay" is expected when stopping long polling — ignore } bot = null currentToken = '' isConnected = false startTime = 0 console.log('[Telegram Bot] 已停止') BotLogger.log({ platform: 'telegram', eventType: 'disconnection', severity: 'info', message: 'Telegram Bot 已停止', }) } } export function getStatus(): BotStatus { return { platform: 'telegram', status: isConnected ? 'connected' : bot ? 'connecting' : 'disconnected', uptime: isConnected && startTime ? Math.floor((Date.now() - startTime) / 1000) : undefined, messagesProcessed, error: lastError?.message, } } // Listen for config changes const configManager = ConfigManager.getInstance() configManager.on('config-changed', async (event: ConfigChangeEvent) => { const { newConfig } = event const platformConfig = newConfig.telegram try { if (platformConfig.enabled && !bot) { await startBot(platformConfig.botToken) } else if (!platformConfig.enabled && bot) { await stopBot() } else if (platformConfig.enabled && bot) { if (platformConfig.botToken !== currentToken) { await stopBot() await startBot(platformConfig.botToken) } } lastError = null } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)) BotLogger.log({ platform: 'telegram', eventType: 'error', severity: 'error', message: `配置变更处理失败: ${lastError.message}`, }) } })