Merge branch 'main' into main

This commit is contained in:
Aidan 2025-04-14 21:36:43 -04:00 committed by GitHub
commit 8788774492
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 220 additions and 52 deletions

8
.dockerignore Normal file
View file

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

37
.github/workflows/update-authors.yml vendored Normal file
View file

@ -0,0 +1,37 @@
name: Update AUTHORS File
on:
push:
branches:
- main
jobs:
update-authors:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Generate AUTHORS file (Name <email> format)
run: |
git log --format='%aN <%aE>' | sort -u > AUTHORS
- name: Check if AUTHORS file changed
run: |
if git diff --quiet AUTHORS; then
echo "No changes in AUTHORS file."
exit 0
fi
- name: Commit and push changes
uses: EndBug/add-and-commit@v9.1.4
with:
push: true
add: "AUTHORS"
default_author: github_actions
message: "Update AUTHORS file automatically"

1
.gitignore vendored
View file

@ -139,7 +139,6 @@ package-lock.json
bun.lock
bun.lockb
tmp/
plugins/
# Executables
*.exe

10
AUTHORS
View file

@ -1,14 +1,16 @@
A Bunch of Computer Nerds <ABOCN@users.noreply.github.com>
A Bunch of Computer Nerds <abocn@protonmail.com>
A Bunch of Computer Nerds <abocn@protonmail.me>
A Bunch of Computer Nerds <ABOCN@users.noreply.github.com>
Aidan <aidan@p0ntus.com>
DaviDev <97841570+DaviisDev@users.noreply.github.com>
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fossabot <badges@fossa.io>
Giovani Finazzi <53719063+GiovaniFZ@users.noreply.github.com>
GiovaniFZ <giovanifinazzi@gmail.com>
Lucas Gabriel <90426410+lucmsilva651@users.noreply.github.com>
Lucas Gabriel <lucmsilva651@gmail.com>
lucmsilva651 <lucmsilva651@gmail.com>
Luquinhas <ABOCN@users.noreply.github.com>
Luquinhas <lucmsilva651@gmail.com>
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fossabot <badges@fossa.io>
github-actions <41898282+github-actions[bot]@users.noreply.github.com>
lucmsilva651 <lucmsilva651@gmail.com>
mthlma <156229140+mthlma@users.noreply.github.com>

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

@ -1,32 +1,84 @@
# Kowalski (Node.js Telegram Bot)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md)
![GitHub License](https://img.shields.io/github/license/ABOCN/TelegramBot)
Kowalski is a a simple Telegram bot made in Node.js.
- You can find Kowalski at [@KowalskiNodeBot](https://t.me/KowalskiNodeBot) on Telegram.
- You can find Kowalski at [@KowalskiNodeBot](https://t.me/KowalskiNodeBot) on Telegram.
## Self-host requirements
- Node.js 20 or newer (you can also use Bun)
- A Telegram bot (create one at [@BotFather](https://t.me/botfather))
- Latest version of Node.js
- FFmpeg (only for the /yt command)
- Node.js 20 or newer (you can also use [Bun](https://bun.sh))
- A Telegram bot (create one at [@BotFather](https://t.me/botfather))
- FFmpeg (only for the `/yt` command)
- Docker and Docker Compose (only required for Docker setup)
## Run it yourself, develop or contribute with Kowalski
First, clone the repo with Git:
```
```bash
git clone https://github.com/ABOCN/TelegramBot
```
And now, init the submodules with these commands (this is very important):
```
```bash
cd TelegramBot
git submodule update --init --recursive
```
Next, inside the repository directory, create a `config.env` file with some content, which you can see the [example .env file](config.env.example) to fill info with. To see the meaning of each one, see [the Functions section](#configenv-functions).
After editing the file, save all changes and run the bot with ``npm start``.
- To deal with dependencies, just run ``npm install`` or ``npm i`` at any moment to install all of them.
> [!TIP]
> 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
- **botSource**: Put the link to your bot source code.
- **botToken**: Put your bot token that you created at [@BotFather](https://t.me/botfather).
- **botAdmins**: Put the ID of the people responsible for managing the bot. They can use some administrative + exclusive commands on any group.
@ -34,7 +86,21 @@ After editing the file, save all changes and run the bot with ``npm start``.
- **weatherKey**: Weather.com API key, used for the `/weather` command.
## Note
- 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
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/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 maxRetries = process.env.maxRetries || 5;
let restartCount = 0;

View file

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

View file

@ -196,7 +196,11 @@ function extractMetaData(meta, key) {
module.exports = (bot) => {
bot.command(['d', 'device'], spamwatchMiddleware, async (ctx) => {
const userId = ctx.from.id;
const userName = ctx.from.first_name;
let userName = String(ctx.from.first_name);
if(userName.includes("<") && userName.includes(">")) {
userName = userName.replace("<", "").replace(">", "");
}
const phone = ctx.message.text.split(" ").slice(1).join(" ");
if (!phone) {

View file

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

View file

@ -6,7 +6,7 @@ module.exports = (bot) => {
bot.start(spamwatchMiddleware, async (ctx) => {
const Strings = getStrings(ctx.from.language_code);
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, {
parse_mode: 'Markdown',

View file

@ -41,7 +41,12 @@ const downloadFromYoutube = async (command, args) => {
};
const getApproxSize = async (command, videoUrl) => {
const args = [videoUrl, '--compat-opt', 'manifest-filesize-approx', '-O', 'filesize_approx'];
let args = [];
if (fs.existsSync(path.resolve(__dirname, "../props/cookies.txt"))) {
args = [videoUrl, '--compat-opt', 'manifest-filesize-approx', '-O', 'filesize_approx', '--cookies', path.resolve(__dirname, "../props/cookies.txt")];
} else {
args = [videoUrl, '--compat-opt', 'manifest-filesize-approx', '-O', 'filesize_approx'];
}
try {
const { stdout } = await downloadFromYoutube(command, args);
const sizeInBytes = parseInt(stdout.trim(), 10);
@ -94,14 +99,28 @@ module.exports = (bot) => {
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(
ctx.chat.id,
downloadingMessage.message_id,
null,
Strings.ytDownload.downloadingVid, {
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id,
},
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id,
},
);
const dlpArgs = [videoUrl, ...cmdArgs.split(' '), mp4File];
@ -112,12 +131,12 @@ module.exports = (bot) => {
downloadingMessage.message_id,
null,
Strings.ytDownload.uploadingVid, {
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id,
},
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id,
},
);
if(fs.existsSync(tempMp4File)){
if (fs.existsSync(tempMp4File)) {
await downloadFromYoutube(ffmpegPath, ffmpegArgs);
}
@ -126,23 +145,24 @@ module.exports = (bot) => {
try {
await ctx.replyWithVideo({
source: mp4File }, {
source: mp4File
}, {
caption: message,
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id,
});
fs.unlinkSync(mp4File);
} catch (error) {
if (toString(error).includes("Request Entity Too Large")) {
if (error.response.description.includes("Request Entity Too Large")) {
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,
},
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id,
},
);
} else {
const errMsg = Strings.ytDownload.uploadErr.replace("{error}", error)
@ -151,9 +171,9 @@ module.exports = (bot) => {
downloadingMessage.message_id,
null,
errMsg, {
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id,
},
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id,
},
);
};
@ -171,22 +191,21 @@ module.exports = (bot) => {
downloadingMessage.message_id,
null,
Strings.ytDownload.libNotFound, {
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id,
},
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id,
},
);
}
} catch (error) {
console.error(error);
const errMsg = Strings.ytDownload.uploadErr.replace("{error}", error)
await ctx.telegram.editMessageText(
ctx.chat.id,
downloadingMessage.message_id,
null,
errMsg, {
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id,
},
parse_mode: 'Markdown',
reply_to_message_id: ctx.message.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",
"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})",

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",
"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})",