├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── src ├── Client.js └── index.js └── test └── index.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "globals": { 9 | "Atomics": "readonly", 10 | "SharedArrayBuffer": "readonly" 11 | }, 12 | "parserOptions": { 13 | "ecmaVersion": 2018 14 | }, 15 | "rules": { 16 | "indent": [ 17 | "error", 18 | "tab" 19 | ], 20 | "quotes": [ 21 | "error", 22 | "single" 23 | ], 24 | "semi": [ 25 | "error", 26 | "always" 27 | ], 28 | "no-console": "off" 29 | } 30 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # Extra 64 | package-lock.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jacob Gunther 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BotList 2 | A package for easily updating your server count on all Discord bot lists. 3 | 4 | ## Getting Started 5 | To install the package, you can simply run `npm i botlist` in your bot directory. After the installation is complete, you can append the code below to your bot, which you may need to modify depending on the structure of your bot. 6 | 7 | ```js 8 | const BotList = require('botlist'); 9 | 10 | const botID = 'xxx'; 11 | 12 | const client = new BotList.Client(botID, { 13 | tokens: { 14 | 'botlist.space': 'xxx', 15 | 'botsfordiscord.com': 'xxx', 16 | 'bots.ondiscord.xyz': 'xxx', 17 | 'botsparadiscord.xyz': 'xxx', 18 | 'carbonitex.net': 'xxx', 19 | 'dankbotlist.com': 'xxx', 20 | 'discordapps.dev': 'xxx', 21 | 'discord.boats': 'xxx', 22 | 'discordbots.org': 'xxx', 23 | 'discordbotlist.com': 'xxx', 24 | 'discordbotreviews.xyz': 'xxx', 25 | 'discordbot.world': 'xxx', 26 | 'discord.bots.gg': 'xxx', 27 | 'discordbotslist.us.to': 'xxx', 28 | 'discordbots.group': 'xxx', 29 | 'discord.services': 'xxx', 30 | 'discordsbestbots.xyz': 'xxx', 31 | 'discordbots.fun': 'xxx', 32 | 'divinediscordbots.com': 'xxx', 33 | 'lbots.org': 'xxx', 34 | 'mythicalbots.xyz': 'xxx', 35 | 'wonderbotlist.com': 'xxx', 36 | 'botlist.co': 'xxx', 37 | 'thereisabotforthat.com': 'xxx' 38 | // You can append more tokens here if more bot list websites exist on BotBlock 39 | }, 40 | interval: 1000 * 30, // The interval (in milliseconds) to post to every list 41 | verbose: false // Logs posting errors to console 42 | }); 43 | 44 | client.on('beforePost', () => { 45 | // This event will be fired every interval that is defined in the client constructor. If the client isn't ready yet, you can simply return before calling Client#update(). This will send the previous server/shard count instead. 46 | 47 | if (!bot.ready) return; 48 | 49 | const serverCount = bot.guilds.size; 50 | const shards = bot.shards.size; 51 | 52 | client.update(serverCount, shards); // Instead of `shards` being a number, you can use the following format to post from an individual shard instead (count means server count from this shard): { id: 0, count: 25 } 53 | }); 54 | 55 | client.on('afterPost', (successful, failed) => { 56 | console.log('Just finished posting to all bot lists, ' + successful + ' were successful, ' + failed + ' failed to post'); 57 | }); 58 | 59 | client.on('error', (error) => { 60 | // There was an error posting to one of the lists, the `error` variable will provide details from the node-fetch package about the error. 61 | 62 | console.warn('Something happened', error); 63 | }); 64 | 65 | // Once the bot is ready, you can call this function to start the interval loop. 66 | client.start(); 67 | 68 | // If the bot disconnects, you can call this to stop it from running. 69 | client.stop(); 70 | ``` 71 | 72 | ## Documentation 73 | There is no documentation because the code above is thoroughly documented using comments. If you need any assistance, you can join our *makeshift* support server: [https://discord.gg/GjEWBQE](https://discord.gg/GjEWBQE). -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "botlist", 3 | "version": "1.0.1", 4 | "description": "A package for easily updating your server count on all Discord bot lists.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/PassTheMayo/BotList.git" 12 | }, 13 | "keywords": [ 14 | "bot", 15 | "list", 16 | "discord", 17 | "server", 18 | "count" 19 | ], 20 | "author": "PassTheMayo", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/PassTheMayo/BotList/issues" 24 | }, 25 | "homepage": "https://github.com/PassTheMayo/BotList#readme", 26 | "dependencies": { 27 | "node-fetch": "^2.6.0" 28 | } 29 | } -------------------------------------------------------------------------------- /src/Client.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); 2 | const EventEmitter = require('events').EventEmitter; 3 | 4 | class Client extends EventEmitter { 5 | constructor(id, options) { 6 | super(); 7 | 8 | this._id = id; 9 | this._lists = {}; 10 | this._interval = null; 11 | this._isRunning = false; 12 | this._serverCount = null; 13 | this._shards = null; 14 | this._options = Object.assign({ tokens: {}, interval: 1000 * 30, verbose: false }, !!options && options.constructor === Object ? options : {}); 15 | 16 | this._fetchLists(); 17 | this._fetchInterval = setInterval(this._fetchLists.bind(this), 1000 * 60 * 5); 18 | 19 | if (this._options.interval > Math.pow(2, 31)) throw new Error('The provided interval (' + this.options.interval + ') is over the maximum safe integer of ' + Math.pow(2, 31)); 20 | } 21 | 22 | start(callback) { 23 | if (!this || this.constructor !== Client) throw new Error('The Client#start() method was called from a bound context, please call it normally'); 24 | 25 | if (this._isRunning) { 26 | if (this._options.verbose) console.warn('Attempted to call Client#start() but the client has already started'); 27 | if (callback) return callback(new Error('Attempted to call Client#start() but the client has already started')); 28 | return Promise.reject(new Error('Attempted to call Client#start() but the client has already started')); 29 | } else { 30 | this.emit('start', true); 31 | 32 | this._isRunning = true; 33 | this._interval = setInterval(() => { 34 | this.emit('beforePost', Date.now()); 35 | 36 | this._sendRequests(); 37 | }, this._options.interval); 38 | } 39 | } 40 | 41 | update(serverCount, shards) { 42 | if (!this || this.constructor !== Client) throw new Error('The Client#update() method was called from a bound context, please call it normally'); 43 | 44 | this._serverCount = serverCount; 45 | this._shards = shards; 46 | } 47 | 48 | stop(callback) { 49 | if (!this || this.constructor !== Client) throw new Error('The Client#stop() method was called from a bound context, please call it normally'); 50 | 51 | if (this._isRunning) { 52 | this.emit('stop', true); 53 | clearInterval(this._interval); 54 | this._isRunning = false; 55 | this._interval = null; 56 | } else { 57 | if (this._options.verbose) console.warn('Attempted to call Client#stop() but the client has already stopped'); 58 | if (callback) return callback(new Error('Attempted to call Client#stop() but the client has already stopped')); 59 | return Promise.reject(new Error('Attempted to call Client#stop() but the client has already stopped')); 60 | } 61 | } 62 | 63 | _fetchLists() { 64 | fetch('https://botblock.org/api/lists') 65 | .then(res => res.json()) 66 | .then(res => { 67 | this._lists = res; 68 | }) 69 | .catch(err => { 70 | this.emit('error', err); 71 | 72 | if (Object.keys(this._lists).length < 1) { 73 | setTimeout(this._fetchLists.bind(this), 1000 * 5); 74 | } 75 | }); 76 | } 77 | 78 | _sendRequests() { 79 | const tokens = Object.entries(this._options.tokens).filter(entry => this._lists.hasOwnProperty(entry[0]) && this._lists[entry[0]].api_post); 80 | let index = 0; 81 | let successful = 0; 82 | let failed = 0; 83 | 84 | const nextSite = () => { 85 | if (index >= tokens.length) { 86 | this.emit('afterPost', successful, failed); 87 | } else { 88 | const token = tokens[index]; 89 | const list = this._lists[token[0]]; 90 | 91 | const body = {}; 92 | 93 | body[list.api_field] = this._serverCount; 94 | 95 | if (list.api_shards && !!this._shards && this._shards.constructor === Array) { 96 | body[list.api_shards] = this._shards; 97 | } 98 | 99 | if (list.api_shard_id && !!this._shards && this._shards.constructor === Object && this._shards.hasOwnProperty('id')) { 100 | body[list.api_shard_id] = this._shards.id; 101 | } 102 | 103 | if (list.api_shard_count && !!this._shards && this._shards.constructor === Object && this._shards.hasOwnProperty('count')) { 104 | body[list.api_shard_count] = this._shards.id; 105 | } 106 | 107 | fetch(list.api_post.replace(/:id/g, this._id), { 108 | method: 'POST', 109 | headers: { 'Content-Type': 'application/json', Authorization: token[1] }, 110 | body: JSON.stringify(body) 111 | }) 112 | .then(res => { 113 | if (res.status < 300) { 114 | this.emit('post', token[0]); 115 | successful++; 116 | } else { 117 | this.emit('error', res); 118 | failed++; 119 | } 120 | 121 | index++; 122 | nextSite(); 123 | }) 124 | .catch(err => { 125 | this.emit('error', err); 126 | failed++; 127 | 128 | index++; 129 | nextSite(); 130 | }); 131 | } 132 | }; 133 | 134 | nextSite(); 135 | } 136 | } 137 | 138 | module.exports = Client; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const Client = require('./Client'); 2 | 3 | module.exports = { 4 | Client 5 | }; -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const BotList = require('../src'); 2 | 3 | const client = new BotList.Client('527964667721678897', { tokens: { 'botlist.space': 'haha no' }, interval: 5000 }); 4 | 5 | client.on('post', (site) => { 6 | console.log('post', site); 7 | }); 8 | 9 | client.on('error', (err) => { 10 | console.log(err); 11 | }); 12 | 13 | client.on('beforePost', () => { 14 | client.update(Math.floor(Math.random() * 20000), []); 15 | }); 16 | 17 | client.on('afterPost', (success, failed) => { 18 | console.log(success, failed); 19 | }); 20 | 21 | client.start(); --------------------------------------------------------------------------------