diff --git a/src/base/callback-command.ts b/src/base/callback-command.ts index 4cc6228..0f3852a 100644 --- a/src/base/callback-command.ts +++ b/src/base/callback-command.ts @@ -2,6 +2,7 @@ import {CallbackQuery, InlineKeyboardButton} from "typescript-telegram-bot-api"; import {Requirements} from "./requirements"; import {bot} from "../index"; +import {logError} from "../util/utils"; export abstract class CallbackCommand { @@ -22,7 +23,7 @@ export abstract class CallbackCommand { } async answerCallbackQuery(query: CallbackQuery): Promise { - bot.answerCallbackQuery(this.getOptions(query)).catch(console.error); + bot.answerCallbackQuery(this.getOptions(query)).catch(logError); } asButton(): InlineKeyboardButton { diff --git a/src/callback_commands/ollama-cancel.ts b/src/callback_commands/ollama-cancel.ts index d6af66e..93b238a 100644 --- a/src/callback_commands/ollama-cancel.ts +++ b/src/callback_commands/ollama-cancel.ts @@ -8,8 +8,6 @@ import {Requirements} from "../base/requirements"; import {Requirement} from "../base/requirement"; import {Environment} from "../common/environment"; -const cancelledText = "```Ollama\n❌ Отменено```"; - export class OllamaCancel extends CallbackCommand { data = "/cancel_ollama"; @@ -48,17 +46,27 @@ export class OllamaCancel extends CallbackCommand { if (msg?.text?.trim()?.length > 0) { content = msg?.text.trim(); - if (content.length + cancelledText.length > 4096) { - content = content.substring(0, 4096 - cancelledText.length - 2) + "\n"; + if (content.length + Environment.ollamaCancelledText.length > 4096) { + content = content.substring(0, 4096 - Environment.ollamaCancelledText.length - 2) + "\n"; } } - await bot.editMessageText({ - chat_id: chatId, - message_id: messageId, - text: `${content ? content : ""}${cancelledText}`, - parse_mode: "Markdown", - reply_markup: {inline_keyboard: []}, - }).catch(logError); + const newText = `${content ? content : ""}${Environment.ollamaCancelledText}`; + + try { + await bot.editMessageText({ + chat_id: chatId, + message_id: messageId, + text: newText, + parse_mode: "Markdown", + reply_markup: {inline_keyboard: []}, + }); + + if (msg) { + await MessageStore.put(msg); + } + } catch (e) { + logError(e); + } } } \ No newline at end of file diff --git a/src/commands/ae.ts b/src/commands/ae.ts index 52c06da..3adf48d 100644 --- a/src/commands/ae.ts +++ b/src/commands/ae.ts @@ -29,7 +29,7 @@ export class Ae extends ChatCommand { return; } - console.error(`${text} + logError(`${text} * Stacktrace: ${e.stack}`); await oldSendMessage(msg, text).catch(logError); diff --git a/src/commands/gemini-chat.ts b/src/commands/gemini-chat.ts index 9ca1afb..6d5d0c0 100644 --- a/src/commands/gemini-chat.ts +++ b/src/commands/gemini-chat.ts @@ -1,19 +1,18 @@ import {ChatCommand} from "../base/chat-command"; import {Message} from "typescript-telegram-bot-api"; -import { - collectReplyChainText, - escapeMarkdownV2Text, - extractText, - logError, - oldReplyToMessage, - startIntervalEditor -} from "../util/utils"; import {Environment} from "../common/environment"; import {bot, googleAi} from "../index"; import {MessageStore} from "../common/message-store"; import {Requirements} from "../base/requirements"; import {Requirement} from "../base/requirement"; import {ApiError} from "@google/genai"; +import { + collectReplyChainText, + escapeMarkdownV2Text, + logError, + oldReplyToMessage, + startIntervalEditor +} from "../util/utils"; export class GeminiChat extends ChatCommand { command = "gemini"; @@ -34,13 +33,13 @@ export class GeminiChat extends ChatCommand { const chatId = msg.chat.id; - const messageParts = await collectReplyChainText(msg, "/gemini"); + const messageParts = await collectReplyChainText(msg); console.log("MESSAGE PARTS", messageParts); const chatMessages = messageParts.map(part => { return { role: part.bot ? "assistant" : "user", - content: (Environment.USE_NAMES_IN_PROMPT && !part.bot ? `MESSAGE FROM USER "${part.name}":\n` : "") + extractText(part.content, "/gemini") + content: (Environment.USE_NAMES_IN_PROMPT && !part.bot ? `MESSAGE FROM USER "${part.name}":\n` : "") + part.content }; }); chatMessages.reverse(); @@ -134,7 +133,7 @@ export class GeminiChat extends ChatCommand { await oldReplyToMessage(waitMessage, `⏱️ ${diff}s`); } } catch (error) { - console.error(error); + logError(error); if (error instanceof ApiError) { if (error.status === 429) { diff --git a/src/commands/gemini-list-models.ts b/src/commands/gemini-list-models.ts index f919919..85bb443 100644 --- a/src/commands/gemini-list-models.ts +++ b/src/commands/gemini-list-models.ts @@ -29,7 +29,7 @@ export class GeminiListModels extends ChatCommand { parse_mode: "HTML" }); } catch (e) { - console.error(e); + logError(e); await replyToMessage({message: msg, text: "Не получилось загрузить список моделей"}).catch(logError); } } diff --git a/src/commands/mistral-chat.ts b/src/commands/mistral-chat.ts index cccd4e8..8fa82b5 100644 --- a/src/commands/mistral-chat.ts +++ b/src/commands/mistral-chat.ts @@ -5,7 +5,6 @@ import {Message} from "typescript-telegram-bot-api"; import { collectReplyChainText, escapeMarkdownV2Text, - extractText, logError, oldReplyToMessage, startIntervalEditor @@ -34,14 +33,14 @@ export class MistralChat extends ChatCommand { const chatId = msg.chat.id; - const messageParts = await collectReplyChainText(msg, "/mistral"); + const messageParts = await collectReplyChainText(msg); console.log("MESSAGE PARTS", messageParts); const chatMessages = messageParts.map(part => { const content = []; content.push({ type: "text", - text: (Environment.USE_NAMES_IN_PROMPT && !part.bot ? `MESSAGE FROM USER "${part.name}":\n` : "") + extractText(part.content, Environment.BOT_PREFIX), + text: (Environment.USE_NAMES_IN_PROMPT && !part.bot ? `MESSAGE FROM USER "${part.name}":\n` : "") + part.content, }); if (part.images && part.images.length > 0) { @@ -143,7 +142,7 @@ export class MistralChat extends ChatCommand { await oldReplyToMessage(waitMessage, `⏱️ ${diff}s`); } } catch (error) { - console.error(error); + logError(error); await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${error.toString()}`).catch(logError); } } diff --git a/src/commands/mistral-list-models.ts b/src/commands/mistral-list-models.ts index 639560d..6fb818e 100644 --- a/src/commands/mistral-list-models.ts +++ b/src/commands/mistral-list-models.ts @@ -29,7 +29,7 @@ export class MistralListModels extends ChatCommand { parse_mode: "HTML" }); } catch (e) { - console.error(e); + logError(e); await oldReplyToMessage(msg, "Не получилось загрузить список моделей").catch(logError); } } diff --git a/src/commands/ollama-chat.ts b/src/commands/ollama-chat.ts index 7577636..af70245 100644 --- a/src/commands/ollama-chat.ts +++ b/src/commands/ollama-chat.ts @@ -4,7 +4,6 @@ import {abortOllamaRequest, bot, getOllamaRequest, ollama, ollamaRequests} from import { collectReplyChainText, escapeMarkdownV2Text, - extractText, logError, oldReplyToMessage, startIntervalEditor @@ -37,7 +36,7 @@ export class OllamaChat extends ChatCommand { const chatMessages = messageParts.map(part => { return { role: part.bot ? "assistant" : "user", - content: (Environment.USE_NAMES_IN_PROMPT && !part.bot ? `MESSAGE FROM USER "${part.name}":\n` : "") + extractText(part.content, Environment.BOT_PREFIX), + content: (Environment.USE_NAMES_IN_PROMPT && !part.bot ? `MESSAGE FROM USER "${part.name}":\n` : "") + part.content, images: part.images }; }); @@ -198,7 +197,7 @@ export class OllamaChat extends ChatCommand { reply_markup: {inline_keyboard: []} }).catch(logError); - console.error(error); + logError(error); await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${error.toString()}`).catch(logError); } } diff --git a/src/commands/ollama-list-models.ts b/src/commands/ollama-list-models.ts index 9f1c8ea..4c29963 100644 --- a/src/commands/ollama-list-models.ts +++ b/src/commands/ollama-list-models.ts @@ -29,7 +29,7 @@ export class OllamaListModels extends ChatCommand { parse_mode: "HTML" }); } catch (e) { - console.error(e); + logError(e); await oldReplyToMessage(msg, "Не получилось загрузить список моделей").catch(logError); } } diff --git a/src/commands/ollama-prompt.ts b/src/commands/ollama-prompt.ts index 2c38a93..095ac64 100644 --- a/src/commands/ollama-prompt.ts +++ b/src/commands/ollama-prompt.ts @@ -182,7 +182,7 @@ export class OllamaPrompt extends ChatCommand { reply_markup: {inline_keyboard: []} }).catch(logError); - console.error(error); + logError(error); await oldReplyToMessage(waitMessage, `Произошла ошибка!\n${error.toString()}`).catch(logError); } } diff --git a/src/commands/ollama-search.ts b/src/commands/ollama-search.ts index 775d240..de7821f 100644 --- a/src/commands/ollama-search.ts +++ b/src/commands/ollama-search.ts @@ -4,7 +4,7 @@ import {Requirement} from "../base/requirement"; import {Message} from "typescript-telegram-bot-api"; import {bot, ollama} from "../index"; import {WebSearchResponse} from "../model/web-search-response"; -import {editMessageText} from "../util/utils"; +import {editMessageText, logError} from "../util/utils"; import {Environment} from "../common/environment"; export class OllamaSearch extends ChatCommand { @@ -42,7 +42,7 @@ export class OllamaSearch extends ChatCommand { await editMessageText(chatId, wait.message_id, message); } catch (error) { - console.error(error); + logError(error); } return Promise.resolve(); } diff --git a/src/commands/quote.ts b/src/commands/quote.ts index 7c03319..68b7826 100644 --- a/src/commands/quote.ts +++ b/src/commands/quote.ts @@ -34,7 +34,7 @@ try { GlobalFonts.registerFromPath("./assets/JetBrainsMono-Italic.ttf", "JetBrainsMonoItalic"); GlobalFonts.registerFromPath("./assets/JetBrainsMono-Regular.ttf", "JetBrainsMonoRegular"); } catch (e) { - console.error(e); + logError(e); } export class Quote extends ChatCommand { @@ -75,7 +75,7 @@ export class Quote extends ChatCommand { }, }).catch(logError); } catch (e) { - console.error(e); + logError(e); await oldSendMessage(msg, "Не смог собрать цитату 😢").catch(logError); } } diff --git a/src/common/environment.ts b/src/common/environment.ts index 0b8d9b3..58b9cbc 100644 --- a/src/common/environment.ts +++ b/src/common/environment.ts @@ -36,6 +36,7 @@ export class Environment { static MISTRAL_MODEL: string; static waitText = "⏳ Дайте-ка подумать..."; + static ollamaCancelledText = "```Ollama\n❌ Отменено```"; static load() { Environment.BOT_TOKEN = process.env.BOT_TOKEN; diff --git a/src/common/message-store.ts b/src/common/message-store.ts index c663bca..7540052 100644 --- a/src/common/message-store.ts +++ b/src/common/message-store.ts @@ -1,7 +1,6 @@ import {StoredMessage} from "../model/stored-message"; import {Message} from "typescript-telegram-bot-api"; -import {extractTextMessage} from "../util/utils"; -import {Environment} from "./environment"; +import {extractTextMessage, isStoredMessage} from "../util/utils"; import {messageDao} from "../index"; export class MessageStore { @@ -15,19 +14,18 @@ export class MessageStore { return this.map; } - static async put(m: Message, prefix: string = Environment.BOT_PREFIX) { - const msg: StoredMessage = { + static async put(m: Message | StoredMessage) { + const msg: StoredMessage = isStoredMessage(m) ? m : { chatId: m.chat.id, id: m.message_id, replyToMessageId: m.reply_to_message?.message_id ?? null, fromId: m.from.id, - text: extractTextMessage(m, prefix), + text: extractTextMessage(m), date: m.date ?? 0, }; - this.map.set(this.key(m.chat.id, m.message_id), msg); - - await messageDao.insert(messageDao.mapTo([m])); + this.map.set(this.key(msg.chatId, msg.id), msg); + await messageDao.insert(messageDao.mapStoredTo([msg])); } static async get(chatId: number, messageId: number): Promise { diff --git a/src/db/database-manager.ts b/src/db/database-manager.ts index 6dfaf94..ed6209a 100644 --- a/src/db/database-manager.ts +++ b/src/db/database-manager.ts @@ -1,6 +1,7 @@ import "dotenv/config"; import {drizzle, LibSQLDatabase} from "drizzle-orm/libsql"; import {Environment} from "../common/environment"; +import {logError} from "../util/utils"; export class DatabaseManager { @@ -10,7 +11,7 @@ export class DatabaseManager { try { DatabaseManager.db = drizzle(Environment.DB_PATH); } catch (e) { - console.error(e); + logError(e); } } } \ No newline at end of file diff --git a/src/db/database.ts b/src/db/database.ts index 24c0097..f19d02b 100644 --- a/src/db/database.ts +++ b/src/db/database.ts @@ -1,5 +1,6 @@ import * as fs from "fs"; import {Environment} from "../common/environment"; +import {logError} from "../util/utils"; export let muted: Set = new Set(); @@ -57,7 +58,7 @@ export async function readData(): Promise { return Promise.resolve(); } catch (e) { - console.error(e); + logError(e); return Promise.reject(e); } } @@ -91,7 +92,7 @@ export async function retrieveAnswers(): Promise { json.day.forEach(e => dayAnswers.push(e)); return Promise.resolve(); } catch (e) { - console.error(e); + logError(e); return Promise.reject(e); } } \ No newline at end of file diff --git a/src/db/message-dao.ts b/src/db/message-dao.ts index 598a618..458e2e4 100644 --- a/src/db/message-dao.ts +++ b/src/db/message-dao.ts @@ -3,10 +3,8 @@ import {DatabaseManager} from "./database-manager"; import {StoredMessage} from "../model/stored-message"; import {and, eq} from "drizzle-orm"; import {inArray} from "drizzle-orm/sql/expressions/conditions"; -import {Message} from "typescript-telegram-bot-api"; import {Dao} from "../base/dao"; -import {buildExcludedSet, extractTextMessage} from "../util/utils"; -import {Environment} from "../common/environment"; +import {buildExcludedSet} from "../util/utils"; export class MessageDao extends Dao { @@ -82,14 +80,14 @@ export class MessageDao extends Dao { return true; } - mapTo(messages: Message[]): MessageInsert[] { + mapStoredTo(messages: StoredMessage[]): MessageInsert[] { return messages.map(msg => { return { - chatId: msg.chat.id, - id: msg.message_id, - replyToMessageId: msg.reply_to_message?.message_id, - fromId: msg.from.id, - text: extractTextMessage(msg, Environment.BOT_PREFIX), + chatId: msg.chatId, + id: msg.id, + replyToMessageId: msg.replyToMessageId, + fromId: msg.fromId, + text: msg.text, date: msg.date, }; }); diff --git a/src/index.ts b/src/index.ts index c3b316f..83c854a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -71,7 +71,7 @@ import {GeminiGetModel} from "./commands/gemini-get-model"; import {GeminiSetModel} from "./commands/gemini-set-model"; import {Debug} from "./commands/debug"; -process.setUncaughtExceptionCaptureCallback(console.error); +process.setUncaughtExceptionCaptureCallback(logError); Environment.load(); DatabaseManager.init(); @@ -112,7 +112,7 @@ export function abortOllamaRequest(uuid: string): boolean { updateOllamaRequest(uuid, {...request, done: true}); return true; } catch (e) { - console.error(e); + logError(e); return false; } } @@ -227,7 +227,7 @@ async function main() { console.log("Bot started!"); } catch (error) { - console.error(error); + logError(error); } } @@ -336,7 +336,7 @@ bot.on("inline_query", async (query) => { results: queryResults, }); } catch (e) { - console.error(e); + logError(e); } } else { await bot.answerInlineQuery({ @@ -351,4 +351,4 @@ bot.on("callback_query", async (query) => { await findAndExecuteCallbackCommand(callbackCommands, query); }); -main().catch(console.error); \ No newline at end of file +main().catch(logError); \ No newline at end of file diff --git a/src/util/utils.ts b/src/util/utils.ts index aac59ce..a62b93d 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -40,7 +40,7 @@ export const ignoreIfMarkupFailed = (e: Error | TelegramError) => { } }; -export const logError = (e: Error | TelegramError) => { +export const logError = (e: Error | TelegramError | string) => { console.error(e); }; @@ -231,7 +231,7 @@ export async function editMessageText(chatId: number, messageId: number, message }).catch(ignoreIfMarkupFailed); return Promise.resolve(); } catch (e) { - console.error(e); + logError(e); if (e instanceof TelegramError && e.response.description.includes("Too Many Requests")) { const delay = Number(e.message.split("retry after ")[1]) || 30; @@ -301,7 +301,7 @@ export async function oldReplyToMessage(message: Message, text: string, parseMod } export async function sendErrorPlaceholder(message: Message): Promise { - return await sendMessage({message: message, text: "Произошла ошибка ⚠️"}).catch(console.error) as Message; + return await sendMessage({message: message, text: "Произошла ошибка ⚠️"}).catch(logError) as Message; } export async function initSystemSpecs(): Promise { @@ -446,34 +446,32 @@ export async function getUserAvatar(userId: number): Promise { return Buffer.from(res.data); } -export function extractTextMessage(msg: Message, prefix: string = ""): string | null { - let text = (msg?.text ?? msg?.caption ?? "").trim(); - if (text.toLowerCase().startsWith(prefix.toLowerCase())) { - text = text.substring(prefix.length); - } - - text = text.trim(); +export function extractTextMessage(msg: Message | StoredMessage | string): string | null { + const text = (typeof msg === "string" ? msg : isStoredMessage(msg) ? msg.text : msg?.text ?? msg?.caption ?? "").trim(); if (text.length === 0) return null; - return text; } -export function extractTextStored(msg: StoredMessage, prefix: string): string { - let text = (msg?.text ?? "").trim(); - if (text.toLowerCase().startsWith(prefix.toLowerCase())) { - text = text.substring(prefix.length).trim(); +export function cutPrefixes(msg: Message | StoredMessage | string): string { + const prefixes = [ + Environment.BOT_PREFIX, + `/gemini@${botUser.username}`, + "/gemini", + `/mistral@${botUser.username}`, + "/mistral" + ]; + + const text = extractTextMessage(msg); + let newText = text; + + for (const prefix of prefixes) { + if (text.toLowerCase().startsWith(prefix.toLowerCase())) { + newText = newText.substring(prefix.length).trim(); + break; + } } - return text; -} - -export function extractText(text: string, prefix: string): string { - if (!text) return ""; - if (text.toLowerCase().startsWith(prefix.toLowerCase())) { - text = text.substring(prefix.length).trim(); - } - - return text; + return newText; } export function isStoredMessage(msg: Message | StoredMessage): msg is StoredMessage { @@ -502,7 +500,7 @@ export async function loadImageIfExists(msg: Message | StoredMessage): Promise { +export async function collectReplyChainText(triggerMsg: Message, limit: number = 40, includeTrigger = true, cutPrefix: boolean = true): Promise { const chatId = triggerMsg.chat.id as number; const parts: MessagePart[] = []; if (includeTrigger) { - const t = extractTextMessage(triggerMsg, prefix); - const img = (await loadImageIfExists(triggerMsg)) /*|| triggerMsg.reply_to_message ? - (await loadImageIfExists(triggerMsg.reply_to_message)) : null*/; - if (t) { + const t = extractTextMessage(triggerMsg); + const text = cutPrefix ? cutPrefixes(t) : t; + const img = await loadImageIfExists(triggerMsg); + if (text) { parts.push({ bot: triggerMsg.from.id === botUser.id, - content: t, + content: text, name: triggerMsg.from.first_name, images: img ? [img] : [] }); @@ -534,7 +532,8 @@ export async function collectReplyChainText(triggerMsg: Message, prefix: string return parts; } - const firstText = extractTextMessage(first, prefix); + const ft = extractTextMessage(first); + const firstText = cutPrefix ? cutPrefixes(ft) : ft; if (firstText || first.photo) { const img = await loadImageIfExists(first); parts.push({ @@ -553,14 +552,17 @@ export async function collectReplyChainText(triggerMsg: Message, prefix: string if (!parentId) break; const parent = await messageDao.getById({chatId: chatId, id: parentId}); - if (!parent?.text && !parent?.photoMaxSizeFilePath) break; + const pt = extractTextMessage(parent); + const parentText = cutPrefix ? cutPrefixes(pt) : pt; + + if (!parentText && !parent?.photoMaxSizeFilePath) break; const user = await UserStore.get(parent.fromId); const img = await loadImageIfExists(parent); parts.push({ bot: parent.fromId === botUser.id, - content: extractTextStored(parent, prefix), + content: parentText, name: user?.firstName, images: img ? [img] : [] }); @@ -829,7 +831,7 @@ export function startIntervalEditor(params: { lastSent = next; } catch (e) { if ((e?.description ?? e?.message ?? "").includes("message is not modified")) return; - console.error("edit failed:", e); + logError("edit failed: " + e); } };