Files
tg-chat-bot/src/index.ts
T
melod1n 13b41c3026 bump libs
migrate to typescript 6
remove ytdl feature
2026-05-01 07:05:17 +03:00

323 lines
9.7 KiB
TypeScript

import "dotenv/config";
import {Environment} from "./common/environment";
import {BotCommand, TelegramBot, User} from "typescript-telegram-bot-api";
import {Command} from "./base/command";
import {
delay,
initSystemSpecs,
logError,
processCallbackQuery,
processEditedMessage,
processInlineQuery,
processMyChatMember,
processNewMessage
} from "./util/utils";
import {Ae} from "./commands/ae";
import {Help} from "./commands/help";
import {Ignore} from "./commands/ignore";
import {Unignore} from "./commands/unignore";
import {Ping} from "./commands/ping";
import {RandomString} from "./commands/random-string";
import {SystemInfo} from "./commands/system-info";
import {Test} from "./commands/test";
import {readData, retrieveAnswers} from "./db/database";
import {Uptime} from "./commands/uptime";
import {WhatBetter} from "./commands/what-better";
import {When} from "./commands/when";
import {RandomInt} from "./commands/random-int";
import {Ban} from "./commands/ban";
import {Quote} from "./commands/quote";
import {Ollama} from "ollama";
import {OllamaSearch} from "./commands/ollama-search";
import {Id} from "./commands/id";
import {OllamaPrompt} from "./commands/ollama-prompt";
import {AdminsAdd} from "./commands/admins-add";
import {AdminsRemove} from "./commands/admins-remove";
import {Shutdown} from "./commands/shutdown";
import {Leave} from "./commands/leave";
import {OllamaChat} from "./commands/ollama-chat";
import {Start} from "./commands/start";
import {GeminiChat} from "./commands/gemini-chat";
import {Choice} from "./commands/choice";
import {Coin} from "./commands/coin";
import {Qr} from "./commands/qr";
import {Distort} from "./commands/distort";
import {Dice} from "./commands/dice";
import {Unban} from "./commands/unban";
import {Title} from "./commands/title";
import {MessageDao} from "./db/message-dao";
import {DatabaseManager} from "./db/database-manager";
import {UserDao} from "./db/user-dao";
import {UserStore} from "./common/user-store";
import {OllamaRequest} from "./model/ollama-request";
import {CallbackCommand} from "./base/callback-command";
import {OllamaCancel} from "./callback_commands/ollama-cancel";
import {MistralChat} from "./commands/mistral-chat";
import {Transliteration} from "./commands/transliteration";
import {OllamaListModels} from "./commands/ollama-list-models";
import {OllamaGetModel} from "./commands/ollama-get-model";
import {OllamaSetModel} from "./commands/ollama-set-model";
import {Mistral} from "@mistralai/mistralai";
import {GoogleGenAI} from "@google/genai";
import {MistralGetModel} from "./commands/mistral-get-model";
import {MistralSetModel} from "./commands/mistral-set-model";
import {MistralListModels} from "./commands/mistral-list-models";
import {GeminiListModels} from "./commands/gemini-list-models";
import {GeminiGetModel} from "./commands/gemini-get-model";
import {GeminiSetModel} from "./commands/gemini-set-model";
import {Debug} from "./commands/debug";
import {GeminiGenerateImage} from "./commands/gemini-generate-image";
import fs from "node:fs";
import path from "node:path";
import {setInterval} from "node:timers";
import {OpenAI} from "openai";
import {OpenAIChat} from "./commands/openai-chat";
import {OpenAIListModels} from "./commands/openai-list-models";
import {OpenAIGetModel} from "./commands/openai-get-model";
import {OpenAISetModel} from "./commands/openai-set-model";
import {Info} from "./commands/info";
import {OpenAIGenImage} from "./commands/openai-gen-image";
import {clearUpFolderFromOldFiles} from "./util/files";
import {AdminsList} from "./commands/admins-list";
import {ExportDb} from "./commands/export-db";
process.setUncaughtExceptionCaptureCallback(logError);
Environment.load();
DatabaseManager.init();
export const messageDao = new MessageDao();
export const userDao = new UserDao();
export const bot = new TelegramBot({botToken: Environment.BOT_TOKEN, testEnvironment: Environment.TEST_ENVIRONMENT});
export let botUser: User;
export const googleAi = new GoogleGenAI({apiKey: Environment.GEMINI_API_KEY});
export const mistralAi = new Mistral({apiKey: Environment.MISTRAL_API_KEY});
export const openAi = new OpenAI({apiKey: Environment.OPENAI_API_KEY, baseURL: Environment.OPENAI_BASE_URL, dangerouslyAllowBrowser: true});
export const ollama = new Ollama({
host: Environment.OLLAMA_ADDRESS,
headers: {"Authorization": `Bearer ${Environment.OLLAMA_API_KEY}`}
});
export const ollamaRequests: OllamaRequest[] = [];
export function getOllamaRequest(uuid: string): OllamaRequest | undefined {
return ollamaRequests.find(r => r.uuid === uuid);
}
export function updateOllamaRequest(uuid: string, request: OllamaRequest) {
const index = ollamaRequests.findIndex(r => r.uuid === uuid);
if (index >= 0) {
ollamaRequests[index] = request;
}
}
export function abortOllamaRequest(uuid: string): boolean {
const request = getOllamaRequest(uuid);
if (!request || request.done) return false;
try {
request.stream.abort();
updateOllamaRequest(uuid, {...request, done: true});
return true;
} catch (e) {
logError(e);
return false;
}
}
export const commands: Command[] = [
new Start(),
new Help(),
new Test(),
new Ignore(),
new Unignore(),
new Ping(),
new RandomInt(),
new RandomString(),
new SystemInfo(),
new Uptime(),
new WhatBetter(),
new When(),
new Ban(),
new Unban(),
new Quote(),
new Id(),
new Choice(),
new Coin(),
new Qr(),
new Distort(),
new Dice(),
new Title(),
new Transliteration(),
new Debug(),
new Info(),
new AdminsAdd(),
new AdminsRemove(),
new AdminsList(),
new ExportDb(),
new Shutdown(),
new Leave(),
];
if (Environment.ENABLE_UNSAFE_EVAL) {
commands.push(new Ae());
}
export const callbackCommands: CallbackCommand[] = [
new OllamaCancel(),
];
if (Environment.OLLAMA_ADDRESS && Environment.OLLAMA_MODEL) {
commands.push(
new OllamaChat(),
new OllamaPrompt(),
new OllamaListModels(),
new OllamaGetModel(),
new OllamaSetModel()
);
}
if (Environment.OLLAMA_API_KEY) {
commands.push(new OllamaSearch());
}
if (Environment.GEMINI_API_KEY) {
commands.push(
new GeminiChat(),
new GeminiListModels(),
new GeminiGetModel(),
new GeminiSetModel(),
new GeminiGenerateImage()
);
}
if (Environment.MISTRAL_API_KEY) {
commands.push(
new MistralChat(),
new MistralListModels(),
new MistralGetModel(),
new MistralSetModel()
);
}
if (Environment.OPENAI_API_KEY) {
commands.push(
new OpenAIChat(),
new OpenAIListModels(),
new OpenAIGetModel(),
new OpenAISetModel(),
new OpenAIGenImage()
);
}
export const cacheDir = path.join(Environment.DATA_PATH, "cache");
export const photoDir = path.join(cacheDir, "photo");
export const photoGenDir = path.join(photoDir, "gen");
export const videoDir = path.join(cacheDir, "video");
export const videoTempDir = path.join(videoDir, "temp");
let isShuttingDown = false;
async function shutdown(signal: NodeJS.Signals) {
if (isShuttingDown) return;
isShuttingDown = true;
console.log(`Received ${signal}. Stopping bot polling...`);
try {
await bot.stopPolling();
} catch (error) {
logError(error);
} finally {
process.exit(0);
}
}
async function main() {
const start = Date.now();
console.log(
`TEST_ENVIRONMENT: ${Environment.TEST_ENVIRONMENT}\n` +
`DATA_PATH: ${Environment.DATA_PATH}\n` +
`MAX_PHOTO_SIZE: ${Environment.MAX_PHOTO_SIZE}\n` +
`ONLY_FOR_CREATOR: ${Environment.ONLY_FOR_CREATOR_MODE}\n` +
`DEFAULT_AI_PROVIDER: ${Environment.DEFAULT_AI_PROVIDER}`
);
const dirsToCheck = [cacheDir, photoDir, photoGenDir, videoDir, videoTempDir];
dirsToCheck.forEach(dir => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
});
const now = new Date();
const midnight = new Date();
midnight.setHours(0, 0, 0, 0);
midnight.setDate(now.getDate() + 1);
const diff = midnight.getTime() - now.getTime();
console.log("Clearing up cache will be started in " + diff + "ms");
clearUpFolderFromOldFiles(cacheDir);
delay(diff).then(() => {
setInterval(() => {
console.log("Started clearing up cache");
clearUpFolderFromOldFiles(cacheDir);
}, 1000 * 60 * 60 * 24);
});
const cmds = commands.filter(cmd => {
return cmd.title && cmd.title.startsWith("/") && cmd.title.split(" ").length === 1 && cmd.description;
}).map(cmd => {
return {
command: cmd.title?.toLowerCase() || "",
description: cmd.description,
};
}) as BotCommand[];
try {
const results = await Promise.all(
[
initSystemSpecs(), readData(), retrieveAnswers(),
bot.getMe(),
bot.setMyCommands({commands: cmds, scope: {type: "default"}})
]
);
botUser = results[3];
await UserStore.put(botUser);
await bot.startPolling();
const end = Date.now();
const diff = Math.abs(end - start);
console.log(`Bot started in ${diff}ms!`);
} catch (error) {
logError(error);
}
}
bot.on("my_chat_member", processMyChatMember);
bot.on("edited_message", processEditedMessage);
bot.on("message", processNewMessage);
bot.on("inline_query", processInlineQuery);
bot.on("callback_query", processCallbackQuery);
process.on("SIGTERM", () => {
shutdown("SIGTERM").catch(logError);
});
process.on("SIGINT", () => {
shutdown("SIGINT").catch(logError);
});
main().catch(logError);