UNPKG

9.62 kBPlain TextView Raw
1import { get, post } from './bttps';
2import { fallbackData } from './fallbackListData';
3import DiscordJSClientFallback from '../types/discord.js';
4
5let listData: any; // TODO add type
6const listAge = new Date();
7let extendedLogging = false;
8let useBotblockAPI = true;
9
10// TODO in general: remove all "any" types and replace them by real types
11// TODO type
12type apiKeysObject = any;
13
14/**
15 * @param apiKeys A JSON object formatted like: {"botlist name":"API Keys for that list", etc.} ;
16 * it also includes other metadata including sharddata
17 */
18async function postToAllLists(apiKeys: apiKeysObject): Promise<void> {
19 // make sure we have all lists we can post to and their apis
20 const currentDate = new Date();
21 if (!listData || listAge < currentDate) {
22 // we try to update the listdata every day
23 // in case new lists are added but the code is not restarted
24 listAge.setDate(currentDate.getDate() + 1);
25 try {
26 const tmpListData = await get('https://botblock.org/api/lists');
27 // make sure we only save it if nothing goes wrong
28 if (tmpListData) {
29 listData = tmpListData;
30 } else {
31 throw new Error('Got empty list from botblock.');
32 }
33 } catch (e) {
34 console.error(`BLAPI: ${e}`);
35 console.error(
36 "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.",
37 );
38 }
39 }
40 Object.entries(listData).forEach(([listname]) => {
41 if (
42 apiKeys[listname]
43 && (listData[listname].api_post || listname === 'discordbots.org')
44 ) {
45 // we even need to check this extra because botblock gives us nulls back
46 let list = listData[listname];
47 if (listname === 'discordbots.org') {
48 list = fallbackData[listname];
49 }
50 const apiPath = list.api_post.replace(':id', apiKeys.bot_id);
51 // creating JSON object to send, reading out shard data
52 const sendObj: any = {}; // TODO type
53 sendObj[list.api_field] = apiKeys.server_count;
54 if (apiKeys.shard_id && list.api_shard_id) {
55 sendObj[list.api_shard_id] = apiKeys.shard_id;
56 }
57 if (apiKeys.shard_count && list.api_shard_count) {
58 sendObj[list.api_shard_count] = apiKeys.shard_count;
59 }
60 if (apiKeys.shards && list.api_shards) {
61 sendObj[list.api_shards] = apiKeys.shards;
62 }
63
64 post(apiPath, apiKeys[listname], sendObj, extendedLogging).catch((e) => console.error(`BLAPI: ${e}`));
65 }
66 });
67}
68
69/**
70 * @param client Discord.js client
71 * @param apiKeys A JSON object formatted like: {"botlist name":"API Keys for that list", etc.}
72 * @param repeatInterval Number of minutes between each repetition
73 */
74async function handleInternal(
75 client: DiscordJSClientFallback,
76 apiKeys: apiKeysObject,
77 repeatInterval: number,
78): Promise<void> {
79 setTimeout(/* eslint-disable-next-line @typescript-eslint/no-misused-promises */
80 handleInternal.bind(null, client, apiKeys, repeatInterval),
81 60000 * repeatInterval,
82 ); // call this function again in the next interval
83 let unchanged;
84 /* eslint-disable no-param-reassign */
85 if (client.user) {
86 /* eslint-disable camelcase */
87 apiKeys.bot_id = client.user.id;
88
89 // Checks if bot is sharded
90 if (client.shard && client.shard.id === 0) {
91 apiKeys.shard_count = client.shard.count;
92
93 // This will get as much info as it can, without erroring
94 try {
95 const _: Array<number> = await client.shard.broadcastEval(
96 'this.guilds.size ? client.guilds.size : client.guilds.cache.size',
97 );
98 const shardCounts = _.filter((count: number) => count !== 0);
99 if (shardCounts.length !== client.shard.count) {
100 // If not all shards are up yet, we skip this run of handleInternal
101 return;
102 }
103 apiKeys.shards = shardCounts;
104 apiKeys.server_count = apiKeys.shards.reduce(
105 (prev: number, val: number) => prev + val,
106 0,
107 );
108
109 /* eslint-enable no-param-reassign */
110 } catch (e) {
111 console.error('BLAPI: Error while fetching shard server counts:', e);
112 }
113 // Checks if bot is sharded with internal sharding
114 } else if (!client.shard) {
115 /*
116 else if (client.ws && client.ws.shards) {
117 apiKeys.shard_count = client.ws.shards.size;
118 // Get array of shards
119 const shardCounts:Array<number> = [];
120 client.ws.shards.forEach(shard => {
121 let count = 0;
122 client.guilds.forEach(g => {
123 if (g.shardID === shard.id) count++;
124 });
125 shardCounts.push(count);
126 });
127 if (shardCounts.length !== client.ws.shards.size) {
128 // If not all shards are up yet, we skip this run of handleInternal
129 console.log(
130 "BLAPI: Not all shards are up yet, so we're skipping this run."
131 );
132 return;
133 }
134 apiKeys.shards = shardCounts;
135 apiKeys.server_count = apiKeys.shards.reduce(
136 (prev:number, val:number) => prev + val,
137 0
138 );
139 // Check if bot is not sharded at all, but still wants to send server count
140 // (it's recommended to shard your bot, even if it's only one shard)
141 } */
142 /* eslint-disable-next-line no-param-reassign */
143 apiKeys.server_count = client.guilds instanceof Map ? client.guilds.size : client.guilds.cache.size;
144 } else {
145 unchanged = true;
146 } // nothing has changed, therefore we don't send any data
147 if (!unchanged) {
148 if (repeatInterval > 2 && useBotblockAPI) {
149 // if the interval isnt below the BotBlock ratelimit, use their API
150 post(
151 'https://botblock.org/api/count',
152 'no key needed for this',
153 apiKeys,
154 extendedLogging,
155 ).catch((e) => console.error(`BLAPI: ${e}`));
156
157 // they blacklisted botblock, so we need to do this, posting their stats manually
158 if (apiKeys['discordbots.org']) {
159 const newApiKeys: apiKeysObject = {};
160 newApiKeys.bot_id = apiKeys.bot_id;
161 newApiKeys['discordbots.org'] = apiKeys['discordbots.org'];
162 newApiKeys.server_count = apiKeys.server_count;
163 if (apiKeys.shard_count) {
164 newApiKeys.shard_count = apiKeys.shard_count;
165 }
166 if (apiKeys.shards) {
167 newApiKeys.shards = apiKeys.shards;
168 }
169 postToAllLists(newApiKeys);
170 }
171 } else {
172 postToAllLists(apiKeys);
173 }
174 }
175 } else {
176 console.error(
177 `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.`,
178 );
179 }
180}
181
182/**
183 * This function is for automated use with discord.js
184 * @param discordClient Client via wich your code is connected to Discord
185 * @param apiKeys A JSON object formatted like: {"botlist name":"API Keys for that list", etc.}
186 * @param repeatInterval Number of minutes until you want to post again, leave out to use 30
187 */
188export function handle(
189 discordClient: DiscordJSClientFallback,
190 apiKeys: apiKeysObject,
191 repeatInterval: number,
192): Promise<void> {
193 // handle inputs
194 /* eslint-disable-next-line no-param-reassign */
195 if (!repeatInterval || repeatInterval < 1) repeatInterval = 30;
196 return handleInternal(discordClient, apiKeys, repeatInterval);
197}
198
199/**
200 * For when you don't use discord.js or just want to post to manual times
201 * @param guildCount Integer value of guilds your bot is serving
202 * @param botID Snowflake of the ID the user your bot is using
203 * @param apiKeys A JSON object formatted like: {"botlist name":"API Keys for that list", etc.}
204 * @param shardID (optional) The shard ID, which will be used to identify the
205 * shards valid for posting
206 * (and for super efficient posting with BLAPIs own distributer when not using botBlock)
207 * @param shardCount (optional) The number of shards the bot has, which is posted to the lists
208 * @param shards (optional) An array of guild counts of each single shard
209 * (this should be a complete list, and only a single shard will post it)
210 */
211export function manualPost(
212 guildCount: number,
213 botID: string,
214 apiKeys: apiKeysObject,
215 shardID: number,
216 shardCount: number,
217 shards: Array<number>,
218): void {
219 /* eslint-disable no-param-reassign */
220 apiKeys.bot_id = botID;
221 apiKeys.server_count = guildCount;
222 // check if we want to use sharding
223 if (shardID === 0 || (shardID && !shards)) {
224 // if we don't have all the shard info in one place well try to post every shard itself
225 apiKeys.shard_id = shardID;
226 apiKeys.shard_count = shardCount;
227 if (shards) {
228 if (shards.length !== shardCount) {
229 console.error(
230 `BLAPI: Shardcount (${shardCount}) does not equal the length of the shards array (${shards.length}).`,
231 );
232 return;
233 }
234 apiKeys.shards = shards;
235 apiKeys.server_count = apiKeys.shards.reduce(
236 (prev: number, val: number) => prev + val,
237 0,
238 );
239 }
240 /* eslint-enable camelcase */
241 }
242 /* eslint-enable no-param-reassign */
243 if (useBotblockAPI) {
244 post(
245 'https://botblock.org/api/count',
246 'no key needed for this',
247 apiKeys,
248 extendedLogging,
249 ).catch((e) => console.error(`BLAPI: ${e}`));
250 } else {
251 postToAllLists(apiKeys);
252 }
253}
254
255export function setLogging(setLog: boolean): void {
256 extendedLogging = setLog;
257}
258
259export function setBotblock(useBotblock: boolean): void {
260 useBotblockAPI = useBotblock;
261}