const { ExternalCommunicationConnector, } = require("../models/externalCommunicationConnector"); const { Telemetry } = require("../models/telemetry"); const { TelegramBotService } = require("../utils/telegramBot"); const { validatedRequest } = require("../utils/middleware/validatedRequest"); const { isSingleUserMode } = require("../utils/middleware/multiUserProtected"); const { reqBody } = require("../utils/http"); const { EventLogs } = require("../models/eventLogs"); const { Workspace } = require("../models/workspace"); const { WorkspaceThread } = require("../models/workspaceThread"); const { encryptToken } = require("../utils/telegramBot/utils"); function telegramEndpoints(app) { if (!app) return; app.get( "/telegram/config", [validatedRequest, isSingleUserMode], async (_request, response) => { try { const connector = await ExternalCommunicationConnector.get("telegram"); if (!connector) { return response.status(200).json({ config: null }); } const service = new TelegramBotService(); // Resolve workspace, thread, and model from the first approved user's // active state, falling back to the default workspace config. const approvedUsers = connector.config.approved_users || []; const activeUser = approvedUsers[0]; const workspaceSlug = activeUser?.active_workspace || connector.config.default_workspace || null; const threadSlug = activeUser?.active_thread || null; let thread = null; let workspace = await Workspace.get({ slug: workspaceSlug }); if (!workspace) { const available = await Workspace.where({}, 1); if (available.length) workspace = available[0]; } if (!threadSlug) { const availableThreads = await WorkspaceThread.where({ workspace_id: workspace.id, }); if (availableThreads.length) thread = availableThreads[0]; } return response.status(200).json({ config: { active: connector.active, connected: service.isRunning, bot_username: connector.config.bot_username || null, default_workspace: workspace?.name || workspaceSlug || "—", active_thread_name: thread?.name || "Default", chat_model: workspace?.chatModel || "System default", voice_response_mode: connector.config.voice_response_mode || "text_only", }, }); } catch (e) { console.error(e.message, e); response.sendStatus(500); } } ); /** * Verify token, save config, and start the Telegram bot. */ app.post( "/telegram/connect", [validatedRequest, isSingleUserMode], async (request, response) => { try { const { bot_token, default_workspace = null } = reqBody(request); if (!bot_token) { return response.status(400).json({ success: false, error: "Bot token is required.", }); } // Verify the token with Telegram API const verification = await TelegramBotService.verifyToken( String(bot_token) ); if (!verification.valid) { return response.status(400).json({ success: false, error: `Invalid bot token: ${verification.error}`, }); } let workspaceSlug = null; if (default_workspace) workspaceSlug = String(default_workspace); else { const workspaces = await Workspace.where({}, 1); if (workspaces.length) workspaceSlug = workspaces[0].slug; else { const { workspace } = await Workspace.new( `${verification.username} Workspace`, null, { chatMode: "automatic" } ); if (workspace) workspaceSlug = workspace.slug; } } if (!workspaceSlug) { return response.status(400).json({ success: false, error: "No workspace found or could be created.", }); } // Preserve approved users when reconnecting with a new token const existing = await ExternalCommunicationConnector.get("telegram"); const storedConfig = { bot_username: verification.username, default_workspace: workspaceSlug, approved_users: existing?.config?.approved_users || [], voice_response_mode: existing?.config?.voice_response_mode || "text_only", }; // Save config with encrypted token const { error } = await ExternalCommunicationConnector.upsert( "telegram", { ...storedConfig, bot_token: encryptToken(String(bot_token)), active: true, } ); if (error) return response.status(500).json({ success: false, error }); // Start the bot with the plaintext token const service = new TelegramBotService(); await service.start({ ...storedConfig, bot_token: String(bot_token) }); await EventLogs.logEvent("telegram_bot_connected", { bot_username: verification.username, }); await Telemetry.sendTelemetry("telegram_bot_connected"); return response.status(200).json({ success: true, bot_username: verification.username, }); } catch (e) { console.error(e.message, e); response.sendStatus(500); } } ); app.post( "/telegram/disconnect", [validatedRequest, isSingleUserMode], async (_request, response) => { try { const service = new TelegramBotService(); await service.stop(); await ExternalCommunicationConnector.delete("telegram"); await EventLogs.logEvent("telegram_bot_disconnected"); return response.status(200).json({ success: true }); } catch (e) { console.error(e.message, e); response.sendStatus(500); } } ); app.get( "/telegram/status", [validatedRequest, isSingleUserMode], async (_request, response) => { try { const connector = await ExternalCommunicationConnector.get("telegram"); const service = new TelegramBotService(); return response.status(200).json({ active: connector?.active && service.isRunning, bot_username: connector?.config?.bot_username || null, }); } catch (e) { console.error(e.message, e); response.sendStatus(500); } } ); app.get( "/telegram/pending-users", [validatedRequest, isSingleUserMode], async (_request, response) => { try { const service = new TelegramBotService(); return response .status(200) .json({ users: service.pendingPairings || [] }); } catch (e) { console.error(e.message, e); response.sendStatus(500); } } ); app.get( "/telegram/approved-users", [validatedRequest, isSingleUserMode], async (_request, response) => { try { const connector = await ExternalCommunicationConnector.get("telegram"); const approved = connector?.config?.approved_users || []; return response.status(200).json({ users: approved }); } catch (e) { console.error(e.message, e); response.sendStatus(500); } } ); app.post( "/telegram/approve-user", [validatedRequest, isSingleUserMode], async (request, response) => { try { const { chatId } = reqBody(request); if (!chatId) return response .status(400) .json({ success: false, error: "chatId is required." }); const service = new TelegramBotService(); await service.approvePendingUser(chatId); await EventLogs.logEvent("telegram_user_approved", { chatId }); return response.status(200).json({ success: true }); } catch (e) { console.error(e.message, e); response.sendStatus(500); } } ); app.post( "/telegram/deny-user", [validatedRequest, isSingleUserMode], async (request, response) => { try { const { chatId } = reqBody(request); if (!chatId) return response .status(400) .json({ success: false, error: "chatId is required." }); const service = new TelegramBotService(); await service.denyPendingUser(chatId); await EventLogs.logEvent("telegram_user_denied", { chatId }); return response.status(200).json({ success: true }); } catch (e) { console.error(e.message, e); response.sendStatus(500); } } ); app.post( "/telegram/revoke-user", [validatedRequest, isSingleUserMode], async (request, response) => { try { const { chatId } = reqBody(request); if (!chatId) return response .status(400) .json({ success: false, error: "chatId is required." }); const service = new TelegramBotService(); await service.revokeExistingUser(chatId); await EventLogs.logEvent("telegram_user_revoked", { chatId }); return response.status(200).json({ success: true }); } catch (e) { console.error(e.message, e); response.sendStatus(500); } } ); app.post( "/telegram/update-config", [validatedRequest, isSingleUserMode], async (request, response) => { try { const { voice_response_mode } = reqBody(request); const updates = {}; if ( voice_response_mode && ["text_only", "mirror", "always_voice"].includes(voice_response_mode) ) { updates.voice_response_mode = voice_response_mode; } if (Object.keys(updates).length === 0) { return response .status(400) .json({ success: false, error: "No valid updates provided." }); } const { error } = await ExternalCommunicationConnector.updateConfig( "telegram", updates ); if (error) { return response.status(500).json({ success: false, error }); } // Update the running bot's config so changes take effect immediately const service = new TelegramBotService(); if (service.isRunning) service.updateConfig(updates); return response.status(200).json({ success: true }); } catch (e) { console.error(e.message, e); response.sendStatus(500); } } ); } module.exports = { telegramEndpoints };