aboutsummaryrefslogtreecommitdiff
path: root/build/hypixelApi.js
blob: 2377ecaa2c106303c3221f2dbea5616aac72d3b5 (plain)
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; }