aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build/cleaners/skyblock/slayers.js30
-rw-r--r--build/cleaners/skyblock/stats.js1
-rw-r--r--build/constants.js47
-rw-r--r--build/database.js44
-rw-r--r--build/hypixelApi.js10
-rw-r--r--build/mojang.js24
-rw-r--r--src/cleaners/skyblock/slayers.ts37
-rw-r--r--src/cleaners/skyblock/stats.ts1
-rw-r--r--src/constants.ts52
-rw-r--r--src/database.ts48
-rw-r--r--src/hypixelApi.ts17
-rw-r--r--src/mojang.ts38
12 files changed, 276 insertions, 73 deletions
diff --git a/build/cleaners/skyblock/slayers.js b/build/cleaners/skyblock/slayers.js
index d2b9dbe..dbc20fc 100644
--- a/build/cleaners/skyblock/slayers.js
+++ b/build/cleaners/skyblock/slayers.js
@@ -1,52 +1,64 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.cleanSlayers = void 0;
-const slayerLevels = 4; // number of slayer levels, this might be 5 soon
+exports.cleanSlayers = exports.slayerLevels = void 0;
+exports.slayerLevels = 4; // number of slayer levels, this might be 5 soon
const SLAYER_NAMES = {
spider: 'tarantula',
zombie: 'revenant',
wolf: 'sven'
};
function cleanSlayers(data) {
+ var _a, _b;
const slayers = [];
const slayersDataRaw = data === null || data === void 0 ? void 0 : data.slayer_bosses;
let totalXp = 0;
+ let totalKills = 0;
for (const slayerNameRaw in slayersDataRaw) {
const slayerDataRaw = slayersDataRaw[slayerNameRaw];
// convert name provided by api (spider) to the real name (tarantula)
- const slayerName = SLAYER_NAMES[slayerDataRaw];
- const slayerXp = slayerDataRaw.xp;
+ const slayerName = SLAYER_NAMES[slayerNameRaw];
+ const slayerXp = (_a = slayerDataRaw.xp) !== null && _a !== void 0 ? _a : 0;
+ let slayerKills = 0;
const slayerTiers = [];
for (const slayerDataKey in slayerDataRaw) {
// if a key starts with boss_kills_tier_ (boss_kills_tier_1), get the last number
if (slayerDataKey.startsWith('boss_kills_tier_')) {
const slayerTierRaw = parseInt(slayerDataKey.substr('boss_kills_tier_'.length));
- const slayerTierKills = slayerDataRaw[slayerDataKey];
+ const slayerTierKills = (_b = slayerDataRaw[slayerDataKey]) !== null && _b !== void 0 ? _b : 0;
// add 1 since hypixel is using 0 indexed tiers
const slayerTier = slayerTierRaw + 1;
slayerTiers.push({
kills: slayerTierKills,
tier: slayerTier
});
+ // count up the total number of kills for this slayer
+ if (slayerTierKills)
+ slayerKills += slayerTierKills;
}
}
// if the slayer tier length is less than the max, add more empty ones
- while (slayerTiers.length < slayerLevels)
+ while (slayerTiers.length < exports.slayerLevels)
slayerTiers.push({
tier: slayerTiers.length + 1,
kills: 0
});
const slayer = {
name: slayerName,
+ raw_name: slayerNameRaw,
tiers: slayerTiers,
- xp: slayerXp
+ xp: slayerXp !== null && slayerXp !== void 0 ? slayerXp : 0,
+ kills: slayerKills
};
slayers.push(slayer);
- // add the xp from this slayer to the total xp
- totalXp += slayerXp;
+ // add the xp and kills from this slayer to the total xp
+ if (slayerXp)
+ totalXp += slayerXp;
+ if (slayerKills)
+ totalKills += slayerKills;
}
return {
xp: totalXp,
+ kills: totalKills,
bosses: slayers
};
}
diff --git a/build/cleaners/skyblock/stats.js b/build/cleaners/skyblock/stats.js
index 5673d99..0eb94c7 100644
--- a/build/cleaners/skyblock/stats.js
+++ b/build/cleaners/skyblock/stats.js
@@ -10,6 +10,7 @@ const statCategories = {
'mythos': ['mythos_burrows_', 'mythos_kills'],
'collection': ['collection_'],
'skills': ['skill_'],
+ 'slayer': ['slayer_'],
'misc': null // everything else goes here
};
function categorizeStat(statNameRaw) {
diff --git a/build/constants.js b/build/constants.js
index 6344a12..96ae2e7 100644
--- a/build/constants.js
+++ b/build/constants.js
@@ -6,7 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.addZones = exports.fetchZones = exports.addSkills = exports.fetchSkills = exports.addCollections = exports.fetchCollections = exports.addStats = exports.fetchStats = exports.addJSONConstants = void 0;
+exports.addSlayers = exports.fetchSlayers = exports.addZones = exports.fetchZones = exports.addSkills = exports.fetchSkills = exports.addCollections = exports.fetchCollections = exports.addStats = exports.fetchStats = exports.addJSONConstants = void 0;
const node_fetch_1 = __importDefault(require("node-fetch"));
const https_1 = require("https");
const node_cache_1 = __importDefault(require("node-cache"));
@@ -30,14 +30,21 @@ const queue = new queue_promise_1.default({
* @param json The JSON body, only applicable for some types of methods
*/
async function fetchGithubApi(method, route, headers, json) {
- return await node_fetch_1.default(githubApiBase + route, {
- agent: () => httpsAgent,
- body: json ? JSON.stringify(json) : null,
- method,
- headers: Object.assign({
- 'Authorization': `token ${process.env.github_token}`
- }, headers),
- });
+ try {
+ return await node_fetch_1.default(githubApiBase + route, {
+ agent: () => httpsAgent,
+ body: json ? JSON.stringify(json) : null,
+ method,
+ headers: Object.assign({
+ 'Authorization': `token ${process.env.github_token}`
+ }, headers),
+ });
+ }
+ catch {
+ // if there's an error, wait a second and try again
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ return await fetchGithubApi(method, route, headers, json);
+ }
}
// cache files for an hour
const fileCache = new node_cache_1.default({
@@ -95,7 +102,7 @@ async function fetchJSONConstant(filename) {
}
}
/** Add stats to skyblock-constants. This has caching so it's fine to call many times */
-async function addJSONConstants(filename, addingValues, units = 'stats') {
+async function addJSONConstants(filename, addingValues, unit = 'stat') {
if (addingValues.length === 0)
return; // no stats provided, just return
queue.enqueue(async () => {
@@ -119,7 +126,7 @@ async function addJSONConstants(filename, addingValues, units = 'stats') {
// there's not actually any new stats, just return
if (newStats.length === 0)
return;
- const commitMessage = newStats.length >= 2 ? `Add ${newStats.length} new ${units}` : `Add '${newStats[0]}'`;
+ const commitMessage = newStats.length >= 2 ? `Add ${newStats.length} new ${unit}s` : `Add '${newStats[0]}' ${unit}`;
await editFile(file, commitMessage, JSON.stringify(updatedStats, null, 2));
});
}
@@ -131,7 +138,7 @@ async function fetchStats() {
exports.fetchStats = fetchStats;
/** Add stats to skyblock-constants. This has caching so it's fine to call many times */
async function addStats(addingStats) {
- await addJSONConstants('stats.json', addingStats, 'stats');
+ await addJSONConstants('stats.json', addingStats, 'stat');
}
exports.addStats = addStats;
/** Fetch all the known SkyBlock collections as an array of strings */
@@ -141,7 +148,7 @@ async function fetchCollections() {
exports.fetchCollections = fetchCollections;
/** Add collections to skyblock-constants. This has caching so it's fine to call many times */
async function addCollections(addingCollections) {
- await addJSONConstants('collections.json', addingCollections, 'collections');
+ await addJSONConstants('collections.json', addingCollections, 'collection');
}
exports.addCollections = addCollections;
/** Fetch all the known SkyBlock collections as an array of strings */
@@ -151,7 +158,7 @@ async function fetchSkills() {
exports.fetchSkills = fetchSkills;
/** Add skills to skyblock-constants. This has caching so it's fine to call many times */
async function addSkills(addingSkills) {
- await addJSONConstants('skills.json', addingSkills, 'skills');
+ await addJSONConstants('skills.json', addingSkills, 'skill');
}
exports.addSkills = addSkills;
/** Fetch all the known SkyBlock collections as an array of strings */
@@ -161,6 +168,16 @@ async function fetchZones() {
exports.fetchZones = fetchZones;
/** Add skills to skyblock-constants. This has caching so it's fine to call many times */
async function addZones(addingZones) {
- await addJSONConstants('zones.json', addingZones, 'zones');
+ await addJSONConstants('zones.json', addingZones, 'zone');
}
exports.addZones = addZones;
+/** Fetch all the known SkyBlock slayer names as an array of strings */
+async function fetchSlayers() {
+ return await fetchJSONConstant('slayers.json');
+}
+exports.fetchSlayers = fetchSlayers;
+/** Add skills to skyblock-constants. This has caching so it's fine to call many times */
+async function addSlayers(addingSlayers) {
+ await addJSONConstants('slayers.json', addingSlayers, 'slayer');
+}
+exports.addSlayers = addSlayers;
diff --git a/build/database.js b/build/database.js
index b40d30c..967828e 100644
--- a/build/database.js
+++ b/build/database.js
@@ -25,7 +25,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.queueUpdateDatabaseMember = exports.updateDatabaseMember = exports.fetchMemberLeaderboardSpots = exports.fetchMemberLeaderboard = exports.fetchAllMemberLeaderboardAttributes = exports.fetchAllLeaderboardsCategorized = void 0;
+exports.queueUpdateDatabaseMember = exports.updateDatabaseMember = exports.fetchMemberLeaderboardSpots = exports.fetchMemberLeaderboard = exports.fetchAllMemberLeaderboardAttributes = exports.fetchSlayerLeaderboards = exports.fetchAllLeaderboardsCategorized = void 0;
const stats_1 = require("./cleaners/skyblock/stats");
const mongodb_1 = require("mongodb");
const cached = __importStar(require("./hypixelCached"));
@@ -34,6 +34,7 @@ const util_1 = require("./util");
const node_cache_1 = __importDefault(require("node-cache"));
const queue_promise_1 = __importDefault(require("queue-promise"));
const _1 = require(".");
+const slayers_1 = require("./cleaners/skyblock/slayers");
// don't update the user for 3 minutes
const recentlyUpdated = new node_cache_1.default({
stdTTL: 60 * 3,
@@ -74,6 +75,20 @@ function getMemberSkillAttributes(member) {
}
return skillAttributes;
}
+function getMemberSlayerAttributes(member) {
+ const slayerAttributes = {
+ slayer_total_xp: member.slayers.xp,
+ slayer_total_kills: member.slayers.kills,
+ };
+ for (const slayer of member.slayers.bosses) {
+ slayerAttributes[`slayer_${slayer.name}_total_xp`] = slayer.xp;
+ slayerAttributes[`slayer_${slayer.name}_total_kills`] = slayer.kills;
+ for (const tier of slayer.tiers) {
+ slayerAttributes[`slayer_${slayer.name}_${tier.tier}_kills`] = tier.kills;
+ }
+ }
+ return slayerAttributes;
+}
function getMemberLeaderboardAttributes(member) {
// if you want to add a new leaderboard for member attributes, add it here (and getAllLeaderboardAttributes)
return {
@@ -83,6 +98,8 @@ function getMemberLeaderboardAttributes(member) {
...getMemberCollectionAttributes(member),
// skill leaderboards
...getMemberSkillAttributes(member),
+ // slayer leaderboards
+ ...getMemberSlayerAttributes(member),
fairy_souls: member.fairy_souls.total,
first_join: member.first_join,
purse: member.purse,
@@ -105,6 +122,24 @@ async function fetchAllLeaderboardsCategorized() {
return categorizedLeaderboards;
}
exports.fetchAllLeaderboardsCategorized = fetchAllLeaderboardsCategorized;
+/** Fetch the raw names for the slayer leaderboards */
+async function fetchSlayerLeaderboards() {
+ const rawSlayerNames = await constants.fetchSlayers();
+ let leaderboardNames = [
+ 'slayer_total_xp',
+ 'slayer_total_kills'
+ ];
+ // we use the raw names (zombie, spider, wolf) instead of the clean names (revenant, tarantula, sven) because the raw names are guaranteed to never change
+ for (const slayerNameRaw of rawSlayerNames) {
+ leaderboardNames.push(`slayer_${slayerNameRaw}_total_xp`);
+ leaderboardNames.push(`slayer_${slayerNameRaw}_total_kills`);
+ for (let slayerTier = 1; slayerTier <= slayers_1.slayerLevels; slayerTier++) {
+ leaderboardNames.push(`slayer_${slayerNameRaw}_${slayerTier}_kills`);
+ }
+ }
+ return leaderboardNames;
+}
+exports.fetchSlayerLeaderboards = fetchSlayerLeaderboards;
/** Fetch the names of all the leaderboards */
async function fetchAllMemberLeaderboardAttributes() {
return [
@@ -114,6 +149,8 @@ async function fetchAllMemberLeaderboardAttributes() {
...(await constants.fetchCollections()).map(value => `collection_${value}`),
// skill leaderboards
...(await constants.fetchSkills()).map(value => `skill_${value}`),
+ // slayer leaderboards
+ ...await fetchSlayerLeaderboards(),
'fairy_souls',
'first_join',
'purse',
@@ -231,6 +268,7 @@ async function updateDatabaseMember(member, profile) {
await constants.addCollections(member.collections.map(coll => coll.name));
await constants.addSkills(member.skills.map(skill => skill.name));
await constants.addZones(member.visited_zones.map(zone => zone.name));
+ await constants.addSlayers(member.slayers.bosses.map(s => s.raw_name));
if (_1.debug)
console.log('done constants..');
const leaderboardAttributes = await getApplicableAttributes(member);
@@ -265,13 +303,13 @@ async function updateDatabaseMember(member, profile) {
console.log('added member to leaderboards', member.username, leaderboardAttributes);
}
exports.updateDatabaseMember = updateDatabaseMember;
-const queue = new queue_promise_1.default({
+const leaderboardUpdateQueue = new queue_promise_1.default({
concurrent: 1,
interval: 500
});
/** Queue an update for the member's leaderboard data on the server if applicable */
async function queueUpdateDatabaseMember(member, profile) {
- queue.enqueue(async () => await updateDatabaseMember(member, profile));
+ leaderboardUpdateQueue.enqueue(async () => await updateDatabaseMember(member, profile));
}
exports.queueUpdateDatabaseMember = queueUpdateDatabaseMember;
/**
diff --git a/build/hypixelApi.js b/build/hypixelApi.js
index ca2526f..2b3a601 100644
--- a/build/hypixelApi.js
+++ b/build/hypixelApi.js
@@ -51,7 +51,15 @@ async function sendApiRequest({ path, key, args }) {
args.key = key;
// Construct a url from the base api url, path, and arguments
const fetchUrl = baseHypixelAPI + '/' + path + '?' + util_1.jsonToQuery(args);
- const fetchResponse = await node_fetch_1.default(fetchUrl, { agent: () => httpsAgent });
+ let fetchResponse;
+ try {
+ fetchResponse = await node_fetch_1.default(fetchUrl, { agent: () => httpsAgent });
+ }
+ 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 });
+ }
if (fetchResponse.headers['ratelimit-limit'])
// remember how many uses it has
apiKeyUsage[key] = {
diff --git a/build/mojang.js b/build/mojang.js
index f18875d..9641422 100644
--- a/build/mojang.js
+++ b/build/mojang.js
@@ -18,9 +18,17 @@ const httpsAgent = new https_1.Agent({
* Get mojang api data from the session server
*/
async function profileFromUuid(uuid) {
- const fetchResponse = await node_fetch_1.default(
- // using mojang directly is faster than ashcon lol, also mojang removed the ratelimits from here
- `https://sessionserver.mojang.com/session/minecraft/profile/${util_1.undashUuid(uuid)}`, { agent: () => httpsAgent });
+ let fetchResponse;
+ try {
+ fetchResponse = await node_fetch_1.default(
+ // using mojang directly is faster than ashcon lol, also mojang removed the ratelimits from here
+ `https://sessionserver.mojang.com/session/minecraft/profile/${util_1.undashUuid(uuid)}`, { agent: () => httpsAgent });
+ }
+ catch {
+ // if there's an error, wait a second and try again
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ return await profileFromUuid(uuid);
+ }
let data;
try {
data = await fetchResponse.json();
@@ -37,7 +45,15 @@ async function profileFromUuid(uuid) {
exports.profileFromUuid = profileFromUuid;
async function profileFromUsername(username) {
// since we don't care about anything other than the uuid, we can use /uuid/ instead of /user/
- const fetchResponse = await node_fetch_1.default(`https://api.mojang.com/users/profiles/minecraft/${username}`, { agent: () => httpsAgent });
+ let fetchResponse;
+ try {
+ fetchResponse = await node_fetch_1.default(`https://api.mojang.com/users/profiles/minecraft/${username}`, { agent: () => httpsAgent });
+ }
+ catch {
+ // if there's an error, wait a second and try again
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ return await profileFromUsername(username);
+ }
let data;
try {
data = await fetchResponse.json();
diff --git a/src/cleaners/skyblock/slayers.ts b/src/cleaners/skyblock/slayers.ts
index dd3881b..2026496 100644
--- a/src/cleaners/skyblock/slayers.ts
+++ b/src/cleaners/skyblock/slayers.ts
@@ -1,5 +1,4 @@
-
-const slayerLevels = 4 // number of slayer levels, this might be 5 soon
+export const slayerLevels = 4 // number of slayer levels, this might be 5 soon
const SLAYER_NAMES = {
spider: 'tarantula',
@@ -17,12 +16,15 @@ interface SlayerTier {
export interface Slayer {
name: SlayerName
+ raw_name: string
xp: number
+ kills: number
tiers: SlayerTier[]
}
export interface SlayerData {
xp: number
+ kills: number
bosses: Slayer[]
}
@@ -31,27 +33,34 @@ export function cleanSlayers(data: any): SlayerData {
const slayersDataRaw = data?.slayer_bosses
- let totalXp = 0
+ let totalXp: number = 0
+ let totalKills: number = 0
for (const slayerNameRaw in slayersDataRaw) {
const slayerDataRaw = slayersDataRaw[slayerNameRaw]
// convert name provided by api (spider) to the real name (tarantula)
- const slayerName: SlayerName = SLAYER_NAMES[slayerDataRaw]
+ const slayerName: SlayerName = SLAYER_NAMES[slayerNameRaw]
- const slayerXp: number = slayerDataRaw.xp
+ const slayerXp: number = slayerDataRaw.xp ?? 0
+ let slayerKills: number = 0
const slayerTiers: SlayerTier[] = []
+
for (const slayerDataKey in slayerDataRaw) {
// if a key starts with boss_kills_tier_ (boss_kills_tier_1), get the last number
if (slayerDataKey.startsWith('boss_kills_tier_')) {
const slayerTierRaw = parseInt(slayerDataKey.substr('boss_kills_tier_'.length))
- const slayerTierKills = slayerDataRaw[slayerDataKey]
+ const slayerTierKills = slayerDataRaw[slayerDataKey] ?? 0
// add 1 since hypixel is using 0 indexed tiers
const slayerTier = slayerTierRaw + 1
slayerTiers.push({
kills: slayerTierKills,
tier: slayerTier
})
+
+ // count up the total number of kills for this slayer
+ if (slayerTierKills)
+ slayerKills += slayerTierKills
}
}
@@ -64,15 +73,25 @@ export function cleanSlayers(data: any): SlayerData {
const slayer: Slayer = {
name: slayerName,
+ raw_name: slayerNameRaw,
tiers: slayerTiers,
- xp: slayerXp
+ xp: slayerXp ?? 0,
+ kills: slayerKills
}
+
slayers.push(slayer)
- // add the xp from this slayer to the total xp
- totalXp += slayerXp
+
+ // add the xp and kills from this slayer to the total xp
+ if (slayerXp)
+ totalXp += slayerXp
+ if (slayerKills)
+ totalKills += slayerKills
}
+
return {
xp: totalXp,
+ kills: totalKills,
bosses: slayers
}
}
+
diff --git a/src/cleaners/skyblock/stats.ts b/src/cleaners/skyblock/stats.ts
index efb79bd..218e034 100644
--- a/src/cleaners/skyblock/stats.ts
+++ b/src/cleaners/skyblock/stats.ts
@@ -8,6 +8,7 @@ const statCategories: { [ key: string ]: string[] | null } = { // sorted in orde
'collection': ['collection_'],
'skills': ['skill_'],
+ 'slayer': ['slayer_'],
'misc': null // everything else goes here
}
diff --git a/src/constants.ts b/src/constants.ts
index 760dcb1..5f20147 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -30,17 +30,23 @@ const queue = new Queue({
* @param json The JSON body, only applicable for some types of methods
*/
async function fetchGithubApi(method: string, route: string, headers?: any, json?: any): Promise<nodeFetch.Response> {
- return await fetch(
- githubApiBase + route,
- {
- agent: () => httpsAgent,
- body: json ? JSON.stringify(json) : null,
- method,
- headers: Object.assign({
- 'Authorization': `token ${process.env.github_token}`
- }, headers),
- }
- )
+ try {
+ return await fetch(
+ githubApiBase + route,
+ {
+ agent: () => httpsAgent,
+ body: json ? JSON.stringify(json) : null,
+ method,
+ headers: Object.assign({
+ 'Authorization': `token ${process.env.github_token}`
+ }, headers),
+ }
+ )
+ } catch {
+ // if there's an error, wait a second and try again
+ await new Promise((resolve) => setTimeout(resolve, 1000))
+ return await fetchGithubApi(method, route, headers, json)
+ }
}
interface GithubFile {
@@ -73,6 +79,7 @@ async function fetchFile(path: string): Promise<GithubFile> {
},
)
const data = await r.json()
+
const file = {
path: data.path,
content: Buffer.from(data.content, data.encoding).toString(),
@@ -119,7 +126,7 @@ async function fetchJSONConstant(filename: string): Promise<string[]> {
}
/** Add stats to skyblock-constants. This has caching so it's fine to call many times */
-export async function addJSONConstants(filename: string, addingValues: string[], units: string='stats'): Promise<void> {
+export async function addJSONConstants(filename: string, addingValues: string[], unit: string='stat'): Promise<void> {
if (addingValues.length === 0) return // no stats provided, just return
queue.enqueue(async() => {
@@ -143,7 +150,7 @@ export async function addJSONConstants(filename: string, addingValues: string[],
// there's not actually any new stats, just return
if (newStats.length === 0) return
- const commitMessage = newStats.length >= 2 ? `Add ${newStats.length} new ${units}` : `Add '${newStats[0]}'`
+ const commitMessage = newStats.length >= 2 ? `Add ${newStats.length} new ${unit}s` : `Add '${newStats[0]}' ${unit}`
await editFile(file, commitMessage, JSON.stringify(updatedStats, null, 2))
})
}
@@ -156,7 +163,7 @@ export async function fetchStats(): Promise<string[]> {
/** Add stats to skyblock-constants. This has caching so it's fine to call many times */
export async function addStats(addingStats: string[]): Promise<void> {
- await addJSONConstants('stats.json', addingStats, 'stats')
+ await addJSONConstants('stats.json', addingStats, 'stat')
}
/** Fetch all the known SkyBlock collections as an array of strings */
@@ -166,7 +173,7 @@ export async function fetchCollections(): Promise<string[]> {
/** Add collections to skyblock-constants. This has caching so it's fine to call many times */
export async function addCollections(addingCollections: string[]): Promise<void> {
- await addJSONConstants('collections.json', addingCollections, 'collections')
+ await addJSONConstants('collections.json', addingCollections, 'collection')
}
/** Fetch all the known SkyBlock collections as an array of strings */
@@ -176,7 +183,7 @@ export async function fetchSkills(): Promise<string[]> {
/** Add skills to skyblock-constants. This has caching so it's fine to call many times */
export async function addSkills(addingSkills: string[]): Promise<void> {
- await addJSONConstants('skills.json', addingSkills, 'skills')
+ await addJSONConstants('skills.json', addingSkills, 'skill')
}
/** Fetch all the known SkyBlock collections as an array of strings */
@@ -186,5 +193,16 @@ export async function fetchZones(): Promise<string[]> {
/** Add skills to skyblock-constants. This has caching so it's fine to call many times */
export async function addZones(addingZones: string[]): Promise<void> {
- await addJSONConstants('zones.json', addingZones, 'zones')
+ await addJSONConstants('zones.json', addingZones, 'zone')
+}
+
+
+/** Fetch all the known SkyBlock slayer names as an array of strings */
+export async function fetchSlayers(): Promise<string[]> {
+ return await fetchJSONConstant('slayers.json')
+}
+
+/** Add skills to skyblock-constants. This has caching so it's fine to call many times */
+export async function addSlayers(addingSlayers: string[]): Promise<void> {
+ await addJSONConstants('slayers.json', addingSlayers, 'slayer')
}
diff --git a/src/database.ts b/src/database.ts
index 9e81f5b..3f44b39 100644
--- a/src/database.ts
+++ b/src/database.ts
@@ -13,6 +13,7 @@ import { shuffle, sleep } from './util'
import NodeCache from 'node-cache'
import Queue from 'queue-promise'
import { debug } from '.'
+import { slayerLevels } from './cleaners/skyblock/slayers'
// don't update the user for 3 minutes
const recentlyUpdated = new NodeCache({
@@ -77,6 +78,23 @@ function getMemberSkillAttributes(member: CleanMember): StringNumber {
return skillAttributes
}
+function getMemberSlayerAttributes(member: CleanMember): StringNumber {
+ const slayerAttributes: StringNumber = {
+ slayer_total_xp: member.slayers.xp,
+ slayer_total_kills: member.slayers.kills,
+ }
+
+ for (const slayer of member.slayers.bosses) {
+ slayerAttributes[`slayer_${slayer.name}_total_xp`] = slayer.xp
+ slayerAttributes[`slayer_${slayer.name}_total_kills`] = slayer.kills
+ for (const tier of slayer.tiers) {
+ slayerAttributes[`slayer_${slayer.name}_${tier.tier}_kills`] = tier.kills
+ }
+ }
+
+ return slayerAttributes
+}
+
function getMemberLeaderboardAttributes(member: CleanMember): StringNumber {
// if you want to add a new leaderboard for member attributes, add it here (and getAllLeaderboardAttributes)
return {
@@ -89,6 +107,9 @@ function getMemberLeaderboardAttributes(member: CleanMember): StringNumber {
// skill leaderboards
...getMemberSkillAttributes(member),
+ // slayer leaderboards
+ ...getMemberSlayerAttributes(member),
+
fairy_souls: member.fairy_souls.total,
first_join: member.first_join,
purse: member.purse,
@@ -115,6 +136,25 @@ export async function fetchAllLeaderboardsCategorized(): Promise<{ [ category: s
return categorizedLeaderboards
}
+/** Fetch the raw names for the slayer leaderboards */
+export async function fetchSlayerLeaderboards(): Promise<string[]> {
+ const rawSlayerNames = await constants.fetchSlayers()
+ let leaderboardNames: string[] = [
+ 'slayer_total_xp',
+ 'slayer_total_kills'
+ ]
+
+ // we use the raw names (zombie, spider, wolf) instead of the clean names (revenant, tarantula, sven) because the raw names are guaranteed to never change
+ for (const slayerNameRaw of rawSlayerNames) {
+ leaderboardNames.push(`slayer_${slayerNameRaw}_total_xp`)
+ leaderboardNames.push(`slayer_${slayerNameRaw}_total_kills`)
+ for (let slayerTier = 1; slayerTier <= slayerLevels; slayerTier ++) {
+ leaderboardNames.push(`slayer_${slayerNameRaw}_${slayerTier}_kills`)
+ }
+ }
+
+ return leaderboardNames
+}
/** Fetch the names of all the leaderboards */
export async function fetchAllMemberLeaderboardAttributes(): Promise<string[]> {
@@ -128,6 +168,9 @@ export async function fetchAllMemberLeaderboardAttributes(): Promise<string[]> {
// skill leaderboards
...(await constants.fetchSkills()).map(value => `skill_${value}`),
+ // slayer leaderboards
+ ...await fetchSlayerLeaderboards(),
+
'fairy_souls',
'first_join',
'purse',
@@ -265,6 +308,7 @@ export async function updateDatabaseMember(member: CleanMember, profile: CleanFu
await constants.addCollections(member.collections.map(coll => coll.name))
await constants.addSkills(member.skills.map(skill => skill.name))
await constants.addZones(member.visited_zones.map(zone => zone.name))
+ await constants.addSlayers(member.slayers.bosses.map(s => s.raw_name))
if (debug) console.log('done constants..')
@@ -306,14 +350,14 @@ export async function updateDatabaseMember(member: CleanMember, profile: CleanFu
if (debug) console.log('added member to leaderboards', member.username, leaderboardAttributes)
}
-const queue = new Queue({
+const leaderboardUpdateQueue = new Queue({
concurrent: 1,
interval: 500
})
/** Queue an update for the member's leaderboard data on the server if applicable */
export async function queueUpdateDatabaseMember(member: CleanMember, profile: CleanFullProfile): Promise<void> {
- queue.enqueue(async() => await updateDatabaseMember(member, profile))
+ leaderboardUpdateQueue.enqueue(async() => await updateDatabaseMember(member, profile))
}
diff --git a/src/hypixelApi.ts b/src/hypixelApi.ts
index 5c4bb2b..8541089 100644
--- a/src/hypixelApi.ts
+++ b/src/hypixelApi.ts
@@ -2,6 +2,7 @@
* Fetch the raw Hypixel API
*/
import fetch from 'node-fetch'
+import * as nodeFetch from 'node-fetch'
import { jsonToQuery, shuffle } from './util'
import { Agent } from 'https'
@@ -136,10 +137,18 @@ export async function sendApiRequest({ path, key, args }): Promise<HypixelRespon
// Construct a url from the base api url, path, and arguments
const fetchUrl = baseHypixelAPI + '/' + path + '?' + jsonToQuery(args)
- const fetchResponse = await fetch(
- fetchUrl,
- { agent: () => httpsAgent }
- )
+ let fetchResponse: nodeFetch.Response
+
+ try {
+ fetchResponse = await fetch(
+ fetchUrl,
+ { agent: () => httpsAgent }
+ )
+ } 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 })
+ }
if (fetchResponse.headers['ratelimit-limit'])
// remember how many uses it has
diff --git a/src/mojang.ts b/src/mojang.ts
index ce5b0bb..829de7f 100644
--- a/src/mojang.ts
+++ b/src/mojang.ts
@@ -3,6 +3,7 @@
*/
import fetch from 'node-fetch'
+import * as nodeFetch from 'node-fetch'
import { Agent } from 'https'
import { isUuid, undashUuid } from './util'
@@ -22,11 +23,20 @@ interface MojangApiResponse {
* Get mojang api data from the session server
*/
export async function profileFromUuid(uuid: string): Promise<MojangApiResponse> {
- const fetchResponse = await fetch(
- // using mojang directly is faster than ashcon lol, also mojang removed the ratelimits from here
- `https://sessionserver.mojang.com/session/minecraft/profile/${undashUuid(uuid)}`,
- { agent: () => httpsAgent }
- )
+ let fetchResponse: nodeFetch.Response
+
+ try {
+ fetchResponse = await fetch(
+ // using mojang directly is faster than ashcon lol, also mojang removed the ratelimits from here
+ `https://sessionserver.mojang.com/session/minecraft/profile/${undashUuid(uuid)}`,
+ { agent: () => httpsAgent }
+ )
+ } catch {
+ // if there's an error, wait a second and try again
+ await new Promise((resolve) => setTimeout(resolve, 1000))
+ return await profileFromUuid(uuid)
+ }
+
let data
try {
data = await fetchResponse.json()
@@ -43,10 +53,20 @@ export async function profileFromUuid(uuid: string): Promise<MojangApiResponse>
export async function profileFromUsername(username: string): Promise<MojangApiResponse> {
// since we don't care about anything other than the uuid, we can use /uuid/ instead of /user/
- const fetchResponse = await fetch(
- `https://api.mojang.com/users/profiles/minecraft/${username}`,
- { agent: () => httpsAgent }
- )
+
+ let fetchResponse: nodeFetch.Response
+
+ try {
+ fetchResponse = await fetch(
+ `https://api.mojang.com/users/profiles/minecraft/${username}`,
+ { agent: () => httpsAgent }
+ )
+ } catch {
+ // if there's an error, wait a second and try again
+ await new Promise((resolve) => setTimeout(resolve, 1000))
+ return await profileFromUsername(username)
+ }
+
let data
try {
data = await fetchResponse.json()