import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; import type { Authenticator } from '../auth/authenticator.js'; import { AgentHarness } from '../harness/agent-harness.js'; import type { InboundMessage } from '../types/messages.js'; import { randomUUID } from 'crypto'; import type { ProviderConfig } from '../llm/provider.js'; export interface TelegramHandlerConfig { authenticator: Authenticator; providerConfig: ProviderConfig; telegramBotToken: string; } interface TelegramUpdate { update_id: number; message?: { message_id: number; from: { id: number; first_name: string; username?: string; }; chat: { id: number; type: string; }; text?: string; photo?: Array<{ file_id: string; file_size: number; }>; }; } /** * Telegram webhook handler */ export class TelegramHandler { private config: TelegramHandlerConfig; private sessions = new Map(); constructor(config: TelegramHandlerConfig) { this.config = config; } /** * Register Telegram webhook routes */ register(app: FastifyInstance): void { app.post('/webhook/telegram', async (request: FastifyRequest, reply: FastifyReply) => { await this.handleWebhook(request, reply, app); }); } /** * Handle Telegram webhook */ private async handleWebhook( request: FastifyRequest, reply: FastifyReply, app: FastifyInstance ): Promise { const logger = app.log; try { const update = request.body as TelegramUpdate; if (!update.message?.text) { // Ignore non-text messages for now reply.code(200).send({ ok: true }); return; } const telegramUserId = update.message.from.id.toString(); const chatId = update.message.chat.id; const text = update.message.text; logger.info({ telegramUserId, chatId, text }, 'Received Telegram message'); // Authenticate const authContext = await this.config.authenticator.authenticateTelegram(telegramUserId); if (!authContext) { logger.warn({ telegramUserId }, 'Telegram user not authenticated'); await this.sendTelegramMessage( chatId, 'Please link your Telegram account to Dexorder first.' ); reply.code(200).send({ ok: true }); return; } // Get or create harness let harness = this.sessions.get(authContext.sessionId); if (!harness) { harness = new AgentHarness({ userId: authContext.userId, sessionId: authContext.sessionId, license: authContext.license, providerConfig: this.config.providerConfig, logger, }); await harness.initialize(); this.sessions.set(authContext.sessionId, harness); } // Process message const inboundMessage: InboundMessage = { messageId: randomUUID(), userId: authContext.userId, sessionId: authContext.sessionId, content: text, attachments: [], // TODO: Add image support for Telegram timestamp: new Date(), }; const response = await harness.handleMessage(inboundMessage); // Send response back to Telegram await this.sendTelegramMessage(chatId, response.content); reply.code(200).send({ ok: true }); } catch (error) { logger.error({ error }, 'Error handling Telegram webhook'); reply.code(500).send({ ok: false, error: 'Internal server error' }); } } /** * Send message to Telegram chat */ private async sendTelegramMessage(chatId: number, text: string): Promise { const url = `https://api.telegram.org/bot${this.config.telegramBotToken}/sendMessage`; try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ chat_id: chatId, text, parse_mode: 'Markdown', }), }); if (!response.ok) { throw new Error(`Telegram API error: ${response.statusText}`); } } catch (error) { console.error('Failed to send Telegram message:', error); throw error; } } /** * Cleanup old sessions (call periodically) */ async cleanupSessions(_maxAgeMs = 30 * 60 * 1000): Promise { // TODO: Track session last activity and cleanup // For now, sessions persist until server restart } }