const BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN!; const DEFAULT_CHAT_ID = process.env.TELEGRAM_CHAT_ID!; const ALLOWED_IDS = new Set( (process.env.TELEGRAM_ALLOWED_CHAT_IDS || DEFAULT_CHAT_ID) .split(",") .map((id) => id.trim()) ); const api = async (method: string, body?: Record) => { const res = await fetch( `https://api.telegram.org/bot${BOT_TOKEN}/${method}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: body ? JSON.stringify(body) : undefined, } ); return res.json(); }; export function isAllowed(chatId: number | string): boolean { return ALLOWED_IDS.has(String(chatId)); } export async function send( text: string, chatId: string | number = DEFAULT_CHAT_ID, opts?: { reply_to?: number; keyboard?: InlineKeyboard } ): Promise { if (!isAllowed(chatId)) return null; // Telegram limits messages to 4096 chars const truncated = text.length > 4000 ? text.slice(0, 4000) + "\n\n... (truncated)" : text; const body: Record = { chat_id: chatId, text: truncated, parse_mode: "Markdown", }; if (opts?.reply_to) body.reply_to_message_id = opts.reply_to; if (opts?.keyboard) { body.reply_markup = { inline_keyboard: opts.keyboard }; } return api("sendMessage", body); } export async function editMessage( chatId: string | number, messageId: number, text: string, keyboard?: InlineKeyboard ): Promise { const truncated = text.length > 4000 ? text.slice(0, 4000) + "\n\n... (truncated)" : text; const body: Record = { chat_id: chatId, message_id: messageId, text: truncated, parse_mode: "Markdown", }; if (keyboard) body.reply_markup = { inline_keyboard: keyboard }; return api("editMessageText", body); } export async function answerCallback( callbackId: string, text?: string ): Promise { return api("answerCallbackQuery", { callback_query_id: callbackId, text, }); } export type InlineKeyboard = Array< Array<{ text: string; callback_data: string }> >; export function agentKeyboard(agentId: number): InlineKeyboard { return [ [ { text: "📋 Logs", callback_data: `logs_${agentId}` }, { text: "💬 Talk", callback_data: `talk_${agentId}` }, { text: "🛑 Kill", callback_data: `kill_${agentId}` }, ], ]; } let offset = 0; export interface TelegramUpdate { update_id: number; message?: { message_id: number; from?: { id: number; first_name: string }; chat: { id: number; type: string }; text?: string; reply_to_message?: { message_id: number }; }; callback_query?: { id: string; from: { id: number }; message?: { message_id: number; chat: { id: number } }; data?: string; }; } export async function poll(): Promise { try { const data = await api("getUpdates", { offset, timeout: 30, allowed_updates: ["message", "callback_query"], }); const updates: TelegramUpdate[] = data.result || []; if (updates.length > 0) { offset = updates[updates.length - 1].update_id + 1; } return updates; } catch (e) { console.error("[telegram] poll error:", e); return []; } } export async function deleteMessage( chatId: string | number, messageId: number ): Promise { try { const res = await api("deleteMessage", { chat_id: chatId, message_id: messageId, }); return !!res?.ok; } catch { return false; } } export default { send, editMessage, deleteMessage, poll, isAllowed, agentKeyboard, answerCallback };