/** * Telegram API and config persistence helpers * Wraps bot API calls, file downloads, and local config reads and writes for the bridge runtime */ import { mkdir, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; export interface TelegramConfig { botToken?: string; botUsername?: string; botId?: number; allowedUserId?: number; lastUpdateId?: number; } interface TelegramApiResponse { ok: boolean; result?: T; description?: string; error_code?: number; } interface TelegramGetFileResult { file_path: string; } export interface TelegramApiClient { call: ( method: string, body: Record, options?: { signal?: AbortSignal }, ) => Promise; callMultipart: ( method: string, fields: Record, fileField: string, filePath: string, fileName: string, options?: { signal?: AbortSignal }, ) => Promise; downloadFile: ( fileId: string, suggestedName: string, tempDir: string, ) => Promise; answerCallbackQuery: ( callbackQueryId: string, text?: string, ) => Promise; } function sanitizeFileName(name: string): string { return name.replace(/[^a-zA-Z0-9._-]+/g, "_"); } export async function readTelegramConfig( configPath: string, ): Promise { try { const content = await readFile(configPath, "utf8"); return JSON.parse(content) as TelegramConfig; } catch { return {}; } } export async function writeTelegramConfig( agentDir: string, configPath: string, config: TelegramConfig, ): Promise { await mkdir(agentDir, { recursive: true }); await writeFile( configPath, JSON.stringify(config, null, "\t") + "\n", "utf8", ); } export async function callTelegram( botToken: string | undefined, method: string, body: Record, options?: { signal?: AbortSignal }, ): Promise { if (!botToken) { throw new Error("Telegram bot token is not configured"); } const response = await fetch( `https://api.telegram.org/bot${botToken}/${method}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), signal: options?.signal, }, ); const data = (await response.json()) as TelegramApiResponse; if (!data.ok || data.result === undefined) { throw new Error(data.description || `Telegram API ${method} failed`); } return data.result; } export async function callTelegramMultipart( botToken: string | undefined, method: string, fields: Record, fileField: string, filePath: string, fileName: string, options?: { signal?: AbortSignal }, ): Promise { if (!botToken) { throw new Error("Telegram bot token is not configured"); } const form = new FormData(); for (const [key, value] of Object.entries(fields)) { form.set(key, value); } const buffer = await readFile(filePath); form.set(fileField, new Blob([buffer]), fileName); const response = await fetch( `https://api.telegram.org/bot${botToken}/${method}`, { method: "POST", body: form, signal: options?.signal, }, ); const data = (await response.json()) as TelegramApiResponse; if (!data.ok || data.result === undefined) { throw new Error(data.description || `Telegram API ${method} failed`); } return data.result; } export async function downloadTelegramFile( botToken: string | undefined, fileId: string, suggestedName: string, tempDir: string, ): Promise { if (!botToken) { throw new Error("Telegram bot token is not configured"); } const file = await callTelegram(botToken, "getFile", { file_id: fileId, }); await mkdir(tempDir, { recursive: true }); const targetPath = join( tempDir, `${Date.now()}-${sanitizeFileName(suggestedName)}`, ); const response = await fetch( `https://api.telegram.org/file/bot${botToken}/${file.file_path}`, ); if (!response.ok) { throw new Error(`Failed to download Telegram file: ${response.status}`); } const arrayBuffer = await response.arrayBuffer(); await writeFile(targetPath, Buffer.from(arrayBuffer)); return targetPath; } export async function answerTelegramCallbackQuery( botToken: string | undefined, callbackQueryId: string, text?: string, ): Promise { try { await callTelegram( botToken, "answerCallbackQuery", text ? { callback_query_id: callbackQueryId, text } : { callback_query_id: callbackQueryId }, ); } catch { // ignore } } export function createTelegramApiClient( getBotToken: () => string | undefined, ): TelegramApiClient { return { call: async (method, body, options) => { return callTelegram(getBotToken(), method, body, options); }, callMultipart: async ( method, fields, fileField, filePath, fileName, options, ) => { return callTelegramMultipart( getBotToken(), method, fields, fileField, filePath, fileName, options, ); }, downloadFile: async (fileId, suggestedName, tempDir) => { return downloadTelegramFile( getBotToken(), fileId, suggestedName, tempDir, ); }, answerCallbackQuery: async (callbackQueryId, text) => { await answerTelegramCallbackQuery(getBotToken(), callbackQueryId, text); }, }; }