UNPKG

8.14 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 sendObj = {};
34 sendObj[list.api_field] = apiKeys.server_count;
35 if (apiKeys.shard_id && list.api_shard_id) {
36 sendObj[list.api_shard_id] = apiKeys.shard_id;
37 }
38 if (apiKeys.shard_count && list.api_shard_count) {
39 sendObj[list.api_shard_count] = apiKeys.shard_count;
40 }
41 if (apiKeys.shards && list.api_shards) {
42 sendObj[list.api_shards] = apiKeys.shards;
43 }
44 bttps.post(listname, apiPath, apiKeys[listname], sendObj, extendedLogging).catch(e => console.error(`BLAPI: ${e}`));
45 }
46 }
47};
48
49/**
50 * @param {Client} client Discord.js client
51 * @param {Object} apiKeys A JSON object formatted like: {"botlist name":"API Keys for that list", etc.}
52 * @param {number} repeatInterval Number of minutes between each repetition
53 */
54const handleInternal = async (client, apiKeys, repeatInterval) => {
55 setTimeout(handleInternal.bind(null, client, apiKeys, repeatInterval), 60000 * repeatInterval); // call this function again in the next interval
56 if (client.user) {
57 /* eslint-disable camelcase */
58 apiKeys.bot_id = client.user.id;
59 // Checks if bot is sharded
60 if (client.shard && client.shard.id === 0) {
61 apiKeys.shard_count = client.shard.count;
62
63 // This will get as much info as it can, without erroring
64 const shardCounts = await client.shard.broadcastEval('this.guilds.size').catch(e => console.error('BLAPI: Error while fetching shard server counts:', e));
65 if (shardCounts.length !== client.shard.count) {
66 // If not all shards are up yet, we skip this run of handleInternal
67 return;
68 }
69
70 apiKeys.shards = shardCounts;
71 apiKeys.server_count = apiKeys.shards.reduce((prev, val) => prev + val, 0);
72
73 // Checks if bot is sharded with internal sharding
74 } else if (client.ws && client.ws.shards) {
75 apiKeys.shard_count = client.ws.shards.size;
76 // Get array of shards
77 const shardCounts = [];
78 client.ws.shards.forEach(shard => {
79 let count = 0;
80 client.guilds.forEach(g => {
81 if (g.shardID === shard.id) count++;
82 });
83 shardCounts.push(count);
84 });
85 if (shardCounts.length !== client.ws.shards.size) {
86 // If not all shards are up yet, we skip this run of handleInternal
87 console.log("BLAPI: Not all shards are up yet, so we're skipping this run.");
88 return;
89 }
90 apiKeys.shards = shardCounts;
91 apiKeys.server_count = apiKeys.shards.reduce((prev, val) => prev + val, 0);
92 } else {
93 apiKeys.server_count = client.guilds.size;
94 }
95 /* eslint-enable camelcase */
96 if (repeatInterval > 2 && useBotblockAPI) { // if the interval isnt below the BotBlock ratelimit, use their API
97 bttps.post('botblock.org', '/api/count', 'no key needed for this', apiKeys, extendedLogging).catch(e => console.error(`BLAPI: ${e}`));
98
99 // they blacklisted botblock, so we need to do this, posting their stats manually
100 if (apiKeys['discordbots.org']) {
101 const newApiKeys = {};
102 /* eslint-disable camelcase */
103 newApiKeys.bot_id = apiKeys.bot_id;
104 newApiKeys['discordbots.org'] = apiKeys['discordbots.org'];
105 newApiKeys.server_count = apiKeys.server_count;
106 if (apiKeys.shard_count) {
107 newApiKeys.shard_count = apiKeys.shard_count;
108 }
109 if (apiKeys.shards) {
110 newApiKeys.shards = apiKeys.shards;
111 }
112 /* eslint-enable camelcase */
113 postToAllLists(newApiKeys);
114 }
115 } else {
116 postToAllLists(apiKeys);
117 }
118 } else {
119 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.`);
120 }
121};
122
123module.exports = {
124 /**
125 * This function is for automated use with discord.js
126 * @param {Client} discordClient Client via wich your code is connected to Discord
127 * @param {Object} apiKeys A JSON object formatted like: {"botlist name":"API Keys for that list", etc.}
128 * @param {integer} repeatInterval Number of minutes until you want to post again, leave out to use 30
129 */
130 handle: (discordClient, apiKeys, repeatInterval) => {
131 // handle inputs
132 if (!repeatInterval || repeatInterval < 1) repeatInterval = 30;
133 handleInternal(discordClient, apiKeys, repeatInterval);
134 },
135 /**
136 * For when you don't use discord.js or just want to post to manual times
137 * @param {integer} guildCount Integer value of guilds your bot is serving
138 * @param {string} botID Snowflake of the ID the user your bot is using
139 * @param {Object} apiKeys A JSON object formatted like: {"botlist name":"API Keys for that list", etc.}
140 * @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)
141 * @param {integer} shardCount (optional) The number of shards the bot has, which is posted to the lists
142 * @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)
143 */
144 manualPost: (guildCount, botID, apiKeys, shardID, shardCount, shards) => {
145 /* eslint-disable camelcase */
146 apiKeys.bot_id = botID;
147 apiKeys.server_count = guildCount;
148 // check if we want to use sharding
149 if (shardID === 0 || (shardID && !shards)) { // if we don't have all the shard info in one place well try to post every shard itself
150 apiKeys.shard_id = shardID;
151 apiKeys.shard_count = shardCount;
152 if (shards) {
153 if (shards.length !== shardCount) {
154 console.error(`BLAPI: Shardcount (${shardCount}) does not equal the length of the shards array (${shards.length}).`);
155 return;
156 }
157 apiKeys.shards = shards;
158 apiKeys.server_count = apiKeys.shards.reduce((prev, val) => prev + val, 0);
159 }
160 /* eslint-enable camelcase */
161 }
162 if (useBotblockAPI) {
163 bttps.post('botblock.org', '/api/count', 'no key needed for this', apiKeys, extendedLogging).catch(e => console.error(`BLAPI: ${e}`));
164 } else {
165 postToAllLists(apiKeys);
166 }
167 },
168 setLogging: setLogging => {
169 extendedLogging = setLogging;
170 },
171 setBotblock: useBotblock => {
172 useBotblockAPI = useBotblock;
173 }
174};