├── .gitignore ├── LICENSE ├── Procfile ├── handlers ├── callback-query.js ├── group-config.js ├── help.js ├── index.js └── new-chat-members.js ├── i18n ├── en.json ├── es.json ├── fr.json └── pt.json ├── index.js ├── middleware ├── i18n.js ├── index.js └── keyboard.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env 3 | *.db 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 YouTwitFace 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 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: npm start 2 | -------------------------------------------------------------------------------- /handlers/callback-query.js: -------------------------------------------------------------------------------- 1 | module.exports = (bot, db) => { 2 | bot.action(/^unmute\.(\d+)$/, async ctx => { 3 | const clickedId = ctx.from.id; 4 | const unmuteId = Number(ctx.match[1]); 5 | 6 | if (clickedId !== unmuteId) { 7 | return ctx.answerCbQuery(await ctx.i18n(`user_must_click`)); 8 | } 9 | 10 | try { 11 | await ctx.restrictChatMember(clickedId, { 12 | until_date: (Date.now() + 86400000) / 1000, // 24 hours 13 | can_send_messages: true, 14 | }); 15 | } catch (err) { 16 | console.log(err); 17 | } 18 | 19 | ctx.deleteMessage(); 20 | 21 | if (ctx.callbackQuery.message) { 22 | const { reply_to_message: reply } = ctx.callbackQuery.message; 23 | 24 | ctx.deleteMessage(reply.message_id).catch(() => { 25 | /* Do nothing */ 26 | }); 27 | } 28 | }); 29 | 30 | bot.action(/^lang\.(\w+)$/, async ctx => { 31 | const [, lang] = ctx.match; 32 | const { status } = await ctx.getChatMember(ctx.from.id); 33 | 34 | if (![`creator`, `administrator`].includes(status)) { 35 | return ctx.answerCbQuery(); 36 | } 37 | 38 | db.update( 39 | { chat_id: ctx.chat.id }, 40 | { $set: { lang } }, 41 | { upsert: true }, 42 | ); 43 | 44 | ctx.answerCbQuery(await ctx.i18n(`language_updated`)); 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /handlers/group-config.js: -------------------------------------------------------------------------------- 1 | const Composer = require(`telegraf/composer`); 2 | const textToHtml = require(`@youtwitface/text-to-html`); 3 | 4 | module.exports = (bot, db) => { 5 | bot.command(`setwelcome`, Composer.admin(async ctx => { 6 | const { text, entities } = ctx.message; 7 | 8 | const parsedText = textToHtml(text, entities).slice(entities[0].length + 1); 9 | 10 | db.update( 11 | { chat_id: ctx.chat.id }, 12 | { $set: { welcome_message: parsedText } }, 13 | { upsert: true }, 14 | ); 15 | 16 | ctx.reply(await ctx.i18n(`welcome_message_updated`)); 17 | })); 18 | 19 | bot.command(`setlang`, Composer.admin(async ctx => { 20 | ctx.reply(await ctx.i18n(`select_lang`), { 21 | ...ctx.i18nButtons, 22 | reply_to_message_id: ctx.message.message_id, 23 | }); 24 | })); 25 | }; 26 | -------------------------------------------------------------------------------- /handlers/help.js: -------------------------------------------------------------------------------- 1 | const Composer = require(`telegraf/composer`); 2 | 3 | module.exports = bot => { 4 | bot.command([`start`, `help`], Composer.privateChat(async ctx => { 5 | ctx.reply(await ctx.i18n(`start`), { 6 | parse_mode: `markdown`, 7 | }); 8 | })); 9 | }; 10 | -------------------------------------------------------------------------------- /handlers/index.js: -------------------------------------------------------------------------------- 1 | const handlers = [ 2 | `help`, 3 | `group-config`, 4 | `new-chat-members`, 5 | `callback-query`, 6 | ]; 7 | 8 | module.exports = (bot, db) => 9 | handlers.forEach(handler => require(`./${handler}`)(bot, db)); 10 | -------------------------------------------------------------------------------- /handlers/new-chat-members.js: -------------------------------------------------------------------------------- 1 | module.exports = (bot, db) => { 2 | bot.on(`new_chat_members`, async ctx => { 3 | const { message_id } = ctx.message; 4 | const { first_name, id } = ctx.message.new_chat_member; 5 | const { title } = ctx.chat; 6 | 7 | if (id === ctx.botInfo.id) { 8 | const { user, status } = await ctx.getChatMember(ctx.from.id); 9 | 10 | if (![`creator`, `administrator`].includes(status)) { 11 | await ctx.reply( 12 | await ctx.i18n(`not_admin`, { 13 | first_name: user.first_name, 14 | user_id: user.id, 15 | }), 16 | { parse_mode: `markdown` }, 17 | ); 18 | 19 | await ctx.leaveChat(); 20 | } 21 | 22 | return; 23 | } 24 | 25 | try { 26 | await ctx.restrictChatMember(id, { 27 | can_send_messages: false, 28 | }); 29 | 30 | db.findOne({ chat_id: ctx.chat.id }, async (err, chat) => { 31 | if (err) { 32 | return console.log(err); 33 | } 34 | 35 | const welcomeMessage = chat && chat.welcome_message || await ctx.i18n(`welcome`, { first_name, title }); 36 | 37 | await ctx.reply(welcomeMessage, { 38 | ...await ctx.keyboard(), 39 | reply_to_message_id: message_id, 40 | parse_mode: `html`, 41 | }); 42 | }); 43 | } catch (err) { 44 | switch (err.description) { 45 | case `Bad Request: can't demote chat creator`: 46 | ctx.reply(await ctx.i18n(`creator`)); 47 | break; 48 | 49 | case `Bad Request: user is an administrator of the chat`: 50 | break; 51 | 52 | default: 53 | await ctx.reply(err.description); 54 | await ctx.leaveChat(); 55 | } 56 | } 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "start": "Hi, I can help you prevent spam attacks in your group. Just add me to your group with permission to ban and I will mute all new users until they prove their humanity. If you give also give me permission to delete messages, I can delete join messages after the user is unmuted.\n\nI'm made by [Twit 💩](tg://user?id=234480941) and you can also find my source code on [github](https://github.com/YouTwitFace/AntiUserbotBot).", 3 | "not_admin": "Hi [{{first_name}}](tg://user?id={{user_id}}). Thanks for adding me but you don't seem to be admin here so I will have to leave. Ask an admin to add me here :)", 4 | "welcome": "Hi {{first_name}}, welcome to {{title}}! For the safety of this chat, please confirm your humanity by clicking the button below this message.", 5 | "user_must_click": "The user must click the button themself.", 6 | "creator": "Why would you leave if you're the creator?", 7 | "not_a_bot": "I'm not a bot", 8 | "language_updated": "The group language has been updated. 👍", 9 | "welcome_message_updated": "I've updated the welcome message for this chat.", 10 | "select_lang": "Select a language:" 11 | } 12 | -------------------------------------------------------------------------------- /i18n/es.json: -------------------------------------------------------------------------------- 1 | { 2 | "start": "Hola, puedo ayudarte a prevenir ataques de spam en tu grupo. Solo agrégame a tu grupo con permisos para restringir y silenciaré a los nuevos usuarios hasta que verifiquen que son humanos. También puedes darme permisos para borrar mensajes ya que puedo borrar los mensajes de unión después de que el usuario haya sido restringido.\n\nHecho por [Twit 💩](tg://user?id=234480941) y puedes encontrar el código fuente en [github](https://github.com/YouTwitFace/AntiUserbotBot).", 3 | "not_admin": "Hola [{{first_name}}](tg://user?id={{user_id}}). Gracias por añadirme, pero parece que no eres el administrador del grupo, asi que tendré que salir. Pídele a un administrador que me añada en el grupo :)", 4 | "welcome": "Hola {{first_name}}, bienvenido a {{title}}! Por seguridad, debes confirmar que eres un humano pulsando el botón indicado debajo de este mensaje.", 5 | "user_must_click": "Solo el usuario indicado debe hacer clic en el botón", 6 | "creator": "¿Por qué te irías si eres el creador?", 7 | "not_a_bot": "No soy un bot", 8 | "language_updated": "El lenguaje del grupo ha sido actualizado", 9 | "welcome_message_updated": "Se ha actualizado el mensaje de bienvenida para este chat", 10 | "select_lang": "Selecciona el idioma:" 11 | } 12 | -------------------------------------------------------------------------------- /i18n/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "start": "Bonjour, je peux vous aider à prévenir les attaques de spam dans votre groupe. Ajoutez-moi à votre groupe avec la permission de bannir et je couperai la parole de tous les nouveaux utilisateurs jusqu'à ce qu'ils prouvent leur humanité. Si vous me donnez aussi la permission de supprimer des messages, je peux supprimer les messages joints après que l'utilisateur ne soit pas muet. Je suis fait par[Twit 💩](tg://user?id=234480941) et vous pouvez aussi trouver mon code source sur[github](https://github.com/YouTwitFace/AntiUserbotBot).", 3 | "not_admin": "Bonjour [{{first_name}}](tg://user?id={{user_id}}). Merci de m'avoir ajouté, mais vous n'avez pas l'air d'être de l'administration ici, alors je vais devoir partir. Demandez à un administrateur de m'ajouter ici :)", 4 | "welcome": "Salut {{first_name}}, bienvenue sur le groupe {{title}}! Pour la sécurité de ce chat, veuillez confirmer votre humanité en cliquant sur le bouton sous ce message.", 5 | "user_must_click": "L'utilisateur doit cliquer sur le bouton lui-même.", 6 | "creator": "Pourquoi partiriez-vous si vous êtes le créateur ?", 7 | "not_a_bot": "Je ne suis pas un robot" 8 | } 9 | -------------------------------------------------------------------------------- /i18n/pt.json: -------------------------------------------------------------------------------- 1 | { 2 | "start": "Oi, Eu posso ajudá-lo a combater ataques de spam no seu grupo. Me adicione ao grupo como administrador com permissão de banir usuários e eu irei dar mute todos os novos usuários até confirmar que eles não são robôs. Se você também me der permissão para deletar mensagens, irei deletar as mensagens de serviço informando que o usuário entrou, logo após ele provar não ser um robô.\n\nFeito por [Twit 💩](tg://user?id=234480941) e traduzido por [@gabriel](tg://user?id=91116586). Você também pode encontrar meu código no [github](https://github.com/YouTwitFace/AntiUserbotBot).", 3 | "not_admin": "Olá [{{first_name}}](tg://user?id={{user_id}}). Obrigado por me adicionar ao grupo, mas parece que você não é um dos administradores do grupo, então, terei que sair. Peça a um administrador para me adicionar aqui :)", 4 | "welcome": "Olá {{first_name}}, bem vindo ao {{title}}! Para a segurança do grupo, por favor confirme que você é realmente um humano clicando no botão abaixo dessa mensagem.", 5 | "user_must_click": "O próprio usuário que deve clicar no botão.", 6 | "creator": "Porque você sairia se você é o criador?", 7 | "not_a_bot": "Não sou um robô" 8 | } 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | `use strict`; 2 | 3 | require(`dotenv`).config(); 4 | 5 | const Telegraf = require(`telegraf`); 6 | const NeDB = require(`nedb`); 7 | 8 | const bot = new Telegraf(process.env.TOKEN); 9 | const db = new NeDB({ filename: `chats.db`, autoload: true }); 10 | 11 | bot.catch(console.log); 12 | 13 | require(`./middleware`)(bot, db); 14 | require(`./handlers`)(bot, db); 15 | 16 | bot.launch().then(() => { 17 | console.log(`@${bot.options.username} is running...`); 18 | }); 19 | -------------------------------------------------------------------------------- /middleware/i18n.js: -------------------------------------------------------------------------------- 1 | const Markup = require(`telegraf/markup`); 2 | const newI18n = require(`new-i18n`); 3 | 4 | const languages = process.env.I18N.split(`,`); 5 | const i18n = newI18n(`${__dirname}/../i18n`, languages, languages[0]); 6 | 7 | const langMap = { 8 | en: `🇺🇸 English`, 9 | es: `🇪🇸 Spanish`, 10 | fr: `🇫🇷 French`, 11 | pt: `🇵🇹 Portuguese`, 12 | }; 13 | 14 | module.exports = (bot, db) => { 15 | bot.context.i18n = function (keyword, variables) { 16 | return new Promise((resolve, reject) => { 17 | db.findOne({ chat_id: this.chat.id }, (error, chat) => { 18 | if (error) { 19 | reject(error); 20 | } else { 21 | resolve(i18n(chat && chat.lang, keyword, variables)); 22 | } 23 | }); 24 | }); 25 | }; 26 | 27 | const _languages = languages.map(lang => Markup.callbackButton(langMap[lang], `lang.${lang}`)); 28 | const buttons = []; 29 | 30 | while (_languages.length) { 31 | buttons.push(_languages.splice(0, 2)); 32 | } 33 | 34 | bot.context.i18nButtons = Markup.inlineKeyboard(buttons).extra(); 35 | }; 36 | -------------------------------------------------------------------------------- /middleware/index.js: -------------------------------------------------------------------------------- 1 | const handlers = [ 2 | `keyboard`, 3 | `i18n`, 4 | ]; 5 | 6 | module.exports = (bot, db) => 7 | handlers.forEach(handler => require(`./${handler}`)(bot, db)); 8 | -------------------------------------------------------------------------------- /middleware/keyboard.js: -------------------------------------------------------------------------------- 1 | const Markup = require(`telegraf/markup`); 2 | 3 | module.exports = bot => { 4 | bot.context.keyboard = async function () { 5 | const { id } = this.message.new_chat_member; 6 | 7 | return Markup.inlineKeyboard([ 8 | Markup.callbackButton(await this.i18n(`not_a_bot`), `unmute.${id}`), 9 | ]).extra(); 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antiuserbotbot", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@youtwitface/escape-html": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/@youtwitface/escape-html/-/escape-html-1.0.0.tgz", 10 | "integrity": "sha512-DShxe5oZWoDBFMrORGJMJNLwcfzl1X//4Utqkw5GLla5L0Ya9L731Nn2FjbPqjzq/y3JkHni1F1UMPbaV5RgBw==" 11 | }, 12 | "@youtwitface/text-to-html": { 13 | "version": "1.1.3", 14 | "resolved": "https://registry.npmjs.org/@youtwitface/text-to-html/-/text-to-html-1.1.3.tgz", 15 | "integrity": "sha512-71Hoq9i9uDluv3Hjx+ACKtCZVUreDgcxfuCvYiIpsjsHuCwLWf9PrAC1cOH9IuBBGq4EX5dvVwyWM2wMfIEJGQ==", 16 | "requires": { 17 | "@youtwitface/escape-html": "^1.0.0" 18 | } 19 | }, 20 | "async": { 21 | "version": "0.2.10", 22 | "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", 23 | "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E=" 24 | }, 25 | "binary-search-tree": { 26 | "version": "0.2.5", 27 | "resolved": "https://registry.npmjs.org/binary-search-tree/-/binary-search-tree-0.2.5.tgz", 28 | "integrity": "sha1-fbs7IQ/coIJFDa0jNMMErzm9x4Q=", 29 | "requires": { 30 | "underscore": "~1.4.4" 31 | } 32 | }, 33 | "debug": { 34 | "version": "4.1.1", 35 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 36 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 37 | "requires": { 38 | "ms": "^2.1.1" 39 | } 40 | }, 41 | "dotenv": { 42 | "version": "8.2.0", 43 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 44 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 45 | }, 46 | "immediate": { 47 | "version": "3.0.6", 48 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", 49 | "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" 50 | }, 51 | "lie": { 52 | "version": "3.1.1", 53 | "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", 54 | "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", 55 | "requires": { 56 | "immediate": "~3.0.5" 57 | } 58 | }, 59 | "localforage": { 60 | "version": "1.7.3", 61 | "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.7.3.tgz", 62 | "integrity": "sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ==", 63 | "requires": { 64 | "lie": "3.1.1" 65 | } 66 | }, 67 | "minimist": { 68 | "version": "1.2.5", 69 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 70 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 71 | }, 72 | "mkdirp": { 73 | "version": "0.5.5", 74 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 75 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 76 | "requires": { 77 | "minimist": "^1.2.5" 78 | } 79 | }, 80 | "module-alias": { 81 | "version": "2.2.2", 82 | "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", 83 | "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" 84 | }, 85 | "ms": { 86 | "version": "2.1.2", 87 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 88 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 89 | }, 90 | "nedb": { 91 | "version": "1.8.0", 92 | "resolved": "https://registry.npmjs.org/nedb/-/nedb-1.8.0.tgz", 93 | "integrity": "sha1-DjUCzYLABNU1WkPJ5VV3vXvZHYg=", 94 | "requires": { 95 | "async": "0.2.10", 96 | "binary-search-tree": "0.2.5", 97 | "localforage": "^1.3.0", 98 | "mkdirp": "~0.5.1", 99 | "underscore": "~1.4.4" 100 | } 101 | }, 102 | "new-i18n": { 103 | "version": "3.0.0-0", 104 | "resolved": "https://registry.npmjs.org/new-i18n/-/new-i18n-3.0.0-0.tgz", 105 | "integrity": "sha512-lkvyQ17oIYLkzKocY9/NGRY5IZ9T3VVBWOAlcidFKOJhC+EiooA39mKevS4EdXcWQHKxyCnjOSDkgkJVaVEO7A==" 106 | }, 107 | "node-fetch": { 108 | "version": "2.6.1", 109 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", 110 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" 111 | }, 112 | "sandwich-stream": { 113 | "version": "2.0.2", 114 | "resolved": "https://registry.npmjs.org/sandwich-stream/-/sandwich-stream-2.0.2.tgz", 115 | "integrity": "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==" 116 | }, 117 | "telegraf": { 118 | "version": "3.38.0", 119 | "resolved": "https://registry.npmjs.org/telegraf/-/telegraf-3.38.0.tgz", 120 | "integrity": "sha512-va4VlrKWp64JrowFoZX/NPzzA6q38kvaIukVXOWFO1V+jR1G8+hCfgJy4TX8Z3rwLJzwaBEet1QhikHDRZWl3A==", 121 | "requires": { 122 | "debug": "^4.0.1", 123 | "minimist": "^1.2.0", 124 | "module-alias": "^2.2.2", 125 | "node-fetch": "^2.2.0", 126 | "sandwich-stream": "^2.0.1", 127 | "telegram-typings": "^3.6.0" 128 | } 129 | }, 130 | "telegram-typings": { 131 | "version": "3.6.1", 132 | "resolved": "https://registry.npmjs.org/telegram-typings/-/telegram-typings-3.6.1.tgz", 133 | "integrity": "sha512-njVv1EAhIZnmQVLocZEADYUyqA1WIXuVcDYlsp+mXua/XB0pxx+PKtMSPeZ/EE4wPWTw9h/hA9ASTT6yQelkiw==" 134 | }, 135 | "underscore": { 136 | "version": "1.4.4", 137 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", 138 | "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antiuserbotbot", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/youtwitfacetg/antiuserbotbot.git" 12 | }, 13 | "author": "Twit", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/youtwitfacetg/antiuserbotbot/issues" 17 | }, 18 | "homepage": "https://github.com/youtwitfacetg/antiuserbotbot#readme", 19 | "dependencies": { 20 | "@youtwitface/text-to-html": "^1.1.3", 21 | "dotenv": "^8.2.0", 22 | "nedb": "^1.8.0", 23 | "new-i18n": "^3.0.0-0", 24 | "telegraf": "^3.38.0" 25 | } 26 | } 27 | --------------------------------------------------------------------------------