UNPKG

8.1 kBJavaScriptView Raw
1const { join } = require('path');
2const bttps = require(join(__dirname, 'bttps.js'));
3const fallbackListData = require('./fallbackListData.json');
4
5let listData;
6const listAge = new Date();
7let extendedLogging = false;
8let useBotblockAPI = true;
9
10/**
11 * @param {Object} apiKeys A JSON object formatted like: {"botlist name":"API Keys for that list", etc.} ; it also includes other metadata including sharddata
12 */
13const postToAllLists = async apiKeys => {
14 // make sure we have all lists we can post to and their apis
15 const currentDate = new Date();
16 if (!listData || listAge < currentDate) {
17 listAge.setDate(currentDate.getDate() + 1); // we try to update the listdata every day, in case new lists are added but the code is not restarted
18 listData = await bttps.get('https://botblock.org/api/lists').catch(e => console.error(`BLAPI: ${e}`));
19 if (!listData) {
20 console.error("BLAPI : Something went wrong when contacting BotBlock for the API of the lists, so we're using an older preset. Some lists might not be available because of this.");
21 listData = fallbackListData;
22 }
23 }
24 for (const listname in listData) {
25 if (apiKeys[listname] && (listData[listname]['api_post'] || listname === 'discordbots.org')) { // we even need to check this extra because botblock gives us nulls back
26 let list = listData[listname];
27 if (listname === 'discordbots.org') {
28 list = fallbackListData[listname];
29 }
30 const url = `https://${listname}`;
31 const apiPath = list.api_post.replace(url, '').replace(':id', apiKeys.bot_id);
32 // creating JSON object to send, reading out shard data
33 let sendObjString = `{ "${list.api_field}": ${apiKeys.server_count}`;
34 if (apiKeys.shard_id && list.api_shard_id) {
35 sendObjString += `, "${list.api_shard_id}": ${apiKeys.shard_id}`;
36 }
37 if (apiKeys.shard_count && list.api_shard_count) {
38 sendObjString += `, "${list.api_shard_count}": ${apiKeys.shard_count}`;
39 }
40 if (apiKeys.shards && list.api_shards) {
41 sendObjString += `, "${list.api_shards}": ${apiKeys.shards}`;
42 }
43 sendObjString += ' }';
44 const sendObj = JSON.parse(sendObjString);
45 bttps.post(listname, apiPath, apiKeys[listname], sendObj, extendedLogging).catch(e => console.error(`BLAPI: ${e}`));
46 }
47 }
48};
49
50/**
51 * @param {Client} client Discord.js client
52 * @param {Object} apiKeys A JSON object formatted like: {"botlist name":"API Keys for that list", etc.}
53 * @param {number} repeatInterval Number of minutes between each repetition
54 */
55const handleInternal = async (client, apiKeys, repeatInterval) => {
56 setTimeout(handleInternal.bind(null, client, apiKeys, repeatInterval), 60000 * repeatInterval); // call this function again in the next interval
57 if (client.user) {
58 /* eslint-disable camelcase */
59 apiKeys.bot_id = client.user.id;
60 // Checks if bot is sharded
61 if (client.shard && client.shard.id === 0) {
62 apiKeys.shard_count = client.shard.count;
63
64 // This will get as much info as it can, without erroring
65 const shardCounts = await client.shard.broadcastEval('this.guilds.size').catch(e => console.error('BLAPI: Error while fetching shard server counts:', e));
66 if (shardCounts.length !== client.shard.count) {
67 // If not all shards are up yet, we skip this run of handleInternal
68 return;
69 }
70
71 apiKeys.shards = shardCounts;
72 apiKeys.server_count = apiKeys.shards.reduce((prev, val) => prev + val, 0);
73
74 // Checks if bot is sharded with internal sharding
75 } else if (client.ws && client.ws.shards) {
76 apiKeys.shard_count = client.ws.shards.size;
77 // Get array of shards
78 const shardCounts = [];
79 client.ws.shards.forEach(shard => {
80 let count = 0;
81 client.guilds.forEach(g => {
82 if (g.shardID === shard.id) count++;
83 });
84 shardCounts.push(count);
85 });
86 if (shardCounts.length !== client.ws.shards.size) {
87 // If not all shards are up yet, we skip this run of handleInternal
88 console.log("BLAPI: Not all shards are up yet, so we're skipping this run.");
89 return;
90 }
91 apiKeys.shards = shardCounts;
92 apiKeys.server_count = apiKeys.shards.reduce((prev, val) => prev + val, 0);
93 } else {
94 apiKeys.server_count = client.guilds.size;
95 }
96 /* eslint-enable camelcase */
97 if (repeatInterval > 2 && useBotblockAPI) { // if the interval isnt below the BotBlock ratelimit, use their API
98 bttps.post('botblock.org', '/api/count', 'no key needed for this', apiKeys, extendedLogging).catch(e => console.error(`BLAPI: ${e}`));
99
100 // they blacklisted botblock, so we need to do this, posting their stats manually
101 if (apiKeys['discordbots.org']) {
102 const newApiKeys = {};
103 /* eslint-disable camelcase */
104 newApiKeys.bot_id = apiKeys.bot_id;
105 newApiKeys['discordbots.org'] = apiKeys['discordbots.org'];
106 newApiKeys.server_count = apiKeys.server_count;
107 if (apiKeys.shard_count) {
108 newApiKeys.shard_count = apiKeys.shard_count;
109 }
110 if (apiKeys.shards) {
111 newApiKeys.shards = apiKeys.shards;
112 }
113 /* eslint-enable camelcase */
114 postToAllLists(newApiKeys);
115 }
116 } else {
117 postToAllLists(apiKeys);
118 }
119 } else {
120 console.error(`BLAPI : Discord client seems to not be connected yet, so we're skipping this run of the post. We will try again in ${repeatInterval} minutes.`);
121 }
122};
123
124module.exports = {
125 /**
126 * This function is for automated use with discord.js
127 * @param {Client} discordClient Client via wich your code is connected to Discord
128 * @param {Object} apiKeys A JSON object formatted like: {"botlist name":"API Keys for that list", etc.}
129 * @param {integer} repeatInterval Number of minutes until you want to post again, leave out to use 30
130 */
131 handle: (discordClient, apiKeys, repeatInterval) => {
132 // handle inputs
133 if (!repeatInterval || repeatInterval < 1) repeatInterval = 30;
134 handleInternal(discordClient, apiKeys, repeatInterval);
135 },
136 /**
137 * For when you don't use discord.js or just want to post to manual times
138 * @param {integer} guildCount Integer value of guilds your bot is serving
139 * @param {string} botID Snowflake of the ID the user your bot is using
140 * @param {Object} apiKeys A JSON object formatted like: {"botlist name":"API Keys for that list", etc.}
141 * @param {integer} shardID (optional) The shard ID, which will be used to identify the shards valid for posting (and for super efficient posting with BLAPIs own distributer when not using botBlock)
142 * @param {integer} shardCount (optional) The number of shards the bot has, which is posted to the lists
143 * @param {[integer]} shards (optional) An array of guild counts of each single shard (this should be a complete list, and only a single shard will post it)
144 */
145 manualPost: (guildCount, botID, apiKeys, shardID, shardCount, shards) => {
146 /* eslint-disable camelcase */
147 apiKeys.bot_id = botID;
148 apiKeys.server_count = guildCount;
149 // check if we want to use sharding
150 if (shardID === 0 || (shardID && !shards)) { // if we don't have all the shard info in one place well try to post every shard itself
151 apiKeys.shard_id = shardID;
152 apiKeys.shard_count = shardCount;
153 if (shards) {
154 if (shards.length !== shardCount) {
155 console.error(`BLAPI: Shardcount (${shardCount}) does not equal the length of the shards array (${shards.length}).`);
156 return;
157 }
158 apiKeys.shards = shards;
159 apiKeys.server_count = apiKeys.shards.reduce((prev, val) => prev + val, 0);
160 }
161 /* eslint-enable camelcase */
162 }
163 if (useBotblockAPI) {
164 bttps.post('botblock.org', '/api/count', 'no key needed for this', apiKeys, extendedLogging).catch(e => console.error(`BLAPI: ${e}`));
165 } else {
166 postToAllLists(apiKeys);
167 }
168 },
169 setLogging: setLogging => {
170 extendedLogging = setLogging;
171 },
172 setBotblock: useBotblock => {
173 useBotblockAPI = useBotblock;
174 }
175};