diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cleaners/skyblock/inventory.ts | 1 | ||||
-rw-r--r-- | src/cleaners/skyblock/member.ts | 2 | ||||
-rw-r--r-- | src/cleaners/skyblock/profile.ts | 15 | ||||
-rw-r--r-- | src/database.ts | 248 | ||||
-rw-r--r-- | src/hypixel.ts | 26 | ||||
-rw-r--r-- | src/hypixelCached.ts | 28 | ||||
-rw-r--r-- | src/index.ts | 13 |
7 files changed, 298 insertions, 35 deletions
diff --git a/src/cleaners/skyblock/inventory.ts b/src/cleaners/skyblock/inventory.ts index 5d72928..d23bbac 100644 --- a/src/cleaners/skyblock/inventory.ts +++ b/src/cleaners/skyblock/inventory.ts @@ -1,3 +1,4 @@ +// maybe todo?: create a fast replacement for prismarine-nbt import * as nbt from 'prismarine-nbt' function base64decode(base64: string): Buffer { diff --git a/src/cleaners/skyblock/member.ts b/src/cleaners/skyblock/member.ts index fb8132f..5d8d4f9 100644 --- a/src/cleaners/skyblock/member.ts +++ b/src/cleaners/skyblock/member.ts @@ -50,7 +50,7 @@ export async function cleanSkyBlockProfileMemberResponseBasic(member: any, inclu /** Cleans up a member (from skyblock/profile) */ export async function cleanSkyBlockProfileMemberResponse(member, included: Included[] = null): Promise<CleanMember> { // profiles.members[] - const inventoriesIncluded = included == null || included.includes('inventories') + const inventoriesIncluded = included === null || included.includes('inventories') const player = await cached.fetchPlayer(member.uuid) if (!player) return return { diff --git a/src/cleaners/skyblock/profile.ts b/src/cleaners/skyblock/profile.ts index 086cd31..433471b 100644 --- a/src/cleaners/skyblock/profile.ts +++ b/src/cleaners/skyblock/profile.ts @@ -45,7 +45,7 @@ export async function cleanSkyblockProfileResponseLighter(data): Promise<CleanPr /** * This function is somewhat costly and shouldn't be called often. Use cleanSkyblockProfileResponseLighter if you don't need all the data */ -export async function cleanSkyblockProfileResponse(data: any, options?: ApiOptions): Promise<CleanFullProfile> { +export async function cleanSkyblockProfileResponse(data: any, options?: ApiOptions): Promise<CleanFullProfile|CleanProfile> { // We use Promise.all so it can fetch all the users at once instead of waiting for the previous promise to complete const promises: Promise<CleanMember>[] = [] @@ -54,12 +54,23 @@ export async function cleanSkyblockProfileResponse(data: any, options?: ApiOptio memberRaw.uuid = memberUUID promises.push(cleanSkyBlockProfileMemberResponse( memberRaw, - ['stats', options?.mainMemberUuid === memberUUID ? 'inventories' : undefined] + [ + !options?.basic ? 'stats' : undefined, + options?.mainMemberUuid === memberUUID ? 'inventories' : undefined + ] )) } const cleanedMembers: CleanMember[] = (await Promise.all(promises)).filter(m => m !== null && m !== undefined) + if (options?.basic) { + return { + uuid: data.profile_id, + name: data.cute_name, + members: cleanedMembers, + } + } + const memberMinions: CleanMinion[][] = [] for (const member of cleanedMembers) { diff --git a/src/database.ts b/src/database.ts index 983dcb2..c715d52 100644 --- a/src/database.ts +++ b/src/database.ts @@ -3,7 +3,7 @@ */ import { categorizeStat, getStatUnit } from './cleaners/skyblock/stats' -import { CleanFullProfile, CleanProfile } from './cleaners/skyblock/profile' +import { CleanBasicProfile, CleanFullProfile, CleanProfile } from './cleaners/skyblock/profile' import { CleanMember } from './cleaners/skyblock/member' import { Collection, Db, MongoClient } from 'mongodb' import { CleanPlayer } from './cleaners/player' @@ -22,19 +22,32 @@ const recentlyUpdated = new NodeCache({ useClones: false, }) -interface DatabaseLeaderboardItem { +interface DatabaseMemberLeaderboardItem { uuid: string profile: string stats: any last_updated: Date } +interface DatabaseProfileLeaderboardItem { + uuid: string + /** An array of uuids for each player in the profile */ + players: string[] + stats: any + last_updated: Date +} -interface LeaderboardItem { +interface MemberLeaderboardItem { player: CleanPlayer + profileUuid: string + value: number +} +interface ProfileLeaderboardItem { + players: CleanPlayer[] + profileUuid: string value: number } -const cachedRawLeaderboards: Map<string, DatabaseLeaderboardItem[]> = new Map() +const cachedRawLeaderboards: Map<string, (DatabaseMemberLeaderboardItem|DatabaseProfileLeaderboardItem)[]> = new Map() const leaderboardMax = 100 const reversedLeaderboards = [ @@ -45,6 +58,7 @@ const reversedLeaderboards = [ let client: MongoClient let database: Db let memberLeaderboardsCollection: Collection<any> +let profileLeaderboardsCollection: Collection<any> async function connect(): Promise<void> { if (!process.env.db_uri) @@ -54,6 +68,7 @@ async function connect(): Promise<void> { client = await MongoClient.connect(process.env.db_uri, { useNewUrlParser: true, useUnifiedTopology: true }) database = client.db(process.env.db_name) memberLeaderboardsCollection = database.collection('member-leaderboards') + profileLeaderboardsCollection = database.collection('profile-leaderboards') } interface StringNumber { @@ -117,11 +132,20 @@ function getMemberLeaderboardAttributes(member: CleanMember): StringNumber { } } +function getProfileLeaderboardAttributes(profile: CleanFullProfile): StringNumber { + // if you want to add a new leaderboard for member attributes, add it here (and getAllLeaderboardAttributes) + return { + minion_count: profile.minion_count + } +} + export async function fetchAllLeaderboardsCategorized(): Promise<{ [ category: string ]: string[] }> { const memberLeaderboardAttributes: string[] = await fetchAllMemberLeaderboardAttributes() + const profileLeaderboardAttributes: string[] = await fetchAllProfileLeaderboardAttributes() + const categorizedLeaderboards: { [ category: string ]: string[] } = {} - for (const leaderboard of memberLeaderboardAttributes) { + for (const leaderboard of [...memberLeaderboardAttributes, ...profileLeaderboardAttributes]) { const { category } = categorizeStat(leaderboard) if (!categorizedLeaderboards[category]) categorizedLeaderboards[category] = [] @@ -156,7 +180,7 @@ export async function fetchSlayerLeaderboards(): Promise<string[]> { return leaderboardNames } -/** Fetch the names of all the leaderboards */ +/** Fetch the names of all the leaderboards that rank members */ export async function fetchAllMemberLeaderboardAttributes(): Promise<string[]> { return [ // we use the raw stat names rather than the clean stats in case hypixel adds a new stat and it takes a while for us to clean it @@ -179,6 +203,13 @@ export async function fetchAllMemberLeaderboardAttributes(): Promise<string[]> { ] } +/** Fetch the names of all the leaderboards that rank profiles */ +async function fetchAllProfileLeaderboardAttributes(): Promise<string[]> { + return [ + 'minion_count' + ] +} + function isLeaderboardReversed(name: string): boolean { for (const leaderboardMatch of reversedLeaderboards) { let trailingEnd = leaderboardMatch[0] === '_' @@ -193,9 +224,11 @@ function isLeaderboardReversed(name: string): boolean { return false } -async function fetchMemberLeaderboardRaw(name: string): Promise<DatabaseLeaderboardItem[]> { +async function fetchMemberLeaderboardRaw(name: string): Promise<DatabaseMemberLeaderboardItem[]> { + if (!client) throw Error('Client isn\'t initialized yet') + if (cachedRawLeaderboards.has(name)) - return cachedRawLeaderboards.get(name) + return cachedRawLeaderboards.get(name) as DatabaseMemberLeaderboardItem[] // typescript forces us to make a new variable and set it this way because it gives an error otherwise const query = {} query[`stats.${name}`] = { '$exists': true, '$ne': NaN } @@ -203,7 +236,7 @@ async function fetchMemberLeaderboardRaw(name: string): Promise<DatabaseLeaderbo const sortQuery: any = {} sortQuery[`stats.${name}`] = isLeaderboardReversed(name) ? 1 : -1 - const leaderboardRaw: DatabaseLeaderboardItem[] = await memberLeaderboardsCollection + const leaderboardRaw: DatabaseMemberLeaderboardItem[] = await memberLeaderboardsCollection .find(query) .sort(sortQuery) .limit(leaderboardMax) @@ -213,23 +246,52 @@ async function fetchMemberLeaderboardRaw(name: string): Promise<DatabaseLeaderbo return leaderboardRaw } -interface Leaderboard { +async function fetchProfileLeaderboardRaw(name: string): Promise<DatabaseProfileLeaderboardItem[]> { + if (cachedRawLeaderboards.has(name)) + return cachedRawLeaderboards.get(name) as DatabaseProfileLeaderboardItem[] + // typescript forces us to make a new variable and set it this way because it gives an error otherwise + const query = {} + query[`stats.${name}`] = { '$exists': true, '$ne': NaN } + + const sortQuery: any = {} + sortQuery[`stats.${name}`] = isLeaderboardReversed(name) ? 1 : -1 + + const leaderboardRaw: DatabaseProfileLeaderboardItem[] = await profileLeaderboardsCollection + .find(query) + .sort(sortQuery) + .limit(leaderboardMax) + .toArray() + console.log('leaderboardRaw', leaderboardRaw) + + cachedRawLeaderboards.set(name, leaderboardRaw) + return leaderboardRaw +} + +interface MemberLeaderboard { + name: string + unit?: string + list: MemberLeaderboardItem[] +} + +interface ProfileLeaderboard { name: string unit?: string - list: LeaderboardItem[] + list: ProfileLeaderboardItem[] } + /** Fetch a leaderboard that ranks members, as opposed to profiles */ -export async function fetchMemberLeaderboard(name: string): Promise<Leaderboard> { +export async function fetchMemberLeaderboard(name: string): Promise<MemberLeaderboard> { const leaderboardRaw = await fetchMemberLeaderboardRaw(name) - const fetchLeaderboardPlayer = async(item: DatabaseLeaderboardItem): Promise<LeaderboardItem> => { + const fetchLeaderboardPlayer = async(item: DatabaseMemberLeaderboardItem): Promise<MemberLeaderboardItem> => { return { player: await cached.fetchBasicPlayer(item.uuid), + profileUuid: item.profile, value: item.stats[name] } } - const promises: Promise<LeaderboardItem>[] = [] + const promises: Promise<MemberLeaderboardItem>[] = [] for (const item of leaderboardRaw) { promises.push(fetchLeaderboardPlayer(item)) } @@ -241,6 +303,44 @@ export async function fetchMemberLeaderboard(name: string): Promise<Leaderboard> } } + +/** Fetch a leaderboard that ranks profiles, as opposed to members */ +export async function fetchProfileLeaderboard(name: string): Promise<ProfileLeaderboard> { + const leaderboardRaw = await fetchProfileLeaderboardRaw(name) + + const fetchLeaderboardProfile = async(item: DatabaseProfileLeaderboardItem): Promise<ProfileLeaderboardItem> => { + const players = [] + for (const playerUuid of item.players) + players.push(await cached.fetchBasicPlayer(playerUuid)) + return { + players: players, + profileUuid: item.uuid, + value: item.stats[name] + } + } + const promises: Promise<ProfileLeaderboardItem>[] = [] + for (const item of leaderboardRaw) { + promises.push(fetchLeaderboardProfile(item)) + } + const leaderboard = await Promise.all(promises) + return { + name: name, + unit: getStatUnit(name) ?? null, + list: leaderboard + } +} + +/** Fetch a leaderboard */ +export async function fetchLeaderboard(name: string): Promise<MemberLeaderboard|ProfileLeaderboard> { + const profileLeaderboards = await fetchAllProfileLeaderboardAttributes() + console.log(name, profileLeaderboards, profileLeaderboards.includes(name)) + if (profileLeaderboards.includes(name)) { + return await fetchProfileLeaderboard(name) + } else { + return await fetchMemberLeaderboard(name) + } +} + /** Get the leaderboard positions a member is on. This may take a while depending on whether stuff is cached */ export async function fetchMemberLeaderboardSpots(player: string, profile: string) { const fullProfile = await cached.fetchProfile(player, profile) @@ -249,7 +349,7 @@ export async function fetchMemberLeaderboardSpots(player: string, profile: strin // update the leaderboard positions for the member await updateDatabaseMember(fullMember, fullProfile) - const applicableAttributes = await getApplicableAttributes(fullMember) + const applicableAttributes = await getApplicableMemberLeaderboardAttributes(fullMember) const memberLeaderboardSpots = [] @@ -268,8 +368,12 @@ export async function fetchMemberLeaderboardSpots(player: string, profile: strin return memberLeaderboardSpots } -async function getMemberLeaderboardRequirement(name: string): Promise<number> { - const leaderboard = await fetchMemberLeaderboardRaw(name) +async function getLeaderboardRequirement(name: string, leaderboardType: 'member' | 'profile'): Promise<number> { + let leaderboard: DatabaseMemberLeaderboardItem[] | DatabaseProfileLeaderboardItem[] + if (leaderboardType === 'member') + leaderboard = await fetchMemberLeaderboardRaw(name) + else if (leaderboardType === 'profile') + leaderboard = await fetchProfileLeaderboardRaw(name) // if there's more than 100 items, return the 100th. if there's less, return null if (leaderboard.length >= leaderboardMax) @@ -279,12 +383,12 @@ async function getMemberLeaderboardRequirement(name: string): Promise<number> { } /** Get the attributes for the member, but only ones that would put them on the top 100 for leaderboards */ -async function getApplicableAttributes(member: CleanMember): Promise<StringNumber> { +async function getApplicableMemberLeaderboardAttributes(member: CleanMember): Promise<StringNumber> { const leaderboardAttributes = getMemberLeaderboardAttributes(member) const applicableAttributes = {} for (const [ leaderboard, attributeValue ] of Object.entries(leaderboardAttributes)) { - const requirement = await getMemberLeaderboardRequirement(leaderboard) + const requirement = await getLeaderboardRequirement(leaderboard, 'member') const leaderboardReversed = isLeaderboardReversed(leaderboard) if ( (requirement === null) @@ -296,16 +400,44 @@ async function getApplicableAttributes(member: CleanMember): Promise<StringNumbe let leaderboardsCount: number = Object.keys(applicableAttributes).length - const leaderboardsCountRequirement: number = await getMemberLeaderboardRequirement('leaderboards_count') + const leaderboardsCountRequirement: number = await getLeaderboardRequirement('leaderboards_count', 'member') if ( (leaderboardsCountRequirement === null) || (leaderboardsCount > leaderboardsCountRequirement) ) { - // add 1 extra because this attribute also counts :) applicableAttributes['leaderboards_count'] = leaderboardsCount } + return applicableAttributes +} + +/** Get the attributes for the profile, but only ones that would put them on the top 100 for leaderboards */ +async function getApplicableProfileLeaderboardAttributes(profile: CleanFullProfile): Promise<StringNumber> { + const leaderboardAttributes = getProfileLeaderboardAttributes(profile) + const applicableAttributes = {} + + for (const [ leaderboard, attributeValue ] of Object.entries(leaderboardAttributes)) { + const requirement = await getLeaderboardRequirement(leaderboard, 'profile') + const leaderboardReversed = isLeaderboardReversed(leaderboard) + if ( + (requirement === null) + || (leaderboardReversed ? attributeValue < requirement : attributeValue > requirement) + ) { + applicableAttributes[leaderboard] = attributeValue + } + } + + + let leaderboardsCount: number = Object.keys(applicableAttributes).length + const leaderboardsCountRequirement: number = await getLeaderboardRequirement('leaderboards_count', 'member') + + if ( + (leaderboardsCountRequirement === null) + || (leaderboardsCount > leaderboardsCountRequirement) + ) { + applicableAttributes['leaderboards_count'] = leaderboardsCount + } return applicableAttributes } @@ -330,9 +462,9 @@ export async function updateDatabaseMember(member: CleanMember, profile: CleanFu if (debug) console.log('done constants..') - const leaderboardAttributes = await getApplicableAttributes(member) + const leaderboardAttributes = await getApplicableMemberLeaderboardAttributes(member) - if (debug) console.log('done getApplicableAttributes..', leaderboardAttributes, member.username, profile.name) + if (debug) console.log('done getApplicableMemberLeaderboardAttributes..', leaderboardAttributes, member.username, profile.name) await memberLeaderboardsCollection.updateOne( { @@ -368,14 +500,78 @@ export async function updateDatabaseMember(member: CleanMember, profile: CleanFu if (debug) console.log('added member to leaderboards', member.username, leaderboardAttributes) } -const leaderboardUpdateQueue = new Queue({ +/** + * Update the profiles's leaderboard data on the server if applicable. + * This will not also update the members, you have to call updateDatabaseMember separately for that + */ +export async function updateDatabaseProfile(profile: CleanFullProfile): Promise<void> { + if (debug) console.log('updateDatabaseProfile', profile.name) + if (!client) return // the db client hasn't been initialized + + // the profile's been updated too recently, just return + if (recentlyUpdated.get(profile.uuid + 'profile')) + return + // store the profile in recentlyUpdated so it cant update for 3 more minutes + recentlyUpdated.set(profile.uuid + 'profile', true) + + if (debug) console.log('adding profile to leaderboards', profile.name) + + const leaderboardAttributes = await getApplicableProfileLeaderboardAttributes(profile) + + if (debug) console.log('done getApplicableProfileLeaderboardAttributes..', leaderboardAttributes, profile.name) + + await profileLeaderboardsCollection.updateOne( + { + uuid: profile.uuid + }, + { + '$set': { + players: profile.members.map(p => p.uuid), + stats: leaderboardAttributes, + last_updated: new Date() + } + }, + { upsert: true } + ) + + // add the profile to the cached leaderboard without having to refetch it + for (const [ attributeName, attributeValue ] of Object.entries(leaderboardAttributes)) { + const existingRawLeaderboard = await fetchProfileLeaderboardRaw(attributeName) + const leaderboardReverse = isLeaderboardReversed(attributeName) + const newRawLeaderboard = existingRawLeaderboard + // remove the player from the leaderboard, if they're there + .filter(value => value.uuid !== profile.uuid) + .concat([{ + last_updated: new Date(), + stats: leaderboardAttributes, + uuid: profile.uuid, + players: profile.members.map(p => p.uuid) + }]) + .sort((a, b) => leaderboardReverse ? a.stats[attributeName] - b.stats[attributeName] : b.stats[attributeName] - a.stats[attributeName]) + .slice(0, 100) + cachedRawLeaderboards.set(attributeName, newRawLeaderboard) + } + + if (debug) console.log('added profile to leaderboards', profile.name, leaderboardAttributes) +} + +const leaderboardUpdateMemberQueue = new Queue({ concurrent: 1, interval: 500 }) +const leaderboardUpdateProfileQueue = new Queue({ + concurrent: 1, + interval: 2000 +}) /** Queue an update for the member's leaderboard data on the server if applicable */ export async function queueUpdateDatabaseMember(member: CleanMember, profile: CleanFullProfile): Promise<void> { - leaderboardUpdateQueue.enqueue(async() => await updateDatabaseMember(member, profile)) + leaderboardUpdateMemberQueue.enqueue(async() => await updateDatabaseMember(member, profile)) +} + +/** Queue an update for the profile's leaderboard data on the server if applicable */ +export async function queueUpdateDatabaseProfile(profile: CleanFullProfile): Promise<void> { + leaderboardUpdateProfileQueue.enqueue(async() => await updateDatabaseProfile(profile)) } @@ -393,7 +589,7 @@ async function removeBadMemberLeaderboardAttributes(): Promise<void> { const unsetValue = {} unsetValue[leaderboard] = '' const filter = {} - const requirement = await getMemberLeaderboardRequirement(leaderboard) + const requirement = await getLeaderboardRequirement(leaderboard, 'member') const leaderboardReversed = isLeaderboardReversed(leaderboard) if (requirement !== null) { filter[`stats.${leaderboard}`] = { diff --git a/src/hypixel.ts b/src/hypixel.ts index 723feea..599a7e1 100644 --- a/src/hypixel.ts +++ b/src/hypixel.ts @@ -9,7 +9,7 @@ import { CleanBasicMember, CleanMemberProfile } from './cleaners/skyblock/member import { cleanSkyblockProfileResponse, CleanProfile, CleanBasicProfile, CleanFullProfile, CleanFullProfileBasicMembers } from './cleaners/skyblock/profile' import { cleanSkyblockProfilesResponse } from './cleaners/skyblock/profiles' import { debug } from '.' -import { queueUpdateDatabaseMember } from './database' +import { queueUpdateDatabaseMember, queueUpdateDatabaseProfile } from './database' export type Included = 'profiles' | 'player' | 'stats' | 'inventories' @@ -25,6 +25,8 @@ export const maxMinion = 11 export interface ApiOptions { mainMemberUuid?: string + /** Only get the most basic information, like uuids and names */ + basic?: boolean } /** Sends an API request to Hypixel and cleans it up. */ @@ -174,7 +176,7 @@ export async function fetchMemberProfile(user: string, profile: string): Promise * @param playerUuid The UUID of the Minecraft player * @param profileUuid The UUID of the Hypixel SkyBlock profile */ -export async function fetchMemberProfileUncached(playerUuid: string, profileUuid: string): Promise<CleanFullProfile> { + export async function fetchMemberProfileUncached(playerUuid: string, profileUuid: string): Promise<CleanFullProfile> { const profile: CleanFullProfile = await sendCleanApiRequest( { path: 'skyblock/profile', @@ -187,6 +189,25 @@ export async function fetchMemberProfileUncached(playerUuid: string, profileUuid // queue updating the leaderboard positions for the member, eventually for (const member of profile.members) queueUpdateDatabaseMember(member, profile) + queueUpdateDatabaseProfile(profile) + + return profile +} + +/** + * Fetches the Hypixel API to get a CleanProfile from its id. This doesn't do any caching and you should use hypixelCached.fetchBasicProfileFromUuid instead + * @param playerUuid The UUID of the Minecraft player + * @param profileUuid The UUID of the Hypixel SkyBlock profile + */ + export async function fetchBasicProfileFromUuidUncached(profileUuid: string): Promise<CleanProfile> { + const profile: CleanFullProfile = await sendCleanApiRequest( + { + path: 'skyblock/profile', + args: { profile: profileUuid } + }, + null, + { basic: true } + ) return profile } @@ -208,6 +229,7 @@ export async function fetchMemberProfilesUncached(playerUuid: string): Promise<C for (const member of profile.members) { queueUpdateDatabaseMember(member, profile) } + queueUpdateDatabaseProfile(profile) } return profiles }
\ No newline at end of file diff --git a/src/hypixelCached.ts b/src/hypixelCached.ts index aa3100b..6a84f5e 100644 --- a/src/hypixelCached.ts +++ b/src/hypixelCached.ts @@ -191,6 +191,8 @@ export async function fetchBasicPlayer(user: string): Promise<CleanPlayer> { return basicPlayerCache.get(playerUuid) const player = await fetchPlayer(playerUuid) + if (!player) console.log(user) + delete player.profiles return player } @@ -312,6 +314,32 @@ export async function fetchProfile(user: string, profile: string): Promise<Clean } /** + * Fetch a CleanProfile from the uuid + * @param profileUuid A profile name or profile uuid +*/ +export async function fetchBasicProfileFromUuid(profileUuid: string): Promise<CleanProfile> { + if (profileCache.has(profileUuid)) { + // we have the profile cached, return it :) + if (debug) console.log('Cache hit! fetchBasicProfileFromUuid', profileUuid) + const profile: CleanFullProfile = profileCache.get(profileUuid) + return { + uuid: profile.uuid, + members: profile.members.map(m => ({ + uuid: m.uuid, + username: m.username, + last_save: m.last_save, + first_join: m.first_join, + rank: m.rank, + })), + name: profile.name + } + } + // TODO: cache this + return await hypixel.fetchBasicProfileFromUuidUncached(profileUuid) +} + + +/** * Fetch the name of a profile from the user and profile uuid * @param user A player uuid or username * @param profile A profile uuid or name diff --git a/src/index.ts b/src/index.ts index 2c0f1ee..1e641a1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { fetchAllLeaderboardsCategorized, fetchMemberLeaderboard, fetchMemberLeaderboardSpots } from './database' +import { fetchAllLeaderboardsCategorized, fetchLeaderboard, fetchMemberLeaderboardSpots } from './database' import { fetchMemberProfile, fetchUser } from './hypixel' import rateLimit from 'express-rate-limit' import express from 'express' @@ -51,9 +51,14 @@ app.get('/player/:user/:profile/leaderboards', async(req, res) => { }) app.get('/leaderboard/:name', async(req, res) => { - res.json( - await fetchMemberLeaderboard(req.params.name) - ) + try { + res.json( + await fetchLeaderboard(req.params.name) + ) + } catch (err) { + console.error(err) + res.json({ 'error': err.toString() }) + } }) app.get('/leaderboards', async(req, res) => { |