1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
/**
* Fetch the raw Hypixel API
*/
import fetch from 'node-fetch';
import { jsonToQuery, shuffle } from './util.js';
import { Agent } from 'https';
if (!process.env.hypixel_keys)
// if there's no hypixel keys in env, run dotenv
(await import('dotenv')).config();
// We need to create an agent to prevent memory leaks and to only do dns lookups once
const httpsAgent = new Agent({
keepAlive: true
});
/** This array should only ever contain one item because using multiple hypixel api keys isn't allowed :) */
const apiKeys = process.env?.hypixel_keys?.split(' ') ?? [];
const apiKeyUsage = {};
const baseHypixelAPI = 'https://api.hypixel.net';
/** Choose the best current API key */
export function chooseApiKey() {
// find the api key with the lowest amount of uses
let bestKeyUsage = null;
let bestKey = null;
for (let key of shuffle(apiKeys.slice())) {
const keyUsage = apiKeyUsage[key];
// if the key has never been used before, use it
if (!keyUsage)
return key;
// if the key has reset since the last use, set the remaining count to the default
if (Date.now() > keyUsage.reset)
keyUsage.remaining = keyUsage.limit;
// if this key has more uses remaining than the current known best one, save it
if (bestKeyUsage === null || keyUsage.remaining > bestKeyUsage.remaining) {
bestKeyUsage = keyUsage;
bestKey = key;
}
}
return bestKey;
}
export function getKeyUsage() {
let keyLimit = 0;
let keyUsage = 0;
for (let key of Object.values(apiKeyUsage)) {
keyLimit += key.limit;
keyUsage += key.limit - key.remaining;
}
return {
limit: keyLimit,
usage: keyUsage
};
}
/** Send an HTTP request to the Hypixel API */
export let sendApiRequest = async function sendApiRequest({ path, key, args }) {
// Send a raw http request to api.hypixel.net, and return the parsed json
if (key)
// If there's an api key, add it to the arguments
args.key = key;
// Construct a url from the base api url, path, and arguments
const fetchUrl = baseHypixelAPI + '/' + path + '?' + jsonToQuery(args);
let fetchResponse;
let fetchJsonParsed;
try {
fetchResponse = await fetch(fetchUrl, { agent: () => httpsAgent });
fetchJsonParsed = await fetchResponse.json();
}
catch {
// if there's an error, wait a second and try again
await new Promise((resolve) => setTimeout(resolve, 1000));
return await sendApiRequest({ path, key, args });
}
// bruh
if (fetchJsonParsed.cause === 'This endpoint is currently disabled') {
await new Promise((resolve) => setTimeout(resolve, 30000));
return await sendApiRequest({ path, key, args });
}
// if the cause is "Invalid API key", remove the key from the list of keys and try again
if (fetchJsonParsed.cause === 'Invalid API key') {
apiKeys.splice(apiKeys.indexOf(key), 1);
console.log(`${key} is invalid, removing it from the list of keys`);
return await sendApiRequest({ path, key: null, args });
}
if (fetchResponse.headers.get('ratelimit-limit'))
// remember how many uses it has
apiKeyUsage[key] = {
remaining: parseInt(fetchResponse.headers.get('ratelimit-remaining') ?? '0'),
limit: parseInt(fetchResponse.headers.get('ratelimit-limit') ?? '0'),
reset: Date.now() + parseInt(fetchResponse.headers.get('ratelimit-reset') ?? '0') * 1000
};
if (fetchJsonParsed.throttle) {
if (apiKeyUsage[key])
apiKeyUsage[key].remaining = 0;
// if it's throttled, wait 10 seconds and try again
await new Promise((resolve) => setTimeout(resolve, 10000));
return await sendApiRequest({ path, key, args });
}
return fetchJsonParsed;
};
// this is necessary for mocking in the tests because es6
export function mockSendApiRequest($value) { sendApiRequest = $value; }
|