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.lock
bun.lockb bun.lockb
tmp/ tmp/
plugins/
# Executables # Executables
*.exe *.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.com>
A Bunch of Computer Nerds <abocn@protonmail.me> 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> 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> Giovani Finazzi <53719063+GiovaniFZ@users.noreply.github.com>
GiovaniFZ <giovanifinazzi@gmail.com> GiovaniFZ <giovanifinazzi@gmail.com>
Lucas Gabriel <90426410+lucmsilva651@users.noreply.github.com> Lucas Gabriel <90426410+lucmsilva651@users.noreply.github.com>
Lucas Gabriel <lucmsilva651@gmail.com> Lucas Gabriel <lucmsilva651@gmail.com>
lucmsilva651 <lucmsilva651@gmail.com>
Luquinhas <ABOCN@users.noreply.github.com> Luquinhas <ABOCN@users.noreply.github.com>
Luquinhas <lucmsilva651@gmail.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> 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) # Kowalski (Node.js Telegram Bot)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) [![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) ![GitHub License](https://img.shields.io/github/license/ABOCN/TelegramBot)
Kowalski is a a simple Telegram bot made in Node.js. 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 ## Self-host requirements
- Node.js 20 or newer (you can also use Bun)
- A Telegram bot (create one at [@BotFather](https://t.me/botfather)) - Node.js 20 or newer (you can also use [Bun](https://bun.sh))
- Latest version of Node.js - A Telegram bot (create one at [@BotFather](https://t.me/botfather))
- 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
First, clone the repo with Git: First, clone the repo with Git:
```
```bash
git clone https://github.com/ABOCN/TelegramBot git clone https://github.com/ABOCN/TelegramBot
``` ```
And now, init the submodules with these commands (this is very important): And now, init the submodules with these commands (this is very important):
```
```bash
cd TelegramBot cd TelegramBot
git submodule update --init --recursive 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). 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``. 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 ## config.env Functions
- **botSource**: Put the link to your bot source code. - **botSource**: Put the link to your bot source code.
- **botToken**: Put your bot token that you created at [@BotFather](https://t.me/botfather). - **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. - **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. - **weatherKey**: Weather.com API key, used for the `/weather` command.
## Note ## 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! - 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

@ -196,7 +196,11 @@ function extractMetaData(meta, key) {
module.exports = (bot) => { module.exports = (bot) => {
bot.command(['d', 'device'], spamwatchMiddleware, async (ctx) => { bot.command(['d', 'device'], spamwatchMiddleware, async (ctx) => {
const userId = ctx.from.id; 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(" "); const phone = ctx.message.text.split(" ").slice(1).join(" ");
if (!phone) { if (!phone) {

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

@ -41,7 +41,12 @@ const downloadFromYoutube = async (command, args) => {
}; };
const getApproxSize = async (command, videoUrl) => { 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 { try {
const { stdout } = await downloadFromYoutube(command, args); const { stdout } = await downloadFromYoutube(command, args);
const sizeInBytes = parseInt(stdout.trim(), 10); const sizeInBytes = parseInt(stdout.trim(), 10);
@ -94,14 +99,28 @@ 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,
null, null,
Strings.ytDownload.downloadingVid, { Strings.ytDownload.downloadingVid, {
parse_mode: 'Markdown', parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id, reply_to_message_id: ctx.message.message_id,
}, },
); );
const dlpArgs = [videoUrl, ...cmdArgs.split(' '), mp4File]; const dlpArgs = [videoUrl, ...cmdArgs.split(' '), mp4File];
@ -112,12 +131,12 @@ module.exports = (bot) => {
downloadingMessage.message_id, downloadingMessage.message_id,
null, null,
Strings.ytDownload.uploadingVid, { Strings.ytDownload.uploadingVid, {
parse_mode: 'Markdown', parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id, reply_to_message_id: ctx.message.message_id,
}, },
); );
if(fs.existsSync(tempMp4File)){ if (fs.existsSync(tempMp4File)) {
await downloadFromYoutube(ffmpegPath, ffmpegArgs); await downloadFromYoutube(ffmpegPath, ffmpegArgs);
} }
@ -126,23 +145,24 @@ module.exports = (bot) => {
try { try {
await ctx.replyWithVideo({ await ctx.replyWithVideo({
source: mp4File }, { source: mp4File
}, {
caption: message, caption: message,
parse_mode: 'Markdown', parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id, reply_to_message_id: ctx.message.message_id,
}); });
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,
null, null,
Strings.ytDownload.uploadLimit, { Strings.ytDownload.uploadLimit, {
parse_mode: 'Markdown', parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id, reply_to_message_id: ctx.message.message_id,
}, },
); );
} else { } else {
const errMsg = Strings.ytDownload.uploadErr.replace("{error}", error) const errMsg = Strings.ytDownload.uploadErr.replace("{error}", error)
@ -151,9 +171,9 @@ module.exports = (bot) => {
downloadingMessage.message_id, downloadingMessage.message_id,
null, null,
errMsg, { errMsg, {
parse_mode: 'Markdown', parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id, reply_to_message_id: ctx.message.message_id,
}, },
); );
}; };
@ -171,22 +191,21 @@ module.exports = (bot) => {
downloadingMessage.message_id, downloadingMessage.message_id,
null, null,
Strings.ytDownload.libNotFound, { Strings.ytDownload.libNotFound, {
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) {
console.error(error);
const errMsg = Strings.ytDownload.uploadErr.replace("{error}", error) const errMsg = Strings.ytDownload.uploadErr.replace("{error}", error)
await ctx.telegram.editMessageText( await ctx.telegram.editMessageText(
ctx.chat.id, ctx.chat.id,
downloadingMessage.message_id, downloadingMessage.message_id,
null, null,
errMsg, { errMsg, {
parse_mode: 'Markdown', parse_mode: 'Markdown',
reply_to_message_id: ctx.message.message_id, 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", "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})",