├── .env.sample ├── .eslintrc.js ├── .github └── FUNDING.yml ├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── app.js ├── config.js ├── helpers ├── admins.js ├── bot.js ├── db.js ├── goldenBorodutchSubCount.js ├── help.js ├── isRuChat.js ├── language.js ├── limit.js ├── localizations.js ├── lock.js ├── requests.js ├── strings.js ├── time.js └── votekickWord.js ├── localize.js ├── models ├── chat.js ├── index.js ├── request.js └── user.js ├── nixpacks.toml ├── package.json ├── scripts ├── download.js └── upload.js └── yarn.lock /.env.sample: -------------------------------------------------------------------------------- 1 | TELEGRAM_API_KEY=123456789 2 | MONGO_DB_URL=mongodb://localhost:27017/banofbot 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "airbnb", 3 | "plugins": [ 4 | "react", 5 | "jsx-a11y", 6 | "import", 7 | "promise", 8 | ], 9 | "rules": { 10 | "require-jsdoc": 2, 11 | "promise/catch-or-return": 2, 12 | "no-underscore-dangle": [2, { "allow": ["_id"] }], /** Mongoose uses _id, we can't have control over it */ 13 | "no-use-before-define": 0, 14 | "no-console": ["error", { allow: ["info"] }], 15 | "global-require": 0, 16 | "no-param-reassign": 0, 17 | "no-restricted-syntax": 0, 18 | } 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: backmeupplz 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.17.0 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Nikita Kolmogorov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [@banofbot](https://telegram.me/banofbot) and [@silent_banofbot](https://telegram.me/banofbot) code 2 | This repository contains the code for the democracy bots I've built. Readme is still in work, any contributions are welcome. 3 | 4 | # Installation and local launch 5 | 1. Clone this repo: `git clone https://github.com/backmeupplz/banofbot` 6 | 2. Launch the [mongo database](https://www.mongodb.com/) locally 7 | 3. Create `.env` file with `TELEGRAM_API_KEY` and `MONGO_DB_URL` 8 | 4. Run `npm i` in the root folder 9 | 5. Run `npm run start` 10 | 11 | And you should be good to go! Feel free to fork and submit pull requests. Thanks! 12 | 13 | # Continuous integration 14 | Any commit pushed to master gets deployed to both @banofbot and @silent_banofbot via [CI Ninja](https://github.com/backmeupplz/ci-ninja). 15 | 16 | # License 17 | MIT — use for any purpose. Would be great if you could leave a note about the original developers. Thanks! 18 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main app logic 3 | * 4 | * @module app 5 | * @license MIT 6 | */ 7 | 8 | /** Dependencies */ 9 | const path = require('path') 10 | require('dotenv').config({ 11 | path: path.join(__dirname, '/.env'), 12 | }) 13 | const mongoose = require('mongoose') 14 | const bot = require('./helpers/bot') 15 | const config = require('./config') 16 | const db = require('./helpers/db') 17 | const language = require('./helpers/language') 18 | const help = require('./helpers/help') 19 | const lock = require('./helpers/lock') 20 | const requests = require('./helpers/requests') 21 | const admins = require('./helpers/admins') 22 | const limit = require('./helpers/limit') 23 | const time = require('./helpers/time') 24 | const votekickWord = require('./helpers/votekickWord') 25 | 26 | global.Promise = require('bluebird') 27 | 28 | global.Promise.config({ cancellation: true }) 29 | 30 | /** Setup mongoose */ 31 | mongoose.Promise = require('bluebird') 32 | 33 | mongoose.connect(config.database, { 34 | socketTimeoutMS: 0, 35 | connectTimeoutMS: 0, 36 | useUnifiedTopology: true, 37 | useNewUrlParser: true, 38 | }) 39 | mongoose.connection.on('disconnected', () => { 40 | mongoose.connect(config.database, { 41 | socketTimeoutMS: 0, 42 | connectTimeoutMS: 0, 43 | useUnifiedTopology: true, 44 | useNewUrlParser: true, 45 | }) 46 | }) 47 | mongoose.set('useCreateIndex', true) 48 | mongoose.set('useFindAndModify', false) 49 | 50 | let timeoutOver = false 51 | setTimeout(() => { 52 | timeoutOver = true 53 | }, 5000) 54 | 55 | bot.on('message', (msg) => { 56 | if (!timeoutOver) { 57 | return 58 | } 59 | handle(msg) 60 | }) 61 | 62 | /** 63 | * Used to handle incoming message 64 | * @param {Telegram:Message} msg Message received 65 | */ 66 | function handle(msg) { 67 | if (!msg) { 68 | return 69 | } 70 | if (msg.text && msg.text.includes('@') && !msg.text.includes('banofbot')) { 71 | return 72 | } 73 | const isPrivateChat = 74 | msg.chat.type === 'private' || msg.chat.type === 'channel' 75 | const isCommand = 76 | msg.text && 77 | msg.entities && 78 | msg.entities[0] && 79 | msg.entities[0].type === 'bot_command' 80 | const isEntry = 81 | (msg.new_chat_participant && 82 | msg.new_chat_participant.username && 83 | msg.new_chat_participant.username === 'banofbot') || 84 | msg.group_chat_created 85 | db.findChat(msg.chat) 86 | .then((chat) => { 87 | let isReply = 88 | msg.reply_to_message && 89 | msg.text && 90 | (msg.text.includes('banofbot') || 91 | msg.text.includes('@ban') || 92 | msg.text.includes('voteban') || 93 | msg.text.includes('Voteban') || 94 | msg.text.includes('/spam') || 95 | (chat.votekickWord && 96 | chat.votekickWord.split(', ').reduce((p, c) => { 97 | return ( 98 | p || 99 | new RegExp( 100 | `(?<=[\\s,.:;"']|^)${c}(?=[\\s,.:;"']|$)`, 101 | 'gum' 102 | ).test(msg.text) 103 | ) 104 | }, false))) 105 | if ( 106 | msg.reply_to_message && 107 | msg.sticker && 108 | msg.sticker.file_id === 'CAADAQADyQIAAgdEiQTkPSm3CRyNIQI' 109 | ) { 110 | isReply = true 111 | } 112 | if (isCommand) { 113 | if (isPrivateChat || !chat.admin_locked) { 114 | if (msg.text.includes('start')) { 115 | language.sendLanguage(bot, chat, false) 116 | } else if (msg.text.includes('help')) { 117 | help.sendHelp(bot, chat) 118 | } else if (msg.text.includes('language')) { 119 | language.sendLanguage(bot, chat, true) 120 | } else if (msg.text.includes('limit')) { 121 | if (!isPrivateChat) { 122 | limit.sendLimit(bot, chat, msg.text) 123 | } 124 | } else if (msg.text.includes('time')) { 125 | if (!isPrivateChat) { 126 | time.sendTime(bot, chat) 127 | } 128 | } else if (msg.text.includes('lock')) { 129 | if (!isPrivateChat) { 130 | lock.toggle(bot, chat) 131 | } 132 | } else if (msg.text.includes('filterNewcomers')) { 133 | if (!isPrivateChat) { 134 | bot.sendMessage(chat.id, 'Please, use @shieldy_bot instead.') 135 | } 136 | } else if (msg.text.includes('/banme')) { 137 | if (!isPrivateChat) { 138 | bot.kickChatMember(msg.chat.id, msg.from.id, { 139 | until_date: Math.floor(Date.now() / 1000) + 60, 140 | }) 141 | } 142 | } else if (msg.text.includes('/votekickWord')) { 143 | if (!isPrivateChat) { 144 | votekickWord.check(bot, chat, msg.text) 145 | } 146 | } 147 | } else { 148 | admins 149 | .isAdmin(bot, chat.id, msg.from.id) 150 | .then((isAdmin) => { 151 | if (msg.text.includes('start')) { 152 | if (!isAdmin) return deleteMessage(msg.chat.id, msg.message_id) 153 | language.sendLanguage(bot, chat, false) 154 | } else if (msg.text.includes('help')) { 155 | if (!isAdmin) return deleteMessage(msg.chat.id, msg.message_id) 156 | help.sendHelp(bot, chat) 157 | } else if (msg.text.includes('language')) { 158 | if (!isAdmin) return deleteMessage(msg.chat.id, msg.message_id) 159 | language.sendLanguage(bot, chat, true) 160 | } else if (msg.text.includes('limit')) { 161 | if (!isPrivateChat) { 162 | if (!isAdmin) 163 | return deleteMessage(msg.chat.id, msg.message_id) 164 | limit.sendLimit(bot, chat, msg.text) 165 | } 166 | } else if (msg.text.includes('time')) { 167 | if (!isPrivateChat) { 168 | if (!isAdmin) 169 | return deleteMessage(msg.chat.id, msg.message_id) 170 | time.sendTime(bot, chat) 171 | } 172 | } else if (msg.text.includes('lock')) { 173 | if (!isAdmin) return deleteMessage(msg.chat.id, msg.message_id) 174 | if (!isPrivateChat) { 175 | lock.toggle(bot, chat) 176 | } 177 | } else if (msg.text.includes('filterNewcomers')) { 178 | if (!isAdmin) return deleteMessage(msg.chat.id, msg.message_id) 179 | bot.sendMessage(chat.id, 'Please, use @shieldy_bot instead.') 180 | } else if (msg.text.includes('/banme')) { 181 | if (!isPrivateChat) { 182 | bot.kickChatMember(msg.chat.id, msg.from.id, { 183 | until_date: Math.floor(Date.now() / 1000) + 60, 184 | }) 185 | } 186 | } else if (msg.text.includes('/votekickWord')) { 187 | if (!isAdmin) return deleteMessage(msg.chat.id, msg.message_id) 188 | if (!isPrivateChat) { 189 | votekickWord.check(bot, chat, msg.text) 190 | } 191 | } 192 | }) 193 | .catch(/** todo: handle error */) 194 | } 195 | } else if (isEntry) { 196 | language.sendLanguage(bot, chat, false) 197 | } else if (isReply) { 198 | try { 199 | requests.startRequest(bot, msg) 200 | } catch (err) { 201 | console.error(err) 202 | // Do nothing 203 | } 204 | } 205 | }) 206 | .catch(/** todo: handle error */) 207 | } 208 | 209 | bot.on('callback_query', (msg) => { 210 | const options = msg.data.split('~') 211 | const inline = options[0] 212 | if (inline === 'li') { 213 | language.setLanguage(bot, msg) 214 | } else if (inline === 'vi') { 215 | try { 216 | requests.voteQuery(bot, msg) 217 | } catch (err) { 218 | // Do nothing 219 | } 220 | } else if (inline === 'lti') { 221 | limit.setLimit(bot, msg) 222 | } else if (inline === 'tlti') { 223 | time.setTime(bot, msg) 224 | } 225 | }) 226 | 227 | console.info('Bot is up and running') 228 | 229 | function getUsername(member) { 230 | return `${ 231 | member.user.username 232 | ? `@${member.user.username}` 233 | : `${member.user.first_name}${ 234 | member.user.last_name ? ` ${member.user.last_name}` : '' 235 | }` 236 | }` 237 | } 238 | 239 | function deleteMessage(c, m) { 240 | try { 241 | bot.deleteMessage(c, m) 242 | } catch (err) { 243 | // Do nothing 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration file 3 | * 4 | * @module config 5 | * @license MIT 6 | */ 7 | 8 | module.exports = { 9 | token: process.env.TELEGRAM_API_KEY, 10 | database: process.env.MONGO_DB_URL, 11 | ssl_certificate_path: process.env.SSL_CERTIFICATE_PATH, 12 | ssl_key_path: process.env.SSL_KEY_PATH, 13 | should_use_webhooks: process.env.USE_WEBHOOKS || false, 14 | webhook_callback_url: process.env.WEBHOOK_CALLBACK_URL, 15 | botan: process.env.BOTAN_API_KEY 16 | }; 17 | -------------------------------------------------------------------------------- /helpers/admins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to get chat admins 3 | * 4 | * @module admins 5 | * @license MIT 6 | */ 7 | 8 | /** 9 | * Function to get chat admin ids 10 | * @param {Telegram:Bot} bot Bot that should perform the action 11 | * @param {Telegram:ChatId} chatId Id of the chat 12 | * @return {Promise([Telegram:UserId])} Ids of admins 13 | */ 14 | function getAdminIds(bot, chatId) { 15 | return new Promise((resolve, reject) => { 16 | bot.getChatAdministrators(chatId) 17 | .then((data) => { 18 | resolve(data.map(v => v.user.id)); 19 | }) 20 | .catch(err => reject(err)); 21 | }); 22 | } 23 | 24 | /** 25 | * Function to check if user id is admin 26 | * @param {Telegram:Bot} bot Bot that should perform the action 27 | * @param {Telegram:ChatId} chatId Id of the chat to get admins 28 | * @param {Telegram:UserId} userId Id of the user to check if admin 29 | * @return {Promise(Boolean)} Promise that's called on completion 30 | */ 31 | function isAdmin(bot, chatId, userId) { 32 | return new Promise((resolve, reject) => { 33 | getAdminIds(bot, chatId) 34 | .then((ids) => { 35 | resolve(ids.includes(userId)); 36 | }) 37 | .catch(err => reject(err)); 38 | }); 39 | } 40 | 41 | /** 42 | * Function to check if the bot is admin in the cat 43 | * @param {Telegram:Bot} bot Bot that needs to check that 44 | * @param {Telegram:ChatId} chatId Id of the chat that's checked 45 | * @return {Promise(Boolean)} Promise with true if bot is admin at the chat 46 | */ 47 | function isBotAdmin(bot, chatId) { 48 | return bot.getMe() 49 | .then(me => 50 | getAdminIds(bot, chatId) 51 | .then(admins => admins.includes(me.id))); 52 | } 53 | 54 | /** Exports */ 55 | module.exports = { 56 | getAdminIds, 57 | isAdmin, 58 | isBotAdmin, 59 | }; 60 | -------------------------------------------------------------------------------- /helpers/bot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Telegam bot 3 | * 4 | * @module bot 5 | * @license MIT 6 | */ 7 | 8 | /** Dependencies */ 9 | const Telegram = require('node-telegram-bot-api'); 10 | const config = require('../config'); 11 | const path = require('path'); 12 | 13 | let bot; 14 | if (config.should_use_webhooks) { 15 | const options = { 16 | webHook: { 17 | port: 5000, 18 | key: path.join(config.ssl_key_path), 19 | cert: path.join(config.ssl_certificate_path), 20 | }, 21 | }; 22 | 23 | bot = new Telegram(config.token, options); 24 | bot.setWebHook( 25 | `${config.webhook_callback_url}${config.token}`, 26 | path.join(config.ssl_certificate_path)) 27 | .then(() => { console.info('Telegram webhook is active'); }) 28 | .catch(/** todo: handle error */); 29 | } else { 30 | bot = new Telegram(config.token, { 31 | polling: true, 32 | }); 33 | console.info('Telegram is using polling'); 34 | } 35 | 36 | bot.on('polling_error', () => { 37 | console.error('Polling error'); 38 | }); 39 | 40 | /** Exports */ 41 | module.exports = bot; 42 | -------------------------------------------------------------------------------- /helpers/db.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module db 3 | * @license MIT 4 | */ 5 | 6 | /** Get schemas **/ 7 | const { 8 | Chat, 9 | User, 10 | Request, 11 | } = require('../models'); 12 | 13 | /** 14 | * Function to get chat, creates if none exists yet 15 | * @param {Telegram:Chat} chat Chat object that was passed from Telegram 16 | * @return {Promise(Mongoose:Chat)} Chat that was created by mongoose 17 | */ 18 | function findChat(chat) { 19 | return Chat.findOne({ id: chat.id }) 20 | .then((dbchat) => { 21 | if (dbchat) { 22 | return dbchat; 23 | } 24 | return new Chat(chat).save(); 25 | }); 26 | } 27 | 28 | function findChatsWithNewcomers() { 29 | return Chat.find({ 'newcomers.0': { '$exists': true } }); 30 | } 31 | 32 | /** 33 | * Function to get user, creates if none exists yet 34 | * @param {Telegram:User} user User object that was passed from Telegram 35 | * @return {Promise(Mongoose:User)} User that was created by mongoose 36 | */ 37 | function findUser(user) { 38 | return User.findOne({ id: user.id }) 39 | .then((dbuser) => { 40 | if (dbuser) { 41 | return dbuser; 42 | } 43 | return new User(user).save(); 44 | }); 45 | } 46 | 47 | /** 48 | * Function to get request from db 49 | * @param {Mongoose:ObjectId} id Id of the request 50 | * @return {Promise(Mongoose:Request)} Found request 51 | */ 52 | function findRequest(id) { 53 | return Request.findById(id); 54 | } 55 | 56 | /** 57 | * Function to create a request 58 | * @param {Mongoose:Request} request Request object without _id 59 | * @return {Promise(Mongoose:Request)} Created request 60 | */ 61 | function createRequest(request) { 62 | const req = new Request(request); 63 | return req.save(); 64 | } 65 | 66 | /** Exports */ 67 | module.exports = { 68 | findChat, 69 | findUser, 70 | findRequest, 71 | createRequest, 72 | findChatsWithNewcomers, 73 | }; 74 | -------------------------------------------------------------------------------- /helpers/goldenBorodutchSubCount.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | 3 | let over10000 = true 4 | 5 | function isOver10000() { 6 | return false 7 | // return over10000 8 | } 9 | 10 | setInterval(async () => { 11 | try { 12 | const response = (await axios('https://stats.borodutch.com/stats')).data 13 | over10000 = response.goldenBorodutch.subCount > 10000 14 | } catch { 15 | // Do nothing 16 | } 17 | }, 60 * 1000) 18 | 19 | module.exports = { isOver10000 } 20 | -------------------------------------------------------------------------------- /helpers/help.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to send help message 3 | * 4 | * @module help 5 | * @license MIT 6 | */ 7 | 8 | /** 9 | * Sends help message to specified chat 10 | * @param {Telegam:Bot} bot Bot that should send help 11 | * @param {Mongoose:Chat} chat Chat where to send help 12 | */ 13 | function sendHelp(bot, chat) { 14 | const strings = require('./strings')() 15 | 16 | const privateText = 17 | chat.type === 'private' || chat.type === 'channel' 18 | ? 'helpPrivate' 19 | : 'helpPublic' 20 | 21 | const text = strings.translate(privateText, chat.language) 22 | bot.sendMessage(chat.id, text, { 23 | parse_mode: 'Markdown', 24 | disable_web_page_preview: true, 25 | }) 26 | } 27 | 28 | /** Exports */ 29 | module.exports = { 30 | sendHelp, 31 | } 32 | -------------------------------------------------------------------------------- /helpers/isRuChat.js: -------------------------------------------------------------------------------- 1 | function isRuChat(chat) { 2 | return chat.language === 'ru' 3 | } 4 | 5 | module.exports = { 6 | isRuChat, 7 | } 8 | -------------------------------------------------------------------------------- /helpers/language.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | const db = require('./db') 3 | const help = require('./help') 4 | 5 | /** 6 | * Sends language message to specified chat 7 | * @param {Telegam:Bot} bot Bot that should send message 8 | * @param {Mongoose:Chat} chat Chat where to send message 9 | * @param {Boolean} isCommand Whether action was triggered by /language or start of the bot 10 | */ 11 | function sendLanguage(bot, chat, isCommand) { 12 | const strings = require('./strings')() 13 | 14 | const text = strings.translate('selectLanguage', chat.language) 15 | const options = { 16 | parse_mode: 'Markdown', 17 | reply_markup: { inline_keyboard: languageKeyboard(isCommand) }, 18 | } 19 | options.reply_markup = JSON.stringify(options.reply_markup) 20 | bot.sendMessage(chat.id, text, options) 21 | } 22 | 23 | /** 24 | * Called when inline button with language is touched 25 | * @param {Telegram:Bot} bot Bot that should respond 26 | * @param {Telegram:Message} msg Message of inline button that was touched 27 | */ 28 | function setLanguage(bot, msg) { 29 | const options = msg.data.split('~') 30 | const isCommand = parseInt(options[1], 10) === 1 31 | const code = options[2] 32 | 33 | db.findChat(msg.message.chat) 34 | .then((chat) => { 35 | chat.language = code 36 | return chat.save().then((dbchat) => { 37 | updateMessagewithSuccess(bot, msg.message, dbchat) 38 | if (!isCommand) { 39 | help.sendHelp(bot, dbchat) 40 | } 41 | }) 42 | }) 43 | .catch((err) => updateMessagewithError(bot, msg.message, err)) 44 | } 45 | 46 | /** 47 | * Updates passed message with error statement 48 | * @param {Telegram:Bot} bot Bot that should update the message 49 | * @param {Telegram:Message} msg Message to be updated 50 | * @param {Error} error Erorr to be displayed 51 | */ 52 | function updateMessagewithError(bot, msg, error) { 53 | bot.editMessageText(`❗️ _${error.message}_`, { 54 | chat_id: msg.chat.id, 55 | message_id: msg.message_id, 56 | parse_mode: 'Markdown', 57 | }) 58 | } 59 | 60 | /** 61 | * Updates passed message with success statement 62 | * @param {Telegram:Bot} bot Bot that should update the message 63 | * @param {Telegram:Message} msg Message to be updated 64 | * @param {Mongoose:Chat} chat Chat that had language updated 65 | */ 66 | function updateMessagewithSuccess(bot, msg, chat) { 67 | const strings = require('./strings')() 68 | 69 | bot.editMessageText( 70 | strings.translate('languageSelectedBanofbot', chat.language), 71 | { 72 | chat_id: msg.chat.id, 73 | message_id: msg.message_id, 74 | parse_mode: 'Markdown', 75 | } 76 | ) 77 | } 78 | 79 | /** 80 | * Returns an inline keyboard with all available languages 81 | * @param {Boolean} isCommand Whether action was triggered by /language or start of the bot 82 | * @return {Telegram:Inline} Inline keyboard with all available languages 83 | */ 84 | function languageKeyboard(isCommand) { 85 | const list = languages() 86 | const keyboard = Object.keys(list).map((key) => { 87 | const code = list[key] 88 | return [ 89 | { 90 | text: key, 91 | callback_data: `li~${isCommand ? 1 : 0}~${code}`, 92 | }, 93 | ] 94 | }) 95 | return keyboard 96 | } 97 | 98 | /** 99 | * Returns a list of supported languages 100 | * @return {[String]]} List of the supported languages 101 | */ 102 | function languages() { 103 | return { 104 | Russian: 'ru', 105 | English: 'en', 106 | Ukrainian: 'uk', 107 | Uzbek: 'uz', 108 | Kazakh: 'kz', 109 | Português: 'pt', 110 | Turkish: 'tr', 111 | Azerbaijani: 'az', 112 | German: 'de', 113 | French: 'fr', 114 | } 115 | } 116 | 117 | // Exports 118 | module.exports = { 119 | sendLanguage, 120 | setLanguage, 121 | } 122 | -------------------------------------------------------------------------------- /helpers/limit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to send limit picker 3 | * 4 | * @module limit 5 | * @license MIT 6 | */ 7 | 8 | /** Dependencies */ 9 | const db = require('./db') 10 | 11 | /** 12 | * Sends limit message to specified chat 13 | * @param {Telegam:Bot} bot Bot that should send message 14 | * @param {Mongoose:Chat} chat Chat where to send message 15 | */ 16 | function sendLimit(bot, chat, text) { 17 | const strings = require('./strings')() 18 | 19 | // Check if limit is set 20 | const limitNumber = +text.substr(7).trim() 21 | if (!isNaN(limitNumber) && limitNumber > 0 && limitNumber < 100000) { 22 | chat.required_voters_count = limitNumber 23 | return chat 24 | .save() 25 | .then(() => 26 | bot.sendMessage( 27 | chat.id, 28 | strings.translate( 29 | 'limitSuccess', 30 | chat.language, 31 | chat.required_voters_count 32 | ), 33 | { 34 | parse_mode: 'Markdown', 35 | } 36 | ) 37 | ) 38 | .catch((err) => bot.sendMessage(chat.id, `❗️ _${error.message}_`)) 39 | } 40 | 41 | const replyText = strings.translate( 42 | 'limitMessage', 43 | chat.language, 44 | chat.required_voters_count 45 | ) 46 | const options = { 47 | parse_mode: 'Markdown', 48 | reply_markup: { inline_keyboard: limitKeyboard() }, 49 | } 50 | options.reply_markup = JSON.stringify(options.reply_markup) 51 | bot.sendMessage(chat.id, replyText, options) 52 | } 53 | 54 | /** 55 | * Called when inline button with limit is touched 56 | * @param {Telegram:Bot} bot Bot that should respond 57 | * @param {Telegram:Message} msg Message of inline button that was touched 58 | */ 59 | function setLimit(bot, msg) { 60 | const options = msg.data.split('~') 61 | const limit = parseInt(options[1], 10) 62 | 63 | db.findChat(msg.message.chat) 64 | .then((chat) => { 65 | chat.required_voters_count = limit 66 | return chat 67 | .save() 68 | .then((dbchat) => updateMessagewithSuccess(bot, msg.message, dbchat)) 69 | }) 70 | .catch((err) => updateMessagewithError(bot, msg.message, err)) 71 | } 72 | 73 | /** 74 | * Updates passed message with error statement 75 | * @param {Telegram:Bot} bot Bot that should update the message 76 | * @param {Telegram:Message} msg Message to be updated 77 | * @param {Error} error Erorr to be displayed 78 | */ 79 | function updateMessagewithError(bot, msg, error) { 80 | bot.editMessageText(`❗️ _${error.message}_`, { 81 | chat_id: msg.chat.id, 82 | message_id: msg.message_id, 83 | parse_mode: 'Markdown', 84 | }) 85 | } 86 | 87 | /** 88 | * Updates passed message with success statement 89 | * @param {Telegram:Bot} bot Bot that should update the message 90 | * @param {Telegram:Message} msg Message to be updated 91 | * @param {Mongoose:Chat} chat Chat that had limit updated 92 | */ 93 | function updateMessagewithSuccess(bot, msg, chat) { 94 | const strings = require('./strings')() 95 | 96 | bot.editMessageText( 97 | strings.translate( 98 | 'limitSuccess', 99 | chat.language, 100 | chat.required_voters_count 101 | ), 102 | { 103 | parse_mode: 'Markdown', 104 | chat_id: msg.chat.id, 105 | message_id: msg.message_id, 106 | } 107 | ) 108 | } 109 | 110 | /** 111 | * Returns an inline keyboard with all available limit options 112 | * @return {Telegram:Inline} Inline keyboard with all available limits 113 | */ 114 | function limitKeyboard() { 115 | const list = [3, 5, 8, 10, 20, 30, 40, 50, 100] 116 | const keyboard = [] 117 | let temp = [] 118 | list.forEach((number) => { 119 | temp.push({ 120 | text: `${number}`, 121 | callback_data: `lti~${number}`, 122 | }) 123 | if (temp.length >= 3) { 124 | keyboard.push(temp) 125 | temp = [] 126 | } 127 | }) 128 | return keyboard 129 | } 130 | 131 | /** Exports */ 132 | module.exports = { 133 | sendLimit, 134 | setLimit, 135 | } 136 | -------------------------------------------------------------------------------- /helpers/localizations.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | selectLanguage: { 3 | uk: '👋 Будь ласка, оберіть свою мову.', 4 | en: '👋 Please, select your language.', 5 | pt: '👋 Por favor, selecione o idioma.', 6 | ru: '👋 Пожалуйста, выберите свой язык.', 7 | uz: '👋 Iltimos, tilni tanlang.', 8 | kz: '👋 Sіzdіń tіlіńіzdі tańdańyz.', 9 | tr: '👋 Lütfen dilinizi seçin.', 10 | az: '👋 Xahiş edirəm dilinizi seçin.', 11 | de: '👋 Bitte wähle deine Sprache.', 12 | fr: '👋 S\'il vous plaît, Veuillez sélectionner votre langue.', 13 | }, 14 | helpPrivate: { 15 | uk: 16 | "😎 *Banofbot* дозволяє голосувати за бан учасників чату. З'явився спамер або ще якийсь негідник, а адмінів немає поруч? Просто надішліть `@banofbot` у відповідь на повідомлення порушника, і бот почне голосування.\n\n@banofbot чудово працює в групових чатах — сміливіше, додайте його в кілька! Не забудьте зробити бота адміном, інакше він не зможе працювати.\n\n/help — Показує це повідомлення 😱\n/language — Дозволяє обрати мову 📣\n\nВи хочете підтримати автора бота? Я написав наукову книгу про те, як жити здоровіше та щасливіше! Ви можете купити її на Амазоні — amazon.com/dp/B0CHL7WRYM або на сайті книги — wdlaty.com. Дякую!", 17 | en: 18 | "😎 *Banofbot* allows you to vote to ban users. Got a spammer or flamer but nobody is out there to ban one? Simply reply to the violator's message with the text `@banofbot`, and the bot will start the voting.\n\n@banofbot works well in group chats — so go on, add it to one of your precious chats! Don't forget to set it as an admin, otherwise it wouldn't work.\n\n/help — Shows this message 😱\n/language — Lets you pick the language 📣\n\nDo you want to support the bot's author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book's website — wdlaty.com. Thank you!", 19 | pt: 20 | "😎 *Banofbot* permite que você vote para banir usuários. Tem alguém fazendo spam ou flood e não tem um admin disponível para dar ban? Simplesmente responda à mensagem do infrator com o texto `@banofbot` e o bot iniciará a votação.\n\n@banofbot funciona bem em grupos — então o adicione a um de seus preciosos bate-papos! Não se esqueça de configurá-lo como administrador, caso contrário não vai adiantar nada.\n\n/help — Mostra esta mensagem 😱\n/language — Permite escolher o idioma 📣\n\nDo you want to support the bot's author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book's website — wdlaty.com. Thank you!", 21 | ru: 22 | '😎 *Banofbot* позволяет голосовать за бан участников чата. Появился спамер или ещё какой негодяй, а админов нет рядом? Просто ответьте на сообщение нарушителя текстом `@banofbot`, и бот начнет голосование.\n\n@banofbot отлично работает в групповых чатах — не бойтесь, добавьте его в парочку! Не забудьте сделать бота админом, иначе он не сможет работать.\n\n/help — Показывает это сообщение 😱\n/language — Позволяет выбрать язык 📣\n\nХотите поддержать автора бота? Я написал научную книгу о том, как жить здоровее и счастливее! Купить ее можно на Амазоне — amazon.com/dp/B0CHL7WRYM или на сайте книги — wdlaty.com. Спасибо!', 23 | uz: 24 | "😎 *Banofbot* guruh a’zolarini ban qilish uchun ovoz berishga yordam beradi. Spamer yoki qandaydir bezori paydo bo‘ldi, lekin adminlar bandmi? Shunchaki bezorining xabariga javob qilib `@banofbot` so‘zini yuboring va bot ban qilish uchun ovoz to‘plashni boshlaydi.\n\n@banofbot guruhlarda zo‘r ishlaydi — uni bir nechta guruhlarga qo‘shing va rivojlantirishga yordam bering! Botni admin qilib tayinlashni unutmang, aks holda u ishlamaydi.\n\n/help — Ushbu xabarni ko‘rsatadi 😱\n/language — Foydalanish tilini tanlashga yordam beradi 📣\n\nBot muallifini qo‘llab-quvvatlashni istaysizmi? Men qanday qilib sog‘lomroq va baxtliroq yashash haqida ilmiy kitob yozganman! Siz uni Amazonda - amazon.com/dp/B0CHL7WRYM yoki vebsaytda — wdlaty.com sotib olishingiz mumkin. Kattakon rahmat!", 25 | kz: 26 | "😎 *Banofbot* chattyń paıdalanýshylaryn ban etýge daýys berý múmkіndіgіn beredі. Spammer nemese basqa da qasqyr chatta otyr, bіraq jaqynda admın joq pa? Tek qana `@banofbot` mátіnі bar habarlamamen buzaqyǵa jaýap berіńіz de, bot daýys berýdі bastaıdy. \n\nBotty admın qyldyrýǵa umytpańyz, áıtpese ol jumys іstemeıdі! \n\n/help — Bul habardy kórsetedі 😱\n/language — Tіldі tańdaýǵa múmkіndіk beredі 📣\n\nDo you want to support the bot's author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book's website — wdlaty.com. Thank you!", 27 | tr: 28 | '😎 *Banofbot* kullanıcıları yasaklamak için oy kullanmanıza izin verir. İstenmeyen ileti gönderen bir kullanıcı var veya bir tane yasaklayacak kimse yok mu? İhlalci iletisine `@banofbot` yazıp cevaplamanız yeterli: Bot hemen oylamaya başlayacaktır.\n\n/help — Bu mesajı gösterir 😱\n/language — Dili seçmenizi sağlar 📣\n\nDo you want to support the bot\'s author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book\'s website — wdlaty.com. Thank you!', 29 | az: 30 | '😎 *Banofbot* istifadəçiləri qadağan etmək üçün səs verməyə imkan verir. Spammer və ya alovlandıran var, amma heç kim qadağan edə bilməz? Qanunu pozanın mesajına sadəcə `@banofbot` mətni ilə cavab verin və bot səsverməyə başlayacaq.\n\n@banofbot qrup söhbətlərində yaxşı işləyir — davam edin, dəyərli söhbətlərinizdən birinə əlavə edin! Admin olaraq qurmağı unutmayın, əks halda işə yaramayacaq.\n\n/help — Bu mesajı göndərir.😱\n/language — Dilinizi seçməyə imkan verir 📣\n\nDo you want to support the bot\'s author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book\'s website — wdlaty.com. Thank you!', 31 | de: 32 | '😎 *Banofbot* erlaubt es, über das Bannen von Benutzern abzustimmen. Ihr habt einen Spammer oder Beleidiger, und niemand ist hier um ihn zu bannen? Antworte einfach mit dem Text `@banofbot` auf die Nachricht und der Bot startet die Abstimmung.\n\n@banofbot funktioniert gut in Gruppenchats — also los, füge ihn einem deiner wertvollen Chats hinzu! Vergiss nicht ihm Admin-Rechte zu geben, sonst funktioniert es nicht.\n\n/help — Zeigt diese Nachricht 😱\n/language — Wähle die Sprache 📣\n\nDo you want to support the bot\'s author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book\'s website — wdlaty.com. Thank you!', 33 | fr: 34 | '😎 *Banofbot* vous permet de voter pour bannir des utilisateurs. Vous avez un spammeur ou un insulteur mais personne n\'est là pour le bannir ? Répondez simplement au message du contrevenant avec le texte `@banofbot` et le bot commencera le vote.\n\n@banofbot fonctionne bien dans les chats de groupe — alors allez-y, ajoutez-le à l\'un de vos précieux chats ! N\'oubliez pas de le configurer en tant qu\'administrateur, sinon il ne fonctionnera pas.\n\n/help — Affiche ce message 😱\n/language — Vous permet de choisir la langue 📣\n\nDo you want to support the bot\'s author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book\'s website — wdlaty.com. Thank you!', 35 | }, 36 | helpPublic: { 37 | uk: 38 | "😎 *Banofbot* дозволяє голосувати за бан учасників чату. З'явився спамер або ще якийсь негідник, а адмінів немає поруч? Просто надішліть `@banofbot` у відповідь на повідомлення порушника, і бот почне голосування.\n\n/help — Відображає це повідомлення 😱\n/language — Дозволяє обрати мову 📣\n/lock — Увімкнути або вимкнути доступ неадмінів до команд бота 🔑\n/limit — Змінити мінімальну кількість голосів для кіку користувача ✌️\n/time — Змінити мінімальний проміжок часу між банами\n/votekickWord — дозволяє додати слова, які починають голосування. Спробуйте, наприклад, `/votekickWord кік, челендж, бійка` 🐸\n\nНе забудьте зробити @banofbot адміном, інакше він не зможе працювати.\n\nВи хочете підтримати автора бота? Я написав наукову книгу про те, як жити здоровіше та щасливіше! Ви можете купити її на Амазоні — amazon.com/dp/B0CHL7WRYM або на сайті книги — wdlaty.com. Дякую!", 39 | en: 40 | "😎 *Banofbot* allows you to vote to ban users. Got a spammer or flamer but nobody is out there to ban one? Simply reply to the violator's message with the text `@banofbot` and the bot will start the voting.\n\n/help — Shows this message 😱\n/language — Lets you pick the language 📣\n/lock — Toggles lock or unlock of non-admins using commands 🔑\n/limit — Lets you set minimal number of voters to kick a user ✌️\n/time — Allows you to select the minimum time between bans\n/votekickWord — allows you to set more votekick words. Use like `/votekickWord kick, trial, challenge` 🐸\n\nDon't forget to set @banofbot as an admin, otherwise it wouldn't work.\n\nDo you want to support the bot's author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book's website — wdlaty.com. Thank you!", 41 | pt: 42 | "😎 *Banofbot* permite que você vote para banir usuários. Tem alguém fazendo spam ou flood, mas não tem um admin disponível para dar ban? Simplesmente responda à mensagem do infrator com o texto `@banofbot` e o bot iniciará a votação.\n\n/help — Mostra esta mensagem 😱 \n/language — Permite escolher o idioma 📣\n/lock — Alterna o bloqueio de não-administradores poderem usar os comandos 🔑\n/limit — Permite que você defina um número mínimo de membros para expulsar um usuário ✌️\n/time — permite que você selecione o tempo mínimo entre banimentos\n/votekickWord — allows you to set more votekick words. Use like `/votekickWord kick, trial, challenge` 🐸\n\nNão se esqueça de configurar @banofbot como administrador, caso contrário não vai funcionar.\n\nDo you want to support the bot's author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book's website — wdlaty.com. Thank you!", 43 | ru: 44 | '😎 *Banofbot* позволяет голосовать за бан участников чата. Появился спамер или ещё какой негодяй, а админов нет рядом? Просто ответьте на сообщение нарушителя текстом `@banofbot`, и бот начнет голосование.\n\n/help — Показывает это сообщение 😱\n/language — Позволяет выбрать язык 📣\n/lock — Включить или выключить доступ неадминов к командам бота 🔑\n/limit — Изменить минимальное количество голосов для кика пользователя ✌️\n/time — Изменить минимальный промежуток времени между банами\n/votekickWord — Добавить слова для начала голосования. Попробуйте, например, `/votekickWord кик, челлендж, драка` 🐸\n\nНе забудьте сделать @banofbot админом, иначе он не сможет работать.\n\nХотите поддержать автора бота? Я написал научную книгу о том, как жить здоровее и счастливее! Купить ее можно на Амазоне — amazon.com/dp/B0CHL7WRYM или на сайте книги — wdlaty.com. Спасибо!', 45 | uz: 46 | "😎 *Banofbot* guruh a’zolarini ban qilish uchun ovoz berishga yordam beradi. Spamer yoki qandaydir bezori paydo bo‘ldi, lekin adminlar bandmi? Shunchaki bezorining xabariga javob qilib `@banofbot` so‘zini yuboring va bot ban qilish uchun ovoz to‘plashni boshlaydi.\n\n/help — Ushbu xabarni ko‘rsatadi 😱\n/language — Foydalanish tilini tanlashga yordam beradi 📣\n/lock — Oddiy foydalanuvchilarga (admin bo‘lmaganlarga) bot buyruqlarini ishlatishni ta’qiqlaydi 🔑\n/limit — Foydalanuvchini ban qilish uchun kerak bo‘lgan eng kam ovozlar sonini belgilaydi ✌️\n/time — Banlar orasidagi eng kam vaqtni belgilaydi\n/votekickWord — allows you to set more votekick words. Use like `/votekickWord kick, trial, challenge` 🐸\n\nBotni admin qilib tayinlashni unutmang, aks holda u ishlamaydi.\n\nBot muallifini qo‘llab-quvvatlashni istaysizmi? Men qanday qilib sog‘lomroq va baxtliroq yashash haqida ilmiy kitob yozganman! Siz uni Amazonda - amazon.com/dp/B0CHL7WRYM yoki vebsaytda — wdlaty.com sotib olishingiz mumkin. Kattakon rahmat!", 47 | kz: 48 | "😎 *Banofbot* chattyń paıdalanýshylaryn ban etýge daýys berý múmkіndіgіn beredі. Spammer nemese basqa da qasqyr chatta otyr, bіraq jaqynda admın joq pa? Tek qana `@banofbot` mátіnі bar habarlamamen buzaqyǵa jaýap berіńіz de, bot daýys berýdі bastaıdy.\n\n/help — Bul habardy kórsetedі 😱\n/language — Tіldі tańdaýǵa múmkіndіk beredі 📣\n/lock — Bot komandalaryna admın emes qoldanýshylarǵa qatynaý berý nemese alý. 🔑\n/limit — Shyǵaryp jіberý úshіn eń az daýys sanyn tańdaý ✌️\n/time — Banǵa daýys berý aralyǵyndagy en az sekýndtar sanyn tańdaý\n/votekickWord — allows you to set more votekick words. Use like `/votekickWord kick, trial, challenge` 🐸\n\nBotty admın qyldyrýǵa umytpańyz, áıtpese ol jumys іstemeıdі!\n\nDo you want to support the bot's author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book's website — wdlaty.com. Thank you!", 49 | tr: 50 | '😎 *Banofbot* kullanıcıları yasaklamak için oy kullanmanıza izin verir. İstenmeyen ileti gönderen bir kullanıcı var veya bir tane yasaklayacak kimse yok mu? İhlalci iletisine `@banofbot` yazıp cevaplamanız yeterli: Bot hemen oylamaya başlayacaktır.\n\n/help — Bu mesajı gösterir 😱\n/language — Dili seçmenizi sağlar 📣\n/lock — Komutları kullanarak yönetici olmayanların kilidini açar veya kapatır 🔑\n/limit — Bir kullanıcıyı kovmak için en az sayıda seçmen belirlemenizi sağlar ✌️\n/time — Yasaklamalar arasındaki minimum süreyi seçmenizi sağlar\n/votekickWord — allows you to set more votekick words. Use like `/votekickWord kick, trial, challenge` 🐸\n\n@banofbot botunu bir yönetici olarak ayarlamayı unutmayın, aksi halde işe yaramaz.\n\nDo you want to support the bot\'s author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book\'s website — wdlaty.com. Thank you!', 51 | az: 52 | '😎 *Banofbot* istifadəçiləri qadağan etmək üçün səs verməyə imkan verir. Spammer və ya alovlandıran var, amma heç kim qadağan edə bilməz? Qanunu pozanın mesajına sadəcə `@banofbot` mətni ilə cavab verin və bot səsverməyə başlayacaq.\n\n/help — Bu mesajı göstərir 😱 \n/language — Dili seçməyə imkan verir 📣 \n/lock — Kilidi açar və ya əmrlərdən istifadə edərək admin olmayanların kilidini açmaq 🔑 \n/limit — Bir istifadəçiyə təpik vurmaq üçün seçicilərin minimum sayını təyin etməyə imkan verir ✌️\n/time — arasında minimum vaxt seçməyə imkan verir qadağalar\n/votekickWord — daha çox səs seçmə sözləri təyin etməyə imkan verir. `/votekickWord kick, trial, challenge` kimi istifadə edin 🐸 \n\n@banofbot u admin olaraq təyin etməyi unutmayın, əks halda alınmayacaq.\n\nDo you want to support the bot\'s author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book\'s website — wdlaty.com. Thank you!', 53 | de: 54 | '😎 *Banofbot* erlaubt es, über das Bannen von Benutzern abzustimmen. Ihr habt einen Spammer oder Beleidiger, und niemand ist hier um ihn zu bannen? Antworte einfach mit dem Text `@banofbot` auf die Nachricht und der Bot startet die Abstimmung.\n\n/help — Zeigt diese Nachricht 😱\n/language — Wähle die Sprache 📣\n/lock — Aktiviert oder deaktiviert, dass nicht-Admins Kommandos nutzen können 🔑\n/limit — Wähle die anzahl benötigter Stimmen, um einen User zu bannen ✌️\n/time — Erlaubt dir, die Zeitspanne zwischen Bans zu setzen\n/votekickWord — Erlaubt dir, andere Wörter zu wählen um eine Abstimmung zu starten. Verwende es wie `/votekickWord entfernen, abstimmung, bannen` 🐸\n\nVergiss nicht, @banofbot Admin-Rechte zu geben, sonst funktioniert es nicht.\n\nDo you want to support the bot\'s author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book\'s website — wdlaty.com. Thank you!', 55 | fr: 56 | '😎 *Banofbot* vous permet de voter pour bannir des utilisateurs. Vous avez un spammeur ou un insulteur mais personne n\'est là pour le bannir ? Répondez simplement au message du contrevenant avec le texte `@banofbot` et le bot commencera le vote.\n\n/help — Affiche ce message 😱\n/language — Vous permet de choisir la langue 📣\n/lock — Permet de verrouiller ou de déverrouiller les utilisateurs non-admins utilisant des commandes 🔑\n/limit — Vous permet de définir le nombre minimal de votants pour expulser un utilisateur ✌️\n/time — Vous permet de sélectionner le temps minimum entre les bannissements `/votekickWord — vous permet de définir plus de mots de votekick. Utilisez comme `/votekickWord kick, trial, challenge` 🐸\n\nN\'oubliez pas de définir @banofbot en tant qu\'admin, sinon cela ne fonctionnera pas.\n\nDo you want to support the bot\'s author? I wrote a scientific book about how to live healthier and happier! You can buy it on Amazon — amazon.com/dp/B0CHL7WRYM or on the book\'s website — wdlaty.com. Thank you!', 57 | }, 58 | languageSelectedBanofbot: { 59 | uk: '@banofbot тепер розмовляє українською. Дякую!', 60 | en: '@banofbot now speaks English. Thank you!', 61 | pt: '@banofbot agora fala português. Obrigado!', 62 | ru: '@banofbot теперь говорит по-русски. Спасибо!', 63 | uz: '@banofbot endi o‘zbekcha gapiradi. Rahmat!', 64 | kz: '@banofbot endі qazaqsha sóıleıdі. Rahmet!', 65 | tr: '@banofbot artık Türkçe konuşuyor. Teşekkür ederiz!', 66 | az: '@banofbot indi Azərbaycan dilində danışır. Çox sağ ol!', 67 | de: '@banofbot spricht nun Deutsch. Danke!', 68 | fr: '@banofbot parle désormais le français. Merci!', 69 | }, 70 | lockOnBanofbot: { 71 | uk: 72 | '🔑 Чудово! *Banofbot* тепер реагує тільки на команди, надіслані *адмінами*, у цьому чаті.', 73 | en: 74 | '🔑 Great! *Banofbot* will now respond only to command calls sent by *admins* in this chat.', 75 | pt: 76 | '🔑 OK! *Banofbot* somente vai atender a comandos enviados por *admins".', 77 | ru: 78 | '🔑 Чудно! *Banofbot* теперь реагирует только на команды, отправленные *админами*, в этом чате.', 79 | uz: 80 | '🔑 Ajoyib! *Banofbot* endi faqat ushbu guruhdagi *adminlar* yuborgan buyruqlarga javob beradi.', 81 | kz: 82 | '🔑 Keremet! *Banofbot* endі osy chatta *admınnyń* komandalaryna ǵana jaýap beredі.', 83 | tr: 84 | '🔑 Harika! *Banofbot* artık bu sohbette yalnızca *yöneticiler* tarafından gönderilen komut çağrılarına yanıt verecek.', 85 | az: 86 | '🔑 Əla! *Banofbot* indi yalnız *söhbət* administratorları tərəfindən göndərilən komanda zənglərinə cavab verəcəkdir.', 87 | de: 88 | '🔑 Super! *Banofbot* antwortet nun nur auf Kommandos, die von *admins* in diesem Chat gesendet werden.', 89 | fr: 90 | '🔑 Super! *Banofbot* ne répondra désormais qu\'aux commandes envoyées par les *admins* dans ce chat.', 91 | }, 92 | lockOffBanofbot: { 93 | uk: 94 | '🔑 Чудово! *Banofbot* тепер реагує на команди, надіслані *будь-якими користувачами*, у цьому чаті.', 95 | en: 96 | '🔑 Great! *Banofbot* will now respond to command calls from *anyone* in this chat.', 97 | pt: 98 | '🔑 OK! *Banofbot* vai atender a comandos de *qualquer membro*.', 99 | ru: 100 | '🔑 Чудно! *Banofbot* теперь реагирует на команды, отправленные *любыми пользователями*, в этом чате.', 101 | uz: 102 | '🔑 Ajoyib! *Banofbot* endi ushbu guruhdagi *istalgan foydalanuvchi* yuborgan buyruqlarga javob beradi.', 103 | kz: 104 | '🔑 Keremet! *Banofbot* endі osy chatta *kez kelgen paıdalanýshy* arqyly jіberіlgen komandalarǵa jaýap beredі.', 105 | tr: 106 | '🔑 Harika! *Banofbot* artık bu sohbette *herkes* tarafından gönderilen komut çağrılarına cevap verecek.', 107 | az: 108 | '🔑 Əla! *Banofbot* indi yalnız bu söhbətdəki *hər kəsdən* gələn komanda zənglərinə cavab verəcəkdir.', 109 | de: 110 | '🔑 Super! *Banofbot* antwortet nun auf Kommandos von *jedem/jeder* in diesem Chat.', 111 | fr: 112 | '🔑 Super! *Banofbot* répondra désormais aux commandes de *n\'importe qui* dans ce chat.', 113 | }, 114 | kickRequest: { 115 | uk: '$[1] хоче кікнути $[2] з чату. Згодні?', 116 | en: '$[1] would like to kick $[2]. Do you agree?', 117 | pt: '$[1] acha que $[2] deveria ser retirado do grupo. Concordam?', 118 | ru: '$[1] хочет кикнуть $[2] из чата. Согласны?', 119 | uz: '$[1] guruhdan $[2]ni ban qilishni istayapti. Rozimisiz?', 120 | kz: '$[1] chattań $[2]-ti shyǵatynyn qalaıdy. Sіz kelіsesіz be?', 121 | tr: '$[1], $[2] kullanıcısını kovmak istiyor. Katılıyor musunuz?', 122 | az: '$[1] təpik vurmaq istəyir $[2]. Razısan?', 123 | de: '$[1] würde gerne $[2] aus dem Chat bannen. Stimmst du zu?', 124 | fr: '$[1] souhaite exclure $[2]. Est-tu d\'accord?', 125 | }, 126 | kickAction: { 127 | uk: '🔫 Кікнути ($[1]/$[2])', 128 | en: '🔫 Kick ($[1]/$[2])', 129 | pt: '🔫 Retirar ($[1]/$[2])', 130 | ru: '🔫 Кикнуть ($[1]/$[2])', 131 | uz: '🔫 Ban qilish ($[1]/$[2])', 132 | kz: '🔫 Shyǵaryp jіberý ($[1]/$[2])', 133 | tr: '🔫 Kov ($[1]/$[2])', 134 | az: '🔫 Vurun ($[1]/$[2])', 135 | de: '🔫 Bannen ($[1]/$[2])', 136 | fr: '🔫 Exclure ($[1]/$[2])', 137 | }, 138 | saveAction: { 139 | uk: '👼 Вибачити ($[1]/$[2])', 140 | en: '👼 Save ($[1]/$[2])', 141 | pt: '👼 Absolver ($[1]/$[2])', 142 | ru: '👼 Простить ($[1]/$[2])', 143 | uz: '👼 Kechirish ($[1]/$[2])', 144 | kz: '👼 Keshіrý', 145 | tr: '👼 Tut ($[1]/$[2])', 146 | az: '👼 Yadda saxla ($[1]/$[2])', 147 | de: '👼 Im Chat behalten ($[1]/$[2])', 148 | fr: '👼 Sauver ($[1]/$[2])', 149 | }, 150 | resultSave: { 151 | uk: 152 | '👼 $[1] врятований — цього разу його не кікнули.\n\nЗа порятунок проголосували:\n$[2]', 153 | en: 154 | '👼 $[1] has been saved — no kick for you this time.\n\nVoters who chose to save:\n$[2]', 155 | pt: 156 | '👼 $[1] foi absolvido — sem expulsões por enquanto.\n\nQuem votou pela absolvição:\n$[2]', 157 | ru: 158 | '👼 $[1] спасён — в этот раз его не кикнули.\n\nЗа спасение проголосовали:\n$[2]', 159 | uz: 160 | '👼 $[1] guruhda qoldirildi — bu safar u ban qilinmadi.\n\nGuruhda qoldirishga berilgan ovozlar:\n$[2]', 161 | kz: 162 | '👼 $[1] qutqaryldy — bul joly ony shyǵaryp jіbermedі. \n\nQutqarý úshіn daýys berdі:\n$[2]', 163 | tr: 164 | '👼 $[1] grupta tutuldu — bu seferlik sizin için kovma eylemi yok.\n\nGrupta tutulmasını isteyenler:\n$[2]', 165 | az: 166 | '👼 $[1] Qeyd edildi — bu dəfə sizin üçün bir zərbə yoxdur. \\n\\nQənaəti seçən seçicilər:\\n$[2]', 167 | de: 168 | '👼 $[1] wurde im Chat behalten — diesmal kein Bannen aus dem Chat für dich.\n\nLeute die für im Chat behalten gestimmt haben:\n$[2]', 169 | fr: 170 | '👼 $[1] a été sauvé — pas d\'exclusion pour cette fois.\n\nLes votants qui ont choisi de sauver:\n$[2]', 171 | }, 172 | resultKick: { 173 | uk: 174 | '🔫 $[1] кікнуто — повернути цього користувача можна тільки розбаном у налаштуваннях чату.\n\nЗа кік проголосували:\n$[2]', 175 | en: 176 | '🔫 $[1] has been kicked — the only way to get this user back is for admins to manualy unban in chat settings.\n\nVoters who chose to kick:\n$[2]', 177 | pt: 178 | '🔫 $[1] foi retirado do grupo — a única forma de retornar é um admin removendo o banimento do usuário nas configurações do grupo.\n\nQuem votou pela expulsão:\n$[2]', 179 | ru: 180 | '🔫 $[1] кикнут — вернуть этого пользователя можно только разбаном в настройках чата.\n\nЗа кик проголосовали:\n$[2]', 181 | uz: 182 | '🔫 $[1] ban qilindi — foydalanuvchini qaytarish uchun uni guruh qora ro‘yxatdan olib tashlash kerak.\n\nBan qilish uchun ovozlar soni:\n$[2]', 183 | kz: 184 | '🔫 $[1] shyǵaryp jіberіldі — bul paıdalanýshyny tek chat parametrlerі arqyly qaıtarylýy múmkіn.\n\nShyǵaryp jіberý úshіn daýys bergen:\n$[2]', 185 | tr: 186 | '🔫 $[1] kovuldu — bu kullanıcıyı geri almanın tek yolu, yöneticilerin sohbet ayarlarından yasağı elle kaldırmasıdır.\n\nGruptan kovulmasını isteyenler:\n$[2]', 187 | az: 188 | '🔫 $[1] təpik atıldı — bu istifadəçini geri qaytarmağın yeganə yolu administratorların sohbet ayarlarında unban elan etməsidir. \n\nDoğuşmağı seçən seçicilər:\n$[2]', 189 | de: 190 | '🔫 $[1] wurde aus dem Chat gebannt — Der einzige Weg, diesen User in den Chat zurück zu lassen ist für die Admins, den User in den Chat-Settings manuell zu entbannen.\n\nLeute die für Bannen haben:\n$[2]', 191 | fr: 192 | '🔫 $[1] a été exclu — le seul moyen d\'autoriser cet utilisasteur à revenir est que les admins l\'autorisent manuellement dans les paramètres du chat.\n\nLes votants qui ont choisi l\'exclusion:\n$[2]', 193 | }, 194 | voteSave: { 195 | uk: 'Ви вже проголосували за 👼', 196 | en: 'You have already voted for 👼', 197 | pt: 'Você já vou em 👼', 198 | ru: 'Вы уже проголосовали за 👼', 199 | uz: 'Siz 👼 uchun ovoz berib bo‘ldingiz', 200 | kz: 'Sіz daýys berіp qoıdynyz 👼', 201 | tr: 'Zaten oy kullandınız 👼', 202 | az: 'Artıq səs vermisiniz 👼', 203 | de: 'Du hast bereits für 👼 gestimmt', 204 | fr: 'Tu as déjà voté pour 👼', 205 | }, 206 | voteKick: { 207 | uk: 'Ви вже проголосували за 🔫', 208 | en: 'You have already voted for 🔫', 209 | pt: 'Você já votou em 🔫', 210 | ru: 'Вы уже проголосовали за 🔫', 211 | uz: 'Siz 🔫 uchun ovoz berib bo‘ldingiz', 212 | kz: 'Sіz daýys berіp qoıdynyz 🔫', 213 | tr: 'Zaten oy kullandınız 🔫', 214 | az: 'Artıq səs vermisiniz 🔫', 215 | de: 'Du hast bereits für 🔫 gestimmt', 216 | fr: 'Tu as déjà voté pour 🔫', 217 | }, 218 | adminError: { 219 | uk: 220 | '🔥 Ой! Схоже, що @banofbot тут ще не адмін. Будь-ласка, попросіть адмінів зробити @banofbot також адміном, інакше він не буде працювати. Дякую!', 221 | en: 222 | '🔥 Oops! Looks like @banofbot is not an admin here yet. Please ask admins to set @banofbot as an admin as well, otherwise it will not work. Thanks!', 223 | pt: 224 | '🔥 Ops! Parece que o @banofbot ainda não é um administrador. Por favor, peça aos administradores que definam @banofbot como administrador também, senão não funcionará. Obrigado!', 225 | ru: 226 | '🔥 Ой! Похоже, что @banofbot здесь ещё не админ. Пожалуйста, попросите админов сделать @banofbot админом тоже, иначе он не будет работать. Спасибо!', 227 | uz: 228 | "🔥 Vay! @banofbot ushbu guruhda admin emasga o‘xshaydi. Iltimos, adminlardan @banofbot'ni guruh admini qilib tayinlashni so‘rang, aks holda u ishlamaydi. Rahmat!", 229 | kz: 230 | '🔥 Oı! @Banofbot álі admın emes sııaqty. Admınderden @banofbot-dі admin dep qosýdy surańyz, áıtpese ol jumys іstemeıdі. Rahmet!', 231 | tr: 232 | '🔥 Hay aksi! Görünüşe göre @banofbot henüz bir yönetici değil. Lütfen yöneticilerinizden @banofbot botunu bir yönetici olarak ayarlamasını isteyin, aksi takdirde çalışmaz. Teşekkür ederiz!', 233 | az: 234 | '🔥 Vay! Görünür @banofbot hələ burada admin deyil. Zəhmət olmasa, administratorlardan @banofbot-u da admin olaraq təyin etmələrini xahiş edin, əks halda işə yaramır. Təşəkkürlər!', 235 | de: 236 | '🔥 Ups! Sieht so aus als wäre @banofbot noch kein Admin hier. Bitte Admins darum, @banofbot ebenfalls zu einem Admin zu machen, ansonsten wird er nicht funktionieren. Danke!', 237 | fr: 238 | '🔥 Oops! On dirait que @banofbot n\'est pas admin ici. Veuillez demander aux admins de définir @banofbot en tant qu\'admin également, autrement cela ne fonctionnera pas. Merci!', 239 | }, 240 | limitMessage: { 241 | uk: 242 | '✌️ Будь ласка, оберіть мінімальну кількість голосів для кіка користувача. Поточна кількість — *$[1]*', 243 | en: 244 | '✌️ Please, select the minimum number of votes to kick a user. Current number is *$[1]*', 245 | pt: 246 | '✌️ Por favor, selecione o número mínimo de votos para retirar um usuário. O número atual é *$[1]*', 247 | ru: 248 | '✌️ Пожалуйста, выберите минимальное количество голосов для кика пользователя. Текущее количество — *$[1]*', 249 | uz: 250 | '✌️ Iltimos, foydalanuvchilarni ban qilish uchun kerak bo‘lgan eng kam ovozlar sonini ayting. Hozir — *$[1]*', 251 | kz: 252 | '✌️ Paıdalanýshyny shyǵaryp jіberý úshіn eń az daýys sanyn tańdańyz. Aǵymdaǵy san — *$[1]*', 253 | tr: 254 | '✌️ Lütfen bir kullanıcıyı kovmak için minimum oy sayısını seçin. Mevcut sayı: *$[1]*', 255 | az: 256 | '✌️ Xahiş edirəm bir istifadəçiyə təpik vurmaq üçün minimum səs sayını seçin. Cari nömrə *$[1]*', 257 | de: 258 | '✌️ Bitte wähle die anzahl benötigter Stimmen, um einen User zu bannen. Die aktuelle Anzahl ist *$[1]*', 259 | fr: 260 | '✌️ Merci de définir un nombre de votants minimum pour exclure un utilisateur. Le nombre actuel est de *$[1]*', 261 | }, 262 | limitSuccess: { 263 | uk: 264 | '@banofbot тепер буде кікати користувача, якщо *$[1]* людей проголосує за це. Дякую!', 265 | en: 266 | '@banofbot will now kick a user if *$[1]* people vote for it. Thanks!', 267 | pt: 268 | '@banofbot agora vai expulsar um usuário se *$[1]* pessoas votarem. Obrigado!', 269 | ru: 270 | '@banofbot теперь будет кикать пользователя, если *$[1]* людей проголосуют за это. Спасибо!', 271 | uz: 272 | '@banofbot endi foydalanuvchini *$[1]*ta qarshi ovozdan keyin kick qiladi. Rahmat!', 273 | kz: 274 | '@banofbot endі paıdalanýshyny *$[1]* adam daýys bergen kezde, ony shyǵaryp jіberedі. Rahmet!', 275 | tr: 276 | '@banofbot artık *$[1]* kişi oy verirse bir kullanıcıyı kovacak. Teşekkür ederiz!', 277 | az: 278 | '@banofbot indi *$[1]* nəfərin buna səs verməsi halında bir istifadəçini vuracaq. Təşəkkürlər!', 279 | de: 280 | '@banofbot bannt nun einen User wenn *$[1]* leute dafür abstimmen. Danke!', 281 | fr: 282 | '@banofbot va exclure un utilisateur si *$[1]* personnes votent. Merci!', 283 | }, 284 | tooSoonError: { 285 | uk: 286 | '🔥 Схоже, ви намагаєтеся почати голосування за кік занадто рано. Мінімальний проміжок часу між голосуваннями можна змінити за допомогою команди /time. Дякую!', 287 | en: 288 | '🔥 Looks like you are trying to start a new ban request too soon. You can change the time limit for ban requests by using /time command. Thanks!', 289 | pt: 290 | '🔥 Parece que você está tentando iniciar uma nova solicitação cedo demais. Você pode alterar o limite de tempo para este tipo de solicitação usando o comando /time. Obrigado!', 291 | ru: 292 | '🔥 Похоже, вы пытаетесь начать голосование за кик слишком рано. Минимальный промежуток времени между голосованиями можно изменить при помощи команды /time. Спасибо!', 293 | uz: 294 | '🔥 Kick qilish uchun ovoz to‘plashni juda erta boshlashga urinyapsiz. Ban qilish uchun ovoz berish jarayonlari orasidagi vaqtni /time buyrug‘i orqali o‘zgartirishingiz mumkin. Rahmat!', 295 | kz: 296 | '🔥 Sіz tym erte shyǵaryp jіberýge daýys berýdі bastap jatyrsyz. Daýys berý arasyndaǵy ýaqyt aralyǵyn /time komandasymen ózgertýge bolady. Rahmet!', 297 | tr: 298 | '🔥 Çok kısa sürede yeni bir yasak isteği başlatmaya çalışıyor gibi görünüyorsun. /time komutunu kullanarak yasak isteklerinin zaman sınırını değiştirebilirsin. Teşekkür ederiz!', 299 | az: 300 | '🔥 Göründüyü kimi yeni bir qadağan tələbini çox tez başlamağa çalışırsınız. /time əmrini istifadə edərək qadağan istəkləri üçün vaxt məhdudiyyətini dəyişə bilərsiniz. Təşəkkürlər!', 301 | de: 302 | '🔥 Sieht so aus, als würdest du zu schnell eine neue Ban-Anfrage starten. Du kannst das Zeitlimit für Ban-Anfragen mit dem /time-Kommando ändern. Danke!', 303 | fr: 304 | '🔥 Il semble que vous essayez de lancer une nouvelle demande d\'exclusion trop tôt. Vous pouvez modifier la limite de temps pour les demandes d\'exclusions en utilisant la commande /time. Merci!', 305 | }, 306 | timeLimitMessage: { 307 | uk: 308 | '✌️ Будь ласка, оберіть мінімальний проміжок часу між голосуваннями за бан. Поточне обмеженння — *$[1]* секунд.', 309 | en: 310 | '✌️ Please, select the minimum amount of time between ban requests. The current limit is *$[1]* seconds.', 311 | pt: 312 | '✌️ Por favor, selecione o tempo mínimo entre as solicitações. O limite atual é *$[1]* segundos.', 313 | ru: 314 | '✌️ Пожалуйста, выберите минимальный промежуток времени между голосованиями за бан. Текущее ограничение — *$[1]* секунд.', 315 | uz: 316 | '✌️ Iltimos, ban qilish uchun ovoz berish jarayonlari orasidagi eng kam vaqtni belgilang. Hozirgi cheklov — *$[1]* soniya.', 317 | kz: 318 | '✌️ Banǵa daýys berý aralyǵyndagy en az sekýndtar sanyn tańdanyz. Kazіrgі lımıt — *$[1]* sekýnd', 319 | tr: 320 | '✌️ Lütfen yasak istekleri arasındaki minimum süreyi seçin. Mevcut süre: *$[1]* saniye.', 321 | az: 322 | '✌️ Xahiş edirəm, qadağan istəkləri arasındakı minimum vaxtı seçin. Cari limit *$[1]* saniyədir.', 323 | de: 324 | '✌️ Bitte wähle das Zeitlimit zwischen zwei Ban-Anfragen. Das momentane Limit ist *$[1]* Sekunden.', 325 | fr: 326 | '✌️ Merci de définir un temps minimum entre les demandes d\'exclusions. La limite actuelle est de *$[1]* secondes.', 327 | }, 328 | timeLimitSuccess: { 329 | uk: 330 | '@banofbot тепер дозволятиме голосування за бан через *$[1]* секунд після останнього бана. Дякую!', 331 | en: 332 | '@banofbot will now allow new ban requests *$[1]* seconds after the last ban. Thanks!', 333 | pt: 334 | '@banofbot agora permitirá novas solicitações *$[1]* segundos após a última. Obrigado!', 335 | ru: 336 | '@banofbot теперь будет разрешать голосования за бан спустя *$[1]* секунд после последнего бана. Спасибо!', 337 | uz: 338 | '@banofbot endi ban qilish uchun ovoz berishni oxirgi bandan *$[1]* soniyadan keyin boshlaydi. Rahmat!', 339 | kz: 340 | '@banofbot sońǵy bannan keıіn *$[1]* sekýnd ótkennen soń banǵa daýys berý bastaıdy. Rahmet!', 341 | tr: 342 | '@banofbot artık yeni yasaklama isteklerine son yasaklamadan *$[1]* saniye sonra izin verecek. Teşekkür ederiz!', 343 | az: 344 | '@banofbot indi son qadağadan *$[1]* saniyə sonra yeni qadağan istəklərinə icazə verəcəkdir. Təşəkkürlər!', 345 | de: 346 | '@banofbot erlaubt nun neue Ban-Anfragen *$[1]* Sekunden nach dem letzten Ban. Danke!', 347 | fr: 348 | '@banofbot autorise désormais de nouvelles exclusions *$[1]* secondes après la dernière exclusion. Merci!', 349 | }, 350 | }; 351 | -------------------------------------------------------------------------------- /helpers/lock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to handle /lock command 3 | * 4 | * @module lock 5 | * @license MIT 6 | */ 7 | 8 | /** 9 | * Function to toggle adminLocked of the chat 10 | * @param {Telegram:Bot} bot Bot that should send success message 11 | * @param {Mongoose:Chat} chat Chat that should get adminLocked changed 12 | */ 13 | function toggle(bot, chat) { 14 | const strings = require('./strings')() 15 | 16 | chat.admin_locked = !chat.admin_locked 17 | chat 18 | .save() 19 | .then((newChat) => { 20 | const text = newChat.admin_locked ? 'lockOnBanofbot' : 'lockOffBanofbot' 21 | bot.sendMessage(newChat.id, strings.translate(text, chat.language), { 22 | parse_mode: 'Markdown', 23 | }) 24 | }) 25 | .catch(/** todo: handle error */) 26 | } 27 | 28 | /** Exports */ 29 | module.exports = { 30 | toggle, 31 | } 32 | -------------------------------------------------------------------------------- /helpers/requests.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Requests logic 3 | * 4 | * @module requests 5 | * @license MIT 6 | */ 7 | 8 | /** Dependencies */ 9 | const db = require('./db') 10 | const _ = require('lodash') 11 | const admins = require('./admins') 12 | const { Lock } = require('semaphore-async-await') 13 | const { isRuChat } = require('./isRuChat') 14 | const { isOver10000 } = require('./goldenBorodutchSubCount') 15 | 16 | const promoAdditions = { 17 | ru: () => 18 | isOver10000() 19 | ? 'Информация по переезду из 🇺🇦 в 🇨🇦' 20 | : 'Информация по переезду из 🇺🇦 в 🇨🇦', 21 | en: () => 22 | 'Info on moving from 🇺🇦 to 🇨🇦', 23 | } 24 | 25 | /** 26 | * Starts ban request 27 | * @param {Telegram:Bot} bot Bot that should respond 28 | * @param {Telegram:Message} msg Message to start ban request 29 | */ 30 | async function startRequest(bot, msg) { 31 | const chat = await db.findChat(msg.chat) 32 | const starter = await db.findUser(msg.from) 33 | const candidate = await db.findUser(msg.reply_to_message.from) 34 | 35 | // Check if it can create new ban request 36 | const now = new Date().getTime() 37 | const lastBan = chat.last_ban.getTime() 38 | const requiredMilliseconds = chat.seconds_between_bans * 1000 39 | if (now - lastBan < requiredMilliseconds) { 40 | return sendBanLimitError(bot, chat) 41 | } 42 | 43 | const isBotAdmin = await admins.isBotAdmin(bot, chat.id) 44 | if (!isBotAdmin) { 45 | sendAdminError(bot, chat) 46 | return 47 | } 48 | 49 | const isCandidateAdmin = await admins.isAdmin(bot, chat.id, candidate.id) 50 | if (isCandidateAdmin) { 51 | return 52 | } 53 | 54 | if (candidate.username === 'banofbot') { 55 | return 56 | } 57 | 58 | const mockRequest = { 59 | reply_chat_id: msg.reply_to_message.chat.id, 60 | reply_message_id: msg.reply_to_message.message_id, 61 | chat, 62 | candidate, 63 | starter, 64 | voters_ban: [starter], 65 | } 66 | const request = await db.createRequest(mockRequest) 67 | 68 | const strings = require('./strings')() 69 | 70 | const starterName = await request.starter.realNameWithHTML(bot, chat.id) 71 | const candidateName = await request.candidate.realNameWithHTML(bot, chat.id) 72 | 73 | const promoAddition = promoAdditions[isRuChat(chat) ? 'ru' : 'en']() 74 | 75 | const text = `${strings.translate( 76 | 'kickRequest', 77 | request.chat.language, 78 | starterName, 79 | candidateName 80 | )}\n${promoAddition}` 81 | const options = { 82 | parse_mode: 'HTML', 83 | disable_web_page_preview: true, 84 | reply_markup: { 85 | inline_keyboard: kickKeyboard( 86 | 1, 87 | 0, 88 | request._id, 89 | strings, 90 | request.chat.required_voters_count, 91 | request.chat.language 92 | ), 93 | }, 94 | } 95 | options.reply_markup = JSON.stringify(options.reply_markup) 96 | const data = await bot.sendMessage(request.chat.id, text, options) 97 | 98 | request.inline_chat_id = data.chat.id 99 | request.inline_message_id = data.message_id 100 | await request.save() 101 | } 102 | 103 | /** 104 | * Function to be called when somebody votes for ban 105 | * @param {Telegram:Bot} bot Bot that should respond 106 | * @param {Teleram:Message} msg Message that triggered inline 107 | */ 108 | async function voteQuery(bot, msg) { 109 | const lock = new Lock(1) 110 | await lock.acquire() 111 | try { 112 | const options = msg.data.split('~') 113 | const requestId = options[1] 114 | const against = parseInt(options[2], 10) === 1 115 | 116 | const member = await bot.getChatMember(msg.message.chat.id, msg.from.id) 117 | 118 | if (!['creator', 'administrator', 'member'].includes(member.status)) { 119 | return bot.answerCallbackQuery(msg.id) 120 | } 121 | 122 | let request = await db 123 | .findRequest(requestId) 124 | .populate('chat candidate starter voters_ban voters_noban') 125 | const voter = await db.findUser(msg.from) 126 | 127 | const strings = require('./strings')() 128 | 129 | if (against) { 130 | const alreadyThere = _.find(request.voters_noban, (arrayVoter) => 131 | arrayVoter._id.equals(voter._id) 132 | ) 133 | if (alreadyThere) { 134 | await bot.answerCallbackQuery(msg.id, { 135 | text: strings.translate('voteSave', request.chat.language), 136 | show_alert: true, 137 | }) 138 | return 139 | } else { 140 | await bot.answerCallbackQuery(msg.id) 141 | } 142 | request.voters_ban = request.voters_ban.filter( 143 | (arrayVoter) => !arrayVoter._id.equals(voter._id) 144 | ) 145 | request.voters_noban.push(voter) 146 | } else { 147 | const alreadyThere = _.find(request.voters_ban, (arrayVoter) => 148 | arrayVoter._id.equals(voter._id) 149 | ) 150 | if (alreadyThere) { 151 | await bot.answerCallbackQuery(msg.id, { 152 | text: strings.translate('voteKick', request.chat.language), 153 | show_alert: true, 154 | }) 155 | return 156 | } else { 157 | await bot.answerCallbackQuery(msg.id) 158 | } 159 | request.voters_noban = request.voters_noban.filter( 160 | (arrayVoter) => !arrayVoter._id.equals(voter._id) 161 | ) 162 | request.voters_ban.push(voter) 163 | } 164 | request = await request.save() 165 | await updateMessage(bot, request) 166 | } catch (err) { 167 | console.error(err) 168 | // Do nothing 169 | } finally { 170 | lock.release() 171 | } 172 | } 173 | 174 | /** 175 | * Function to update existing request message 176 | * @param {Telegram:Bot} bot Bot that should respond 177 | * @param {Mongoose:Request} request Request whos message to be updated 178 | */ 179 | async function updateMessage(bot, request) { 180 | const finished = 181 | request.voters_noban.length >= request.chat.required_voters_count || 182 | request.voters_ban.length >= request.chat.required_voters_count 183 | if (finished) { 184 | return await finishRequest(bot, request) 185 | } 186 | const strings = require('./strings')() 187 | 188 | const starterName = await request.starter.realNameWithHTML( 189 | bot, 190 | request.chat.id 191 | ) 192 | const candidateName = await request.candidate.realNameWithHTML( 193 | bot, 194 | request.chat.id 195 | ) 196 | 197 | const promoAddition = promoAdditions[isRuChat(request.chat) ? 'ru' : 'en']() 198 | 199 | const text = `${strings.translate( 200 | 'kickRequest', 201 | request.chat.language, 202 | starterName, 203 | candidateName 204 | )}\n${promoAddition}` 205 | const options = { 206 | parse_mode: 'HTML', 207 | chat_id: request.inline_chat_id, 208 | message_id: request.inline_message_id, 209 | disable_web_page_preview: true, 210 | reply_markup: { 211 | inline_keyboard: kickKeyboard( 212 | request.voters_ban.length, 213 | request.voters_noban.length, 214 | request._id, 215 | strings, 216 | request.chat.required_voters_count, 217 | request.chat.language 218 | ), 219 | }, 220 | } 221 | options.reply_markup = JSON.stringify(options.reply_markup) 222 | 223 | return await bot.editMessageText(text, options) 224 | } 225 | 226 | /** 227 | * Finalizes request when there are enough people 228 | * @param {Telegram:Bot} bot Bot that should respond 229 | * @param {Mongoose:Request} request Request to be finalized 230 | */ 231 | async function finishRequest(bot, request) { 232 | const strings = require('./strings')() 233 | 234 | const saved = 235 | request.voters_noban.length >= request.chat.required_voters_count 236 | let voters 237 | if (saved) { 238 | const votersArray = [] 239 | for (const voter of request.voters_noban) { 240 | const realName = await voter.realNameWithHTML(bot, request.chat.id) 241 | votersArray.push(realName) 242 | } 243 | voters = votersArray.join(', ') 244 | } else { 245 | const votersArray = [] 246 | for (const voter of request.voters_ban) { 247 | const realName = await voter.realNameWithHTML(bot, request.chat.id) 248 | votersArray.push(realName) 249 | } 250 | voters = votersArray.join(', ') 251 | } 252 | 253 | const candidateName = await request.candidate.realNameWithHTML( 254 | bot, 255 | request.chat.id 256 | ) 257 | 258 | const promoAddition = promoAdditions[isRuChat(request.chat) ? 'ru' : 'en']() 259 | 260 | const text = `${ 261 | saved 262 | ? strings.translate( 263 | 'resultSave', 264 | request.chat.language, 265 | candidateName, 266 | voters 267 | ) 268 | : strings.translate( 269 | 'resultKick', 270 | request.chat.language, 271 | candidateName, 272 | voters 273 | ) 274 | }\n${promoAddition}` 275 | 276 | if (!saved) { 277 | bot.kickChatMember(request.chat.id, request.candidate.id) 278 | if (request.reply_chat_id && request.reply_message_id) { 279 | bot.deleteMessage(request.reply_chat_id, request.reply_message_id) 280 | } 281 | try { 282 | request.chat.last_ban = new Date() 283 | await request.chat.save() 284 | } catch (err) { 285 | // Do nothing 286 | } 287 | } 288 | 289 | const options = { 290 | parse_mode: 'HTML', 291 | chat_id: request.inline_chat_id, 292 | message_id: request.inline_message_id, 293 | disable_web_page_preview: true, 294 | } 295 | return bot.editMessageText(text, options) 296 | } 297 | 298 | /** 299 | * Keyboard to kick user 300 | * @param {Number} forkick Number of users to kick 301 | * @param {Number} against Number of users against kick 302 | * @param {Mongoose:ObjectId} requestId Id of the request 303 | * @param {strings.js} strings Localization object 304 | * @param {Number} voteCount Minimal number of votes to kick or save 305 | * @return {Telegram:InlineKeyboard} Keyboard to kick or not to kick user 306 | */ 307 | function kickKeyboard( 308 | forkick, 309 | against, 310 | requestId, 311 | strings, 312 | voteCount, 313 | language 314 | ) { 315 | return [ 316 | [ 317 | { 318 | text: strings.translate('kickAction', language, forkick, voteCount), 319 | callback_data: `vi~${String(requestId)}~0`, 320 | }, 321 | ], 322 | [ 323 | { 324 | text: strings.translate('saveAction', language, against, voteCount), 325 | callback_data: `vi~${String(requestId)}~1`, 326 | }, 327 | ], 328 | ] 329 | } 330 | 331 | /** 332 | * Function to send error that bot is not an admin to the chat 333 | * @param {Telegram:Bot} bot Bot that should send message 334 | * @param {Mongoose:Chat} chat Chat that should receive the message 335 | */ 336 | function sendAdminError(bot, chat) { 337 | const strings = require('./strings')() 338 | 339 | return bot.sendMessage( 340 | chat.id, 341 | chat.language, 342 | strings.translate('adminError') 343 | ) 344 | } 345 | 346 | /** 347 | * Function to send error that ban limit is not sufficient 348 | * @param {Telegram:Bot} bot Bot that should send message 349 | * @param {Mongoose:Chat} chat Chat that should receive the message 350 | */ 351 | function sendBanLimitError(bot, chat) { 352 | const strings = require('./strings')() 353 | 354 | return bot.sendMessage( 355 | chat.id, 356 | strings.translate('tooSoonError', chat.language) 357 | ) 358 | } 359 | 360 | /** Exports */ 361 | module.exports = { 362 | startRequest, 363 | voteQuery, 364 | } 365 | -------------------------------------------------------------------------------- /helpers/strings.js: -------------------------------------------------------------------------------- 1 | const texts = require('./localizations') 2 | 3 | module.exports = () => ({ 4 | translate: (key, language, ...theRest) => { 5 | let text = texts[key][language] || texts[key].en 6 | theRest.forEach((sub, i) => { 7 | text = text.replace(new RegExp(`\\$\\[${i + 1}\\]`, 'gum'), sub) 8 | }) 9 | return text 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /helpers/time.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to send time limit picker 3 | * 4 | * @module limit 5 | * @license MIT 6 | */ 7 | 8 | /** Dependencies */ 9 | const db = require('./db') 10 | 11 | /** 12 | * Sends time limit message to specified chat 13 | * @param {Telegam:Bot} bot Bot that should send message 14 | * @param {Mongoose:Chat} chat Chat where to send message 15 | */ 16 | function sendTime(bot, chat) { 17 | const strings = require('./strings')() 18 | 19 | const text = strings.translate( 20 | 'timeLimitMessage', 21 | chat.language, 22 | chat.seconds_between_bans 23 | ) 24 | const options = { 25 | parse_mode: 'Markdown', 26 | reply_markup: { inline_keyboard: limitKeyboard() }, 27 | } 28 | options.reply_markup = JSON.stringify(options.reply_markup) 29 | bot.sendMessage(chat.id, text, options) 30 | } 31 | 32 | /** 33 | * Called when inline button with time limit is touched 34 | * @param {Telegram:Bot} bot Bot that should respond 35 | * @param {Telegram:Message} msg Message of inline button that was touched 36 | */ 37 | function setTime(bot, msg) { 38 | const options = msg.data.split('~') 39 | const limit = parseInt(options[1], 10) 40 | 41 | db.findChat(msg.message.chat) 42 | .then((chat) => { 43 | chat.seconds_between_bans = limit 44 | return chat 45 | .save() 46 | .then((dbchat) => updateMessagewithSuccess(bot, msg.message, dbchat)) 47 | }) 48 | .catch((err) => updateMessagewithError(bot, msg.message, err)) 49 | } 50 | 51 | /** 52 | * Updates passed message with error statement 53 | * @param {Telegram:Bot} bot Bot that should update the message 54 | * @param {Telegram:Message} msg Message to be updated 55 | * @param {Error} error Erorr to be displayed 56 | */ 57 | function updateMessagewithError(bot, msg, error) { 58 | bot.editMessageText(`❗️ _${error.message}_`, { 59 | chat_id: msg.chat.id, 60 | message_id: msg.message_id, 61 | parse_mode: 'Markdown', 62 | }) 63 | } 64 | 65 | /** 66 | * Updates passed message with success statement 67 | * @param {Telegram:Bot} bot Bot that should update the message 68 | * @param {Telegram:Message} msg Message to be updated 69 | * @param {Mongoose:Chat} chat Chat that had limit updated 70 | */ 71 | function updateMessagewithSuccess(bot, msg, chat) { 72 | const strings = require('./strings')() 73 | 74 | bot.editMessageText( 75 | strings.translate( 76 | 'timeLimitSuccess', 77 | chat.language, 78 | chat.seconds_between_bans 79 | ), 80 | { 81 | parse_mode: 'Markdown', 82 | chat_id: msg.chat.id, 83 | message_id: msg.message_id, 84 | } 85 | ) 86 | } 87 | 88 | /** 89 | * Returns an inline keyboard with all available time limit options 90 | * @return {Telegram:Inline} Inline keyboard with all available limits 91 | */ 92 | function limitKeyboard() { 93 | const list = [0, 30, 60, 120, 240, 300, 600, 1200, 5000] 94 | const keyboard = [] 95 | let temp = [] 96 | list.forEach((number) => { 97 | temp.push({ 98 | text: `${number}`, 99 | callback_data: `tlti~${number}`, 100 | }) 101 | if (temp.length >= 3) { 102 | keyboard.push(temp) 103 | temp = [] 104 | } 105 | }) 106 | return keyboard 107 | } 108 | 109 | /** Exports */ 110 | module.exports = { 111 | sendTime, 112 | setTime, 113 | } 114 | -------------------------------------------------------------------------------- /helpers/votekickWord.js: -------------------------------------------------------------------------------- 1 | function check(bot, chat, text) { 2 | const votekickWordString = text.substr('/votekickWord'.length).trim() 3 | if (!votekickWordString.length) { 4 | chat.votekickWord = undefined 5 | chat 6 | .save() 7 | .then((newChat) => { 8 | bot.sendMessage(newChat.id, `👍`) 9 | }) 10 | .catch(() => { 11 | // Do nothing 12 | }) 13 | return 14 | } 15 | 16 | chat.votekickWord = votekickWordString 17 | chat 18 | .save() 19 | .then((newChat) => { 20 | bot.sendMessage(newChat.id, `👍 ${votekickWordString}`) 21 | }) 22 | .catch(() => { 23 | // Do nothing 24 | }) 25 | } 26 | 27 | module.exports = { 28 | check, 29 | } 30 | -------------------------------------------------------------------------------- /localize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * File to build localization files 3 | */ 4 | 5 | /** Dependencies */ 6 | const strings = require('./helpers/strings'); 7 | const fm = require('easy-file-manager'); 8 | 9 | const translations = strings().getTranslations(); 10 | 11 | fm.upload('/lozalizations/', 'strings.js', new Buffer(JSON.stringify(translations, null, 2)).toString('base64'), () => {}); 12 | 13 | const englishTranslations = Object.keys(translations).map(v => `${JSON.stringify(v)}`).join('\n\n'); 14 | 15 | fm.upload('/lozalizations/', 'strings.txt', new Buffer(englishTranslations).toString('base64'), () => {}); 16 | -------------------------------------------------------------------------------- /models/chat.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | const mongoose = require('mongoose') 3 | 4 | // Schema 5 | const Schema = mongoose.Schema 6 | const chatSchema = new Schema( 7 | { 8 | id: { 9 | type: String, 10 | required: true, 11 | index: true, 12 | }, 13 | type: { 14 | type: String, 15 | required: true, 16 | }, 17 | username: { 18 | type: String, 19 | index: true, 20 | }, 21 | first_name: String, 22 | last_name: String, 23 | all_members_are_administrators: Boolean, 24 | language: { 25 | type: String, 26 | required: true, 27 | default: 'en', 28 | }, 29 | admin_locked: { 30 | type: Boolean, 31 | required: true, 32 | default: false, 33 | }, 34 | required_voters_count: { 35 | type: Number, 36 | required: true, 37 | default: 5, 38 | }, 39 | last_ban: { 40 | type: Date, 41 | required: true, 42 | default: new Date(0), 43 | index: true, // Add index for time-based queries 44 | }, 45 | seconds_between_bans: { 46 | type: Number, 47 | required: true, 48 | default: 30, 49 | }, 50 | votekickWord: { 51 | type: String, 52 | required: false, 53 | }, 54 | }, 55 | { timestamps: true, usePushEach: true } 56 | ) 57 | 58 | // Create a unique index on the id field for faster lookups 59 | chatSchema.index({ id: 1 }, { unique: true }); 60 | 61 | // Create a compound index for queries that might filter by type and sort by last_ban 62 | chatSchema.index({ type: 1, last_ban: -1 }); 63 | 64 | // Create an index for the findChatsWithNewcomers function that looks for chats with newcomers 65 | chatSchema.index({ 'newcomers.0': 1 }); 66 | 67 | // Exports 68 | module.exports = mongoose.model('chat', chatSchema) 69 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Includes all models in one place 3 | * 4 | * @module models 5 | * @license MIT 6 | */ 7 | 8 | module.exports = { 9 | Chat: require('./chat'), 10 | User: require('./user'), 11 | Request: require('./request'), 12 | }; -------------------------------------------------------------------------------- /models/request.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module models/request 3 | * @license MIT 4 | */ 5 | 6 | /** Dependencies */ 7 | const mongoose = require('mongoose') 8 | 9 | /** Schema */ 10 | const Schema = mongoose.Schema 11 | const requestSchema = new Schema( 12 | { 13 | inline_chat_id: Number, 14 | inline_message_id: Number, 15 | reply_chat_id: Number, 16 | reply_message_id: Number, 17 | chat: { 18 | type: Schema.ObjectId, 19 | ref: 'chat', 20 | required: true, 21 | index: true, // Add index for faster lookups by chat 22 | }, 23 | candidate: { 24 | type: Schema.ObjectId, 25 | ref: 'user', 26 | required: true, 27 | index: true, // Add index for faster lookups by candidate 28 | }, 29 | starter: { 30 | type: Schema.ObjectId, 31 | ref: 'user', 32 | required: true, 33 | index: true, // Add index for faster lookups by request starter 34 | }, 35 | voters_ban: [ 36 | { 37 | type: Schema.ObjectId, 38 | ref: 'user', 39 | required: true, 40 | default: [], 41 | }, 42 | ], 43 | voters_noban: [ 44 | { 45 | type: Schema.ObjectId, 46 | ref: 'user', 47 | required: true, 48 | default: [], 49 | }, 50 | ], 51 | }, 52 | { timestamps: true, usePushEach: true } 53 | ) 54 | 55 | // Create compound indexes for common query patterns 56 | // Index for queries that filter by chat and sort by createdAt (for finding recent requests in a chat) 57 | requestSchema.index({ chat: 1, createdAt: -1 }); 58 | 59 | // Index for finding active requests (those with fewer votes than required) 60 | requestSchema.index({ 'voters_ban.0': 1 }); 61 | requestSchema.index({ 'voters_noban.0': 1 }); 62 | 63 | // Index for finding requests by inline message details (used when updating votes) 64 | requestSchema.index({ inline_chat_id: 1, inline_message_id: 1 }); 65 | 66 | /** Exports */ 67 | module.exports = mongoose.model('request', requestSchema) 68 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @module models/user 3 | * @license MIT 4 | */ 5 | 6 | /** Dependencies */ 7 | const mongoose = require('mongoose') 8 | 9 | /** Schema */ 10 | const Schema = mongoose.Schema 11 | const userSchema = new Schema( 12 | { 13 | id: { 14 | type: Number, 15 | required: true, 16 | index: true, // Add index for faster lookups by Telegram user ID 17 | }, 18 | first_name: { 19 | type: String, 20 | required: true, 21 | }, 22 | last_name: String, 23 | username: { 24 | type: String, 25 | index: true, // Add index for username lookups 26 | }, 27 | }, 28 | { timestamps: true, usePushEach: true } 29 | ) 30 | 31 | // Create a unique index on the id field for faster lookups 32 | userSchema.index({ id: 1 }, { unique: true }); 33 | 34 | // Create index for searching users by name 35 | userSchema.index({ first_name: 1, last_name: 1 }); 36 | 37 | userSchema.methods.name = function name() { 38 | if (this.username) { 39 | return `@${this.username}` 40 | } else if (this.last_name) { 41 | return `${this.first_name} ${this.last_name}` 42 | } 43 | return this.first_name 44 | } 45 | 46 | userSchema.methods.realNameWithHTML = function(bot, chatId) { 47 | return bot.getChatMember(chatId, this.id).then(res => { 48 | const user = res.user 49 | if (user.username) { 50 | return `@${user.username}` 51 | } 52 | return `${(user.first_name || 'User') 53 | .replace('<', '') 54 | .replace('>', '')}${ 55 | user.last_name 56 | ? ` ${user.last_name.replace('<', '').replace('>', '')}` 57 | : '' 58 | }` 59 | }) 60 | } 61 | 62 | /** Exports */ 63 | module.exports = mongoose.model('user', userSchema) 64 | -------------------------------------------------------------------------------- /nixpacks.toml: -------------------------------------------------------------------------------- 1 | [phases.install] 2 | cmds = ["yarn install"] 3 | 4 | [start] 5 | cmd = "yarn start" 6 | 7 | [variables] 8 | NODE_ENV = "production" -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "banofbot", 3 | "version": "0.0.1", 4 | "description": "Telegram bot to kick and ban users", 5 | "main": "app.js", 6 | "private": true, 7 | "license": "MIT", 8 | "engines": { 9 | "node": ">=6.4" 10 | }, 11 | "scripts": { 12 | "start": "node app.js", 13 | "localize": "node localize.js", 14 | "upload-translations": "node scripts/upload.js", 15 | "download-translations": "node scripts/download.js && yarn prettier --single-quote --no-semi --write ./helpers/localizations.js" 16 | }, 17 | "dependencies": { 18 | "bluebird": "^3.5.5", 19 | "botanio": "0.0.6", 20 | "dotenv": "^8.1.0", 21 | "easy-file-manager": "^0.1.3", 22 | "lodash": "^4.17.21", 23 | "mongoose": "^5.7.0", 24 | "node-telegram-bot-api": "^0.30.0", 25 | "nodemon": "^1.19.2", 26 | "semaphore-async-await": "^1.5.1" 27 | }, 28 | "devDependencies": { 29 | "axios": "^0.21.2", 30 | "eslint": "^6.3.0", 31 | "eslint-config-airbnb": "^18.0.1", 32 | "eslint-plugin-import": "^2.18.2", 33 | "eslint-plugin-jsx-a11y": "^6.2.3", 34 | "eslint-plugin-promise": "^4.2.1", 35 | "eslint-plugin-react": "^7.14.3", 36 | "flat": "^5.0.0", 37 | "prettier": "^2.0.5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /scripts/download.js: -------------------------------------------------------------------------------- 1 | const dotenv = require('dotenv') 2 | dotenv.config({ path: `${__dirname}/../.env` }) 3 | const axios = require('axios') 4 | const unflatten = require('flat').unflatten 5 | const fs = require('fs') 6 | 7 | ;(async function getTranslations() { 8 | console.log('==== Getting localizations') 9 | const translations = ( 10 | await axios.get('https://localizer.borodutch.com/localizations') 11 | ).data.filter((l) => { 12 | return l.tags.indexOf('banofbot') > -1 13 | }) 14 | console.log('==== Got localizations:') 15 | console.log(JSON.stringify(translations, undefined, 2)) 16 | // Get flattened map 17 | const flattenedMap = {} // { key: {en: '', ru: ''}} 18 | translations.forEach((t) => { 19 | const key = t.key 20 | const variants = t.variants.filter((v) => !!v.selected) 21 | flattenedMap[key] = variants.reduce((p, c) => { 22 | p[c.language] = c.text 23 | return p 24 | }, {}) 25 | }) 26 | console.log('==== Decoded response:') 27 | console.log(flattenedMap) 28 | const unflattened = unflatten(flattenedMap) 29 | console.log('==== Reversed and unflattened map') 30 | console.log(unflattened) 31 | fs.writeFileSync( 32 | `${__dirname}/../helpers/localizations.js`, 33 | `module.exports = ${JSON.stringify(unflattened, undefined, 2)}` 34 | ) 35 | console.log('==== Saved object to the file') 36 | })() 37 | -------------------------------------------------------------------------------- /scripts/upload.js: -------------------------------------------------------------------------------- 1 | const dotenv = require('dotenv') 2 | dotenv.config({ path: `${__dirname}/../.env` }) 3 | const axios = require('axios') 4 | const flatten = require('flat') 5 | const fs = require('fs') 6 | const localizations = require(`${__dirname}/../helpers/localizations.js`) 7 | 8 | const flattenedLocalizations = {} 9 | Object.keys(localizations).forEach((language) => { 10 | flattenedLocalizations[language] = flatten(localizations[language]) 11 | }) 12 | ;(async function postLocalizations() { 13 | console.log('==== Posting body:') 14 | console.log(JSON.stringify(flattenedLocalizations, undefined, 2)) 15 | try { 16 | await axios.post(`https://localizer.borodutch.com/localizations`, { 17 | // await axios.post(`http://localhost:1337/localizations`, { 18 | localizations: flattenedLocalizations, 19 | password: process.env.PASSWORD, 20 | username: 'borodutch', 21 | tags: ['banofbot'], 22 | }) 23 | console.error(`==== Body posted!`) 24 | } catch (err) { 25 | console.error(`==== Error posting: ${err.message}`) 26 | } 27 | })() 28 | --------------------------------------------------------------------------------