Merge branch 'main' into fix_ttk

This commit is contained in:
Aidan 2025-04-14 20:29:15 -04:00
commit 32a389a301
11 changed files with 321 additions and 213 deletions

8
.dockerignore Normal file
View file

@ -0,0 +1,8 @@
node_modules
npm-debug.log
.git
.gitignore
.env
config.env
*.md
!README.md

18
Dockerfile Normal file
View file

@ -0,0 +1,18 @@
FROM node:20-slim
# Install ffmpeg and other deps
RUN apt-get update && apt-get install -y ffmpeg && apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN chmod +x /usr/src/app/src/plugins/yt-dlp/yt-dlp
VOLUME /usr/src/app/config.env
CMD ["npm", "start"]

View file

@ -9,10 +9,10 @@ Kowalski is a a simple Telegram bot made in Node.js.
## Self-host requirements ## Self-host requirements
- Node.js 20 or newer (you can also use Bun) - Node.js 20 or newer (you can also use [Bun](https://bun.sh))
- A Telegram bot (create one at [@BotFather](https://t.me/botfather)) - A Telegram bot (create one at [@BotFather](https://t.me/botfather))
- Latest version of Node.js
- FFmpeg (only for the `/yt` command) - FFmpeg (only for the `/yt` command)
- Docker and Docker Compose (only required for Docker setup)
## Run it yourself, develop or contribute with Kowalski ## Run it yourself, develop or contribute with Kowalski
@ -36,6 +36,47 @@ After editing the file, save all changes and run the bot with ``npm start``.
> [!TIP] > [!TIP]
> To deal with dependencies, just run ``npm install`` or ``npm i`` at any moment to install all of them. > To deal with dependencies, just run ``npm install`` or ``npm i`` at any moment to install all of them.
## Running with Docker
> [!IMPORTANT]
> Please complete the above steps to prepare your local copy for building. You do not need to install FFmpeg on your host system.
You can also run Kowalski using Docker, which simplifies the setup process. Make sure you have Docker and Docker Compose installed.
### Using Docker Compose
1. **Make sure to setup your `config.env` file first!**
2. **Run the container**
```bash
docker compose up -d
```
> [!NOTE]
> The `-d` flag causes Kowalski to run in the background. If you're just playing around, you may not want to use this flag.
### Using Docker Run
If you prefer to use Docker directly, you can use these instructions instead.
1. **Make sure to setup your `config.env` file first!**
2. **Build the image**
```bash
docker build -t kowalski .
```
3. **Run the container**
```bash
docker run -d --name kowalski --restart unless-stopped -v $(pwd)/config.env:/usr/src/app/config.env:ro kowalski
```
> [!NOTE]
> The `-d` flag causes Kowalski to run in the background. If you're just playing around, you may not want to use this flag.
## config.env Functions ## config.env Functions
- **botSource**: Put the link to your bot source code. - **botSource**: Put the link to your bot source code.
@ -48,6 +89,18 @@ After editing the file, save all changes and run the bot with ``npm start``.
- Take care of your ``config.env`` file, as it is so much important and needs to be secret (like your passwords), as anyone can do whatever they want to the bot with this token! - Take care of your ``config.env`` file, as it is so much important and needs to be secret (like your passwords), as anyone can do whatever they want to the bot with this token!
## Troubleshooting
### YouTube Downloading
**Q:** I get a "Permission denied (EACCES)" error in the console when running the `/yt` command
**A:** Make sure `src/plugins/yt-dlp/yt-dlp` is executable. You can do this on Linux like so:
```bash
chmod +x src/plugins/yt-dlp/yt-dlp
```
## About/License ## About/License
BSD-3-Clause - 2024 Lucas Gabriel (lucmsilva). BSD-3-Clause - 2024 Lucas Gabriel (lucmsilva).

9
docker-compose.yml Normal file
View file

@ -0,0 +1,9 @@
services:
kowalski:
build: .
container_name: kowalski
restart: unless-stopped
volumes:
- ./config.env:/usr/src/app/config.env:ro
environment:
- NODE_ENV=production

View file

@ -6,6 +6,12 @@ require('@dotenvx/dotenvx').config({ path: "config.env" });
require('./plugins/ytdlp-wrapper.js'); require('./plugins/ytdlp-wrapper.js');
// require('./plugins/termlogger.js'); // require('./plugins/termlogger.js');
// Ensures bot token is set, and not default value
if (!process.env.botToken || process.env.botToken === 'InsertYourBotTokenHere') {
console.error('Bot token is not set. Please set the bot token in the config.env file.')
process.exit(1)
}
const bot = new Telegraf(process.env.botToken); const bot = new Telegraf(process.env.botToken);
const maxRetries = process.env.maxRetries || 5; const maxRetries = process.env.maxRetries || 5;
let restartCount = 0; let restartCount = 0;

View file

@ -62,7 +62,7 @@ async function handleAdminCommand(ctx, action, successMessage, errorMessage) {
reply_to_message_id: ctx.message.message_id reply_to_message_id: ctx.message.message_id
}); });
} catch (error) { } catch (error) {
ctx.reply(errorMessage.replace('{error}', error.message), { ctx.reply(errorMessage.replace(/{error}/g, error.message), {
parse_mode: 'Markdown', parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id reply_to_message_id: ctx.message.message_id
}); });
@ -91,12 +91,12 @@ module.exports = (bot) => {
handleAdminCommand(ctx, async () => { handleAdminCommand(ctx, async () => {
try { try {
const commitHash = await getGitCommitHash(); const commitHash = await getGitCommitHash();
await ctx.reply(Strings.gitCurrentCommit.replace('{commitHash}', commitHash), { await ctx.reply(Strings.gitCurrentCommit.replace(/{commitHash}/g, commitHash), {
parse_mode: 'Markdown', parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id reply_to_message_id: ctx.message.message_id
}); });
} catch (error) { } catch (error) {
ctx.reply(Strings.gitErrRetrievingCommit.replace('{error}', error), { ctx.reply(Strings.gitErrRetrievingCommit.replace(/{error}/g, error), {
parse_mode: 'Markdown', parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id reply_to_message_id: ctx.message.message_id
}); });
@ -109,12 +109,12 @@ module.exports = (bot) => {
handleAdminCommand(ctx, async () => { handleAdminCommand(ctx, async () => {
try { try {
const result = await updateBot(); const result = await updateBot();
await ctx.reply(Strings.botUpdated.replace('{result}', result), { await ctx.reply(Strings.botUpdated.replace(/{result}/g, result), {
parse_mode: 'Markdown', parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id reply_to_message_id: ctx.message.message_id
}); });
} catch (error) { } catch (error) {
ctx.reply(Strings.errorUpdatingBot.replace('{error}', error), { ctx.reply(Strings.errorUpdatingBot.replace(/{error}/g, error), {
parse_mode: 'Markdown', parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id reply_to_message_id: ctx.message.message_id
}); });
@ -127,7 +127,7 @@ module.exports = (bot) => {
const botName = ctx.message.text.split(' ').slice(1).join(' '); const botName = ctx.message.text.split(' ').slice(1).join(' ');
handleAdminCommand(ctx, async () => { handleAdminCommand(ctx, async () => {
await ctx.telegram.setMyName(botName); await ctx.telegram.setMyName(botName);
}, Strings.botNameChanged.replace('{botName}', botName), Strings.botNameErr.replace('{error}', error)); }, Strings.botNameChanged.replace(/{botName}/g, botName), Strings.botNameErr.replace(/{error}/g, error));
}); });
bot.command('setbotdesc', spamwatchMiddleware, async (ctx) => { bot.command('setbotdesc', spamwatchMiddleware, async (ctx) => {
@ -135,7 +135,7 @@ module.exports = (bot) => {
const botDesc = ctx.message.text.split(' ').slice(1).join(' '); const botDesc = ctx.message.text.split(' ').slice(1).join(' ');
handleAdminCommand(ctx, async () => { handleAdminCommand(ctx, async () => {
await ctx.telegram.setMyDescription(botDesc); await ctx.telegram.setMyDescription(botDesc);
}, Strings.botDescChanged.replace('{botDesc}', botDesc), Strings.botDescErr.replace('{error}', error)); }, Strings.botDescChanged.replace(/{botDesc}/g, botDesc), Strings.botDescErr.replace(/{error}/g, error));
}); });
bot.command('botkickme', spamwatchMiddleware, async (ctx) => { bot.command('botkickme', spamwatchMiddleware, async (ctx) => {
@ -159,7 +159,7 @@ module.exports = (bot) => {
caption: botFile caption: botFile
}); });
} catch (error) { } catch (error) {
ctx.reply(Strings.unexpectedErr.replace('{error}', error.message), { ctx.reply(Strings.unexpectedErr.replace(/{error}/g, error.message), {
parse_mode: 'Markdown', parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id reply_to_message_id: ctx.message.message_id
}); });

View file

@ -6,8 +6,8 @@ async function sendHelpMessage(ctx, isEditing) {
const Strings = getStrings(ctx.from.language_code); const Strings = getStrings(ctx.from.language_code);
const botInfo = await ctx.telegram.getMe(); const botInfo = await ctx.telegram.getMe();
const helpText = Strings.botHelp const helpText = Strings.botHelp
.replace('{botName}', botInfo.first_name) .replace(/{botName}/g, botInfo.first_name)
.replace("{sourceLink}", process.env.botSource); .replace(/{sourceLink}/g, process.env.botSource);
const options = { const options = {
parse_mode: 'Markdown', parse_mode: 'Markdown',
disable_web_page_preview: true, disable_web_page_preview: true,
@ -35,7 +35,7 @@ module.exports = (bot) => {
bot.command("about", spamwatchMiddleware, async (ctx) => { bot.command("about", spamwatchMiddleware, async (ctx) => {
const Strings = getStrings(ctx.from.language_code); const Strings = getStrings(ctx.from.language_code);
const aboutMsg = Strings.botAbout.replace("{sourceLink}", `${process.env.botSource}`); const aboutMsg = Strings.botAbout.replace(/{sourceLink}/g, `${process.env.botSource}`);
ctx.reply(aboutMsg, { ctx.reply(aboutMsg, {
parse_mode: 'Markdown', parse_mode: 'Markdown',

View file

@ -6,7 +6,7 @@ module.exports = (bot) => {
bot.start(spamwatchMiddleware, async (ctx) => { bot.start(spamwatchMiddleware, async (ctx) => {
const Strings = getStrings(ctx.from.language_code); const Strings = getStrings(ctx.from.language_code);
const botInfo = await ctx.telegram.getMe(); const botInfo = await ctx.telegram.getMe();
const startMsg = Strings.botWelcome.replace('{botName}', botInfo.first_name); const startMsg = Strings.botWelcome.replace(/{botName}/g, botInfo.first_name);
ctx.reply(startMsg, { ctx.reply(startMsg, {
parse_mode: 'Markdown', parse_mode: 'Markdown',

View file

@ -99,6 +99,20 @@ module.exports = (bot) => {
getApproxSize(ytDlpPath, videoUrl), getApproxSize(ytDlpPath, videoUrl),
]); ]);
if (approxSizeInMB > 50) {
await ctx.telegram.editMessageText(
ctx.chat.id,
downloadingMessage.message_id,
null,
Strings.ytDownload.uploadLimit, {
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id,
},
);
return;
}
await ctx.telegram.editMessageText( await ctx.telegram.editMessageText(
ctx.chat.id, ctx.chat.id,
downloadingMessage.message_id, downloadingMessage.message_id,
@ -140,7 +154,7 @@ module.exports = (bot) => {
fs.unlinkSync(mp4File); fs.unlinkSync(mp4File);
} catch (error) { } catch (error) {
if (toString(error).includes("Request Entity Too Large")) { if (error.response.description.includes("Request Entity Too Large")) {
await ctx.telegram.editMessageText( await ctx.telegram.editMessageText(
ctx.chat.id, ctx.chat.id,
downloadingMessage.message_id, downloadingMessage.message_id,

View file

@ -1,5 +1,5 @@
{ {
"botWelcome": "*Hello! I am {botName}!*\nI was made with love by some nerds who really love programming!\n\n*Before using, you need to read the privacy policy (/privacy) to understand where your data goes when using this bot.*\n\nAlso, you can use /help to see the bot commands!", "botWelcome": "*Hello! I'm {botName}!*\nI was made with love by some nerds who really love programming!\n\n*By using {botName}, you affirm that you have read to and agree with the privacy policy (/privacy). This helps you understand where your data goes when using this bot.*\n\nAlso, you can use /help to see the bot commands!",
"botHelp": "*Hey, I'm {botName}, a simple bot made entirely from scratch in Telegraf and Node.js by some nerds who really love programming.*\n\nClick on the buttons below to see which commands you can use!\n", "botHelp": "*Hey, I'm {botName}, a simple bot made entirely from scratch in Telegraf and Node.js by some nerds who really love programming.*\n\nClick on the buttons below to see which commands you can use!\n",
"botPrivacy": "Check out [this link](https://blog.lucmsilva.com/posts/lynx-privacy-policy) to read the bot's privacy policy.", "botPrivacy": "Check out [this link](https://blog.lucmsilva.com/posts/lynx-privacy-policy) to read the bot's privacy policy.",
"botAbout": "*About the bot*\n\nThe bot base was originally created by [Lucas Gabriel (lucmsilva)](https://github.com/lucmsilva651), now maintained by several people.\n\nThe bot's purpose is to bring fun to your groups here on Telegram in a relaxed and simple way. The bot also features some very useful commands, which you can see using the help command (/help).\n\nSpecial thanks to @givfnz2 for his many contributions to the bot!\n\nSee the source code: [Click here to go to GitHub]({sourceLink})", "botAbout": "*About the bot*\n\nThe bot base was originally created by [Lucas Gabriel (lucmsilva)](https://github.com/lucmsilva651), now maintained by several people.\n\nThe bot's purpose is to bring fun to your groups here on Telegram in a relaxed and simple way. The bot also features some very useful commands, which you can see using the help command (/help).\n\nSpecial thanks to @givfnz2 for his many contributions to the bot!\n\nSee the source code: [Click here to go to GitHub]({sourceLink})",

View file

@ -1,5 +1,5 @@
{ {
"botWelcome": "*Olá! Eu sou o {botName}!*\n\n*Antes de usar, você precisa ler a política de privacidade (/privacy) para entender onde seus dados vão ao usar este bot.*\n\nAlém disso, você pode usar /help para ver os meus comandos!", "botWelcome": "*Olá! Eu sou o {botName}!*\n\n*Ao usar o {botName}, você afirma que leu e concorda com a política de privacidade (/privacy). Isso ajuda você a entender onde seus dados vão ao usar este bot.*\n\nAlém disso, você pode usar /help para ver os meus comandos!",
"botHelp": "*Oi, eu sou o {botName}, um bot simples feito do zero em Telegraf e Node.js por uns nerds que gostam de programação.*\n\nVeja o código fonte: [Clique aqui para ir ao GitHub]({sourceLink})\n\nClique nos botões abaixo para ver quais comandos você pode usar!\n", "botHelp": "*Oi, eu sou o {botName}, um bot simples feito do zero em Telegraf e Node.js por uns nerds que gostam de programação.*\n\nVeja o código fonte: [Clique aqui para ir ao GitHub]({sourceLink})\n\nClique nos botões abaixo para ver quais comandos você pode usar!\n",
"botPrivacy": "Acesse [este link](https://blog.lucmsilva.com/posts/lynx-privacy-policy) para ler a política de privacidade do bot.", "botPrivacy": "Acesse [este link](https://blog.lucmsilva.com/posts/lynx-privacy-policy) para ler a política de privacidade do bot.",
"botAbout": "*Sobre o bot*\n\nA base deste bot foi feita originalmente por [Lucas Gabriel (lucmsilva)](https://github.com/lucmsilva651), agora sendo mantido por várias pessoas.\n\nA intenção do bot é trazer diversão para os seus grupos aqui no Telegram de uma maneira bem descontraida e simples. O bot também conta com alguns comandos bem úteis, que você consegue ver com o comando de ajuda (/help).\n\nAgradecimento especial ao @givfnz2 pelas suas várias contribuições ao bot!\n\nVeja o código fonte: [Clique aqui para ir ao GitHub]({sourceLink})", "botAbout": "*Sobre o bot*\n\nA base deste bot foi feita originalmente por [Lucas Gabriel (lucmsilva)](https://github.com/lucmsilva651), agora sendo mantido por várias pessoas.\n\nA intenção do bot é trazer diversão para os seus grupos aqui no Telegram de uma maneira bem descontraida e simples. O bot também conta com alguns comandos bem úteis, que você consegue ver com o comando de ajuda (/help).\n\nAgradecimento especial ao @givfnz2 pelas suas várias contribuições ao bot!\n\nVeja o código fonte: [Clique aqui para ir ao GitHub]({sourceLink})",