From d373f44ca149f438738333efbfb9e84f84413e98 Mon Sep 17 00:00:00 2001 From: Aidan Date: Wed, 7 May 2025 23:04:16 -0400 Subject: [PATCH] Revert "[FEATURE] Add /ask command (#54)" (#55) This reverts commit 4f88a85ccb1d53eb2692e6c0a12e7fee82430b34. --- .dockerignore | 3 +- .env.example | 3 - .gitignore | 8 +- README.md | 30 +-- ...-compose.yml.example => docker-compose.yml | 2 +- docker-compose.yml.ai.example | 15 -- src/commands/ai.ts | 245 ------------------ src/commands/help.ts | 7 +- src/locales/english.json | 44 ++-- src/locales/portuguese.json | 7 +- src/utils/log.ts | 82 ------ src/utils/rate-limiter.ts | 235 ----------------- 12 files changed, 27 insertions(+), 654 deletions(-) rename docker-compose.yml.example => docker-compose.yml (84%) delete mode 100644 docker-compose.yml.ai.example delete mode 100644 src/commands/ai.ts delete mode 100644 src/utils/log.ts delete mode 100644 src/utils/rate-limiter.ts diff --git a/.dockerignore b/.dockerignore index 9fe19f3..33e390a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,5 +4,4 @@ npm-debug.log .gitignore .env *.md -!README.md -ollama/ \ No newline at end of file +!README.md \ No newline at end of file diff --git a/.env.example b/.env.example index 997fbc5..452211b 100644 --- a/.env.example +++ b/.env.example @@ -5,9 +5,6 @@ botSource = "https://github.com/ABOCN/TelegramBot" # insert token here botToken = "" -# ai features -# ollamaApi = "http://ollama:11434" - # misc (botAdmins isnt a array here!) maxRetries = 9999 botAdmins = 00000000, 00000000, 00000000 diff --git a/.gitignore b/.gitignore index 278fef8..6b42f1f 100644 --- a/.gitignore +++ b/.gitignore @@ -144,10 +144,4 @@ yt-dlp ffmpeg # Bun -bun.lock* - -# Ollama -ollama/ - -# Docker -docker-compose.yml \ No newline at end of file +bun.lock* \ No newline at end of file diff --git a/README.md b/README.md index a0e4aab..8fa5b60 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,6 @@ Kowalski is a a simple Telegram bot made in Node.js. - FFmpeg (only for the `/yt` command) - Docker and Docker Compose (only required for Docker setup) -_AI features require a higher-end system with a CPU/GPU_ - ## Running locally (non-Docker setup) First, clone the repo with Git: @@ -57,23 +55,9 @@ You can also run Kowalski using Docker, which simplifies the setup process. Make ### Using Docker Compose -1. **Copy compose file** +1. **Make sure to setup your `.env` file first!** - _Without AI (Ollama)_ - - ```bash - mv docker-compose.yml.example docker-compose.yml - ``` - - _With AI (Ollama)_ - - ```bash - mv docker-compose.yml.ai.example docker-compose.yml - ``` - -2. **Make sure to setup your `.env` file first!** - -3. **Run the container** +2. **Run the container** ```bash docker compose up -d @@ -97,9 +81,6 @@ If you prefer to use Docker directly, you can use these instructions instead. docker run -d --name kowalski --restart unless-stopped -v $(pwd)/.env:/usr/src/app/.env:ro kowalski ``` -> [!NOTE] -> You must setup Ollama on your own if you would like to use AI features. - ## .env Functions > [!IMPORTANT] @@ -109,7 +90,6 @@ If you prefer to use Docker directly, you can use these instructions instead. - **botPrivacy**: Put the link to your bot privacy policy. - **maxRetries**: Maximum number of retries for a failing command on Kowalski. Default is 5. If the limit is hit, the bot will crash past this number. - **botToken**: Put your bot token that you created at [@BotFather](https://t.me/botfather). -- **ollamaApi** (optional): Ollama API endpoint for various AI features, will be disabled if not set - **botAdmins**: Put the ID of the people responsible for managing the bot. They can use some administrative + exclusive commands on any group. - **lastKey**: Last.fm API key, for use on `lastfm.js` functions, like see who is listening to what song and etc. - **weatherKey**: Weather.com API key, used for the `/weather` command. @@ -126,12 +106,6 @@ If you prefer to use Docker directly, you can use these instructions instead. chmod +x src/plugins/yt-dlp/yt-dlp ``` -### AI - -**Q:** How can I disable AI features? - -**A:** AI features are disabled by default, unless you have set `ollamaApi` in your `.env` file. Please remove or comment out this line to disable all AI functionality. - ## Contributors diff --git a/docker-compose.yml.example b/docker-compose.yml similarity index 84% rename from docker-compose.yml.example rename to docker-compose.yml index f3bb819..0aab44a 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml @@ -6,4 +6,4 @@ services: volumes: - ./.env:/usr/src/app/.env:ro environment: - - NODE_ENV=production \ No newline at end of file + - NODE_ENV=production \ No newline at end of file diff --git a/docker-compose.yml.ai.example b/docker-compose.yml.ai.example deleted file mode 100644 index 2c516f7..0000000 --- a/docker-compose.yml.ai.example +++ /dev/null @@ -1,15 +0,0 @@ -services: - kowalski: - build: . - container_name: kowalski - restart: unless-stopped - volumes: - - ./.env:/usr/src/app/.env:ro - environment: - - NODE_ENV=production - ollama: - image: ollama/ollama - container_name: kowalski-ollama - restart: unless-stopped - volumes: - - ./ollama:/root/.ollama \ No newline at end of file diff --git a/src/commands/ai.ts b/src/commands/ai.ts deleted file mode 100644 index e62489c..0000000 --- a/src/commands/ai.ts +++ /dev/null @@ -1,245 +0,0 @@ -// AI.TS -// by ihatenodejs/Aidan -// -// ----------------------------------------------------------------------- -// -// This is free and unencumbered software released into the public domain. -// -// Anyone is free to copy, modify, publish, use, compile, sell, or -// distribute this software, either in source code form or as a compiled -// binary, for any purpose, commercial or non-commercial, and by any -// means. -// -// In jurisdictions that recognize copyright laws, the author or authors -// of this software dedicate any and all copyright interest in the -// software to the public domain. We make this dedication for the benefit -// of the public at large and to the detriment of our heirs and -// successors. We intend this dedication to be an overt act of -// relinquishment in perpetuity of all present and future rights to this -// software under copyright law. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -// -// For more information, please refer to - -import { isOnSpamWatch } from "../spamwatch/spamwatch" -import spamwatchMiddlewareModule from "../spamwatch/Middleware" -import { Telegraf, Context } from "telegraf" -import type { Message } from "telegraf/types" -import { replyToMessageId } from "../utils/reply-to-message-id" -import { getStrings } from "../plugins/checklang" -import { languageCode } from "../utils/language-code" -import axios from "axios" -import { rateLimiter } from "../utils/rate-limiter" -import { logger } from "../utils/log" - -const spamwatchMiddleware = spamwatchMiddlewareModule(isOnSpamWatch) -//const model = "qwen3:0.6b" -const model = "deepseek-r1:1.5b" - -type TextContext = Context & { message: Message.TextMessage } - -export function sanitizeForJson(text: string): string { - return text - .replace(/\\/g, '\\\\') - .replace(/"/g, '\\"') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/\t/g, '\\t') -} - -async function getResponse(prompt: string, ctx: TextContext, replyGenerating: Message) { - const Strings = getStrings(languageCode(ctx)) - - if (!ctx.chat) return { - "success": false, - "error": Strings.unexpectedErr.replace("{error}", "No chat found"), - } - - try { - const aiResponse = await axios.post(`${process.env.ollamaApi}/api/generate`, { - model: model, - prompt: prompt, - stream: true, - }, { - responseType: "stream", - }) - - let fullResponse = "" - let thoughts = "" - let lastUpdate = Date.now() - - for await (const chunk of aiResponse.data) { - const lines = chunk.toString().split('\n') - for (const line of lines) { - if (!line.trim()) continue - let ln = JSON.parse(line) - - if (ln.response.includes("")) { logger.logThinking(true) } else if (ln.response.includes("")) { logger.logThinking(false) } - - try { - const now = Date.now() - - if (ln.response) { - const patchedThoughts = ln.response.replace("", "`Thinking...`").replace("", "`Finished thinking`") - thoughts += patchedThoughts - fullResponse += patchedThoughts - - if (now - lastUpdate >= 1000) { - await rateLimiter.editMessageWithRetry( - ctx, - ctx.chat.id, - replyGenerating.message_id, - thoughts, - { parse_mode: 'Markdown' } - ) - lastUpdate = now - } - } - } catch (e) { - console.error("Error parsing chunk:", e) - } - } - } - - return { - "success": true, - "response": fullResponse, - } - } catch (error: any) { - let shouldPullModel = false - - if (error.response?.data?.error) { - if (error.response.data.error.includes(`model '${model}' not found`) || error.status === 404) { - shouldPullModel = true - } else { - console.error("[!] 1", error.response.data.error) - return { - "success": false, - "error": error.response.data.error, - } - } - } else if (error.status === 404) { - shouldPullModel = true - } - - if (shouldPullModel) { - ctx.telegram.editMessageText(ctx.chat.id, replyGenerating.message_id, undefined, `🔄 Pulling ${model} from ollama...`) - console.log(`[i] Pulling ${model} from ollama...`) - - const pullModelStream = await axios.post(`${process.env.ollamaApi}/api/pull`, { - model: model, - stream: false, - }) - - if (pullModelStream.data.status !== ("success")) { - console.error("[!] Something went wrong:", pullModelStream.data) - return { - "success": false, - "error": `❌ Something went wrong while pulling ${model}, please try your command again!`, - } - } - - console.log("[i] Model pulled successfully") - return { - "success": true, - "response": `✅ Pulled ${model} successfully, please retry the command.`, - } - } - - if (error.response) { - console.error("[!] 2", error.response) - return { - "success": false, - "error": error.response, - } - } - - if (error.statusText) { - console.error("[!] 3", error.statusText) - return { - "success": false, - "error": error.statusText, - } - } - - return { - "success": false, - "error": "An unexpected error occurred", - } - } -} - -export default (bot: Telegraf) => { - bot.command("ask", spamwatchMiddleware, async (ctx) => { - if (!ctx.message || !('text' in ctx.message)) return; - const textCtx = ctx as TextContext; - const reply_to_message_id = replyToMessageId(textCtx) - const Strings = getStrings(languageCode(textCtx)) - const message = textCtx.message.text - const author = ("@" + ctx.from?.username) || ctx.from?.first_name - - logger.logCmdStart(author) - - if (!process.env.ollamaApi) { - await ctx.reply(Strings.aiDisabled, { - parse_mode: 'Markdown', - ...({ reply_to_message_id }) - }) - return - } - - const replyGenerating = await ctx.reply(Strings.askGenerating.replace("{model}", model), { - parse_mode: 'Markdown', - ...({ reply_to_message_id }) - }) - - const fixedMsg = message.replace(/\/ask /, "") - if (fixedMsg.length < 1) { - await ctx.reply(Strings.askNoMessage, { - parse_mode: 'Markdown', - ...({ reply_to_message_id }) - }) - return - } - - logger.logPrompt(fixedMsg) - - const prompt = sanitizeForJson( -`You are a helpful assistant named Kowalski, who has been given a message from a user. - -The message is: - -${fixedMsg}`) - const aiResponse = await getResponse(prompt, textCtx, replyGenerating) - if (!aiResponse) return - - if (aiResponse.success && aiResponse.response) { - if (!ctx.chat) return - await rateLimiter.editMessageWithRetry( - ctx, - ctx.chat.id, - replyGenerating.message_id, - aiResponse.response, - { parse_mode: 'Markdown' } - ) - } else { - if (!ctx.chat) return - const error = Strings.unexpectedErr.replace("{error}", aiResponse.error) - await rateLimiter.editMessageWithRetry( - ctx, - ctx.chat.id, - replyGenerating.message_id, - error, - { parse_mode: 'Markdown' } - ) - console.error("[!] Error sending response:", aiResponse.error) - } - }) -} \ No newline at end of file diff --git a/src/commands/help.ts b/src/commands/help.ts index 3a6d3a0..39191c1 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -32,8 +32,7 @@ async function sendHelpMessage(ctx, isEditing) { [{ text: Strings.mainCommands, callback_data: 'helpMain' }, { text: Strings.usefulCommands, callback_data: 'helpUseful' }], [{ text: Strings.interactiveEmojis, callback_data: 'helpInteractive' }, { text: Strings.funnyCommands, callback_data: 'helpFunny' }], [{ text: Strings.lastFm.helpEntry, callback_data: 'helpLast' }, { text: Strings.animalCommands, callback_data: 'helpAnimals' }], - [{ text: Strings.ytDownload.helpEntry, callback_data: 'helpYouTube' }, { text: Strings.ponyApi.helpEntry, callback_data: 'helpMLP' }], - [{ text: Strings.aiCmds, callback_data: 'helpAi' }] + [{ text: Strings.ytDownload.helpEntry, callback_data: 'helpYouTube' }, { text: Strings.ponyApi.helpEntry, callback_data: 'helpMLP' }] ] } }; @@ -113,10 +112,6 @@ export default (bot) => { await ctx.answerCbQuery(); await ctx.editMessageText(Strings.ponyApi.helpDesc, options); break; - case 'helpAi': - await ctx.answerCbQuery(); - await ctx.editMessageText(Strings.aiCmdsDesc, options); - break; case 'helpBack': await ctx.answerCbQuery(); await sendHelpMessage(ctx, true); diff --git a/src/locales/english.json b/src/locales/english.json index 76cef32..f8bb0ec 100644 --- a/src/locales/english.json +++ b/src/locales/english.json @@ -33,8 +33,8 @@ "funEmojiResult": "*You rolled {emoji} and got* `{value}`*!*\nYou don't know what that means? Me neither!", "gifErr": "*Something went wrong while sending the GIF. Please try again later.*\n\n{err}", "lastFm": { - "helpEntry": "🎵 Last.fm", - "helpDesc": "🎵 *Last.fm*\n\n- /lt | /lmu | /last | /lfm: Shows the last song from your Last.fm profile + the number of plays.\n- /setuser ``: Sets the user for the command above.", + "helpEntry": "Last.fm", + "helpDesc": "*Last.fm*\n\n- /lt | /lmu | /last | /lfm: Shows the last song from your Last.fm profile + the number of plays.\n- /setuser ``: Sets the user for the command above.", "noUser": "*Please provide a Last.fm username.*\nExample: `/setuser `", "noUserSet": "*You haven't set your Last.fm username yet.*\nUse the command /setuser to set.\n\nExample: `/setuser `", "noRecentTracks": "*No recent tracks found for Last.fm user* `{lastfmUser}`*.*", @@ -52,27 +52,25 @@ "apiErr": "*An error occurred while retrieving the weather. Please try again later.*\n\n`{error}`", "apiKeyErr": "*An API key was not set by the bot owner. Please try again later.*" }, - "mainCommands": "ℹ️ Main Commands", - "mainCommandsDesc": "ℹ️ *Main Commands*\n\n- /help: Show bot's help\n- /start: Start the bot\n- /privacy: Read the bot's Privacy Policy", - "usefulCommands": "🛠️ Useful Commands", - "usefulCommandsDesc": "🛠️ *Useful commands*\n\n- /chatinfo: Send information about the group\n- /userinfo: Send information about yourself\n- /d | /device ``: Search for a device on GSMArena and show its specs.\n/codename | /whatis ``: Shows what device is based on the codename. Example: `/codename begonia`\n- /weather | /clima ``: See weather status for a specific location.\n- /modarchive | /tma ``: Download a module from The Mod Archive.\n- /http ``: Send details about a specific HTTP code. Example: `/http 404`", - "funnyCommands": "😂 Funny Commands", - "funnyCommandsDesc": "😂 *Funny Commands*\n\n- /gay: Check if you are gay\n- /furry: Check if you are a furry\n- /random: Pick a random number between 0-10", - "interactiveEmojis": "🎲 Interactive Emojis", - "interactiveEmojisDesc": "🎲 *Interactive emojis*\n\n- /dice: Roll a dice\n- /idice: Infinitely roll a colored dice\n- /slot: Try to combine the figures!\n- /ball: Try to kick the ball into the goal!\n- /bowling: Try to hit the pins!\n- /dart: Try to hit the target!", - "animalCommands": "🐱 Animals", - "animalCommandsDesc": "🐱 *Animals*\n\n- /soggy | /soggycat `<1 | 2 | 3 | 4 | orig | thumb | sticker | alt>`: Sends the [Soggy cat meme](https://knowyourmeme.com/memes/soggy-cat)\n- /cat: Sends a random picture of a cat.\n- /fox: Sends a random picture of a fox.\n- /duck: Sends a random picture of a duck.\n- /dog: Sends a random picture of a dog.\n- /httpcat ``: Send cat memes from http.cat with your specified HTTP code. Example: `/httpcat 404`", - "aiCmds": "✨ AI Commands", - "aiCmdsDesc": "✨ *AI Commands*\n\n- /ask ``: Ask a question to an AI", + "mainCommands": "Main commands", + "mainCommandsDesc": "*Main commands*\n\n- /help: Show bot's help\n- /start: Start the bot\n- /privacy: Read the bot's Privacy Policy", + "usefulCommands": "Useful commands", + "usefulCommandsDesc": "*Useful commands*\n\n- /chatinfo: Send information about the group\n- /userinfo: Send information about yourself\n- /d | /device ``: Search for a device on GSMArena and show its specs.\n/codename | /whatis ``: Shows what device is based on the codename. Example: `/codename begonia`\n- /weather | /clima ``: See weather status for a specific location.\n- /modarchive | /tma ``: Download a module from The Mod Archive.\n- /http ``: Send details about a specific HTTP code. Example: `/http 404`", + "funnyCommands": "Funny commands", + "funnyCommandsDesc": "*Funny commands*\n\n- /gay: Check if you are gay\n- /furry: Check if you are a furry\n- /random: Pick a random number between 0-10", + "interactiveEmojis": "Interactive emojis", + "interactiveEmojisDesc": "*Interactive emojis*\n\n- /dice: Roll a dice\n- /idice: Infinitely roll a colored dice\n- /slot: Try to combine the figures!\n- /ball: Try to kick the ball into the goal!\n- /bowling: Try to hit the pins!\n- /dart: Try to hit the target!", + "animalCommands": "Animals", + "animalCommandsDesc": "*Animals*\n\n- /soggy | /soggycat `<1 | 2 | 3 | 4 | orig | thumb | sticker | alt>`: Sends the [Soggy cat meme](https://knowyourmeme.com/memes/soggy-cat)\n- /cat: Sends a random picture of a cat.\n- /fox: Sends a random picture of a fox.\n- /duck: Sends a random picture of a duck.\n- /dog: Sends a random picture of a dog.\n- /httpcat ``: Send cat memes from http.cat with your specified HTTP code. Example: `/httpcat 404`", "maInvalidModule": "Please provide a valid module ID from The Mod Archive.\nExample: `/modarchive 81574`", "maDownloadError": "Error downloading the file. Check the module ID and try again.", "ytDownload": { - "helpEntry": "📺 YouTube Download", - "helpDesc": "📺 *YouTube Download*\n\n- /yt | /ytdl | /sdl | /dl | /video `