Files
anything-llm/server/endpoints/telegram.js

336 lines
10 KiB
JavaScript

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 };