From 3592c0f599369ddaef26339d741f498bcc1b3e24 Mon Sep 17 00:00:00 2001 From: Jacob Date: Mon, 26 Aug 2024 21:42:14 +0800 Subject: [PATCH 1/2] Better Errors --- src/API/getGuild.ts | 15 ++++--- src/API/getHouse.ts | 3 +- src/API/getLeaderboards.ts | 7 ++- src/API/getPlayer.ts | 5 ++- src/API/getPlayerHouses.ts | 3 +- src/API/getRecentGames.ts | 3 +- src/API/getSkyblockAuction.ts | 9 ++-- src/API/getSkyblockAuctions.ts | 11 +++-- src/API/getSkyblockAuctionsByPlayer.ts | 3 +- src/API/getSkyblockGarden.ts | 3 +- src/API/getSkyblockMember.ts | 7 ++- src/API/getSkyblockMuseum.ts | 3 +- src/API/getSkyblockProfiles.ts | 7 ++- src/Client.test.ts | 2 +- src/Client.ts | 4 +- src/Errors.ts | 42 ------------------ src/Private/Endpoint.ts | 3 +- src/Private/ErrorHandler.ts | 57 ++++++++++++++++++++++++ src/Private/RateLimit.ts | 3 +- src/Private/Requests.test.ts | 2 +- src/Private/Requests.ts | 60 +++++++++++++------------- src/Private/Updater.ts | 5 ++- src/index.ts | 2 +- src/typings/index.d.ts | 2 +- 24 files changed, 154 insertions(+), 107 deletions(-) delete mode 100644 src/Errors.ts create mode 100644 src/Private/ErrorHandler.ts diff --git a/src/API/getGuild.ts b/src/API/getGuild.ts index 487e9b4..4b37ae3 100644 --- a/src/API/getGuild.ts +++ b/src/API/getGuild.ts @@ -1,5 +1,6 @@ import { RequestOptions } from '../Private/Requests'; import Guild from '../structures/Guild/Guild'; +import Error from '../Private/ErrorHandler'; import isGuildID from '../utils/isGuildID'; import Endpoint from '../Private/Endpoint'; import Client from '../Client'; @@ -16,17 +17,19 @@ class getGuild extends Endpoint { query: string, options?: RequestOptions ): Promise { - if (!query) throw new Error(this.client.errors.NO_GUILD_QUERY); - if ('id' === searchParameter && !isGuildID(query)) throw new Error(this.client.errors.INVALID_GUILD_ID); - const isPlayerQuery = 'player' === searchParameter; - if (isPlayerQuery) query = await this.client.requests.toUUID(query); if (!['id', 'name', 'player'].includes(searchParameter)) { - throw new Error(this.client.errors.INVALID_GUILD_SEARCH_PARAMETER); + throw new Error(this.client.errors.INVALID_GUILD_SEARCH_PARAMETER, 'Fetching Guild Stats'); + } + if (!query) throw new Error(this.client.errors.NO_GUILD_QUERY, 'Fetching Guild Stats'); + if ('id' === searchParameter && !isGuildID(query)) { + throw new Error(this.client.errors.INVALID_GUILD_ID, 'Fetching Guild Stats'); } + const isPlayerQuery = 'player' === searchParameter; + if (isPlayerQuery) query = await this.client.requests.toUUID(query); const res = await this.client.requests.request(`/guild?${searchParameter}=${encodeURI(query)}`, options); if (res.options.raw) return res.data; if (!res.data.guild && 'player' !== searchParameter) { - throw new Error(this.client.errors.GUILD_DOES_NOT_EXIST); + throw new Error(this.client.errors.GUILD_DOES_NOT_EXIST, 'Fetching Guild Stats'); } return res.data.guild ? new Guild(res.data.guild, isPlayerQuery ? query : undefined) : null; } diff --git a/src/API/getHouse.ts b/src/API/getHouse.ts index 9d6c93c..3ce593c 100644 --- a/src/API/getHouse.ts +++ b/src/API/getHouse.ts @@ -1,4 +1,5 @@ import { RequestOptions } from '../Private/Requests'; +import Error from '../Private/ErrorHandler'; import Endpoint from '../Private/Endpoint'; import House from '../structures/House'; import Client from '../Client'; @@ -11,7 +12,7 @@ class getHouse extends Endpoint { } async execute(query: string, options?: RequestOptions): Promise { - if (!query) throw new Error(this.client.errors.NO_UUID); + if (!query) throw new Error(this.client.errors.NO_UUID, 'Fetching a House'); const res = await this.client.requests.request(`/housing/house?house=${query}`, options); if (res.options.raw) return res.data; return new House(res.data); diff --git a/src/API/getLeaderboards.ts b/src/API/getLeaderboards.ts index 5396015..c25bb82 100644 --- a/src/API/getLeaderboards.ts +++ b/src/API/getLeaderboards.ts @@ -1,7 +1,8 @@ import { RequestOptions } from '../Private/Requests'; import Leaderboard from '../structures/Leaderboard'; -import Constants from '../utils/Constants'; +import Error from '../Private/ErrorHandler'; import Endpoint from '../Private/Endpoint'; +import Constants from '../utils/Constants'; import Client from '../Client'; class getLeaderboards extends Endpoint { @@ -15,7 +16,9 @@ class getLeaderboards extends Endpoint { const res = await this.client.requests.request('/leaderboards', options); if (res.options.raw) return res.data; if (!res.data.leaderboards) { - throw new Error(this.client.errors.SOMETHING_WENT_WRONG.replace(/{cause}/, 'Try again.')); + throw new Error(this.client.errors.SOMETHING_WENT_WRONG, 'Fetching Leaderboards', { + cause: 'Data is missing. Try again.' + }); } const lbnames = Object.create(Constants.leaderboardNames); for (const name in lbnames) { diff --git a/src/API/getPlayer.ts b/src/API/getPlayer.ts index 06dd245..640669a 100644 --- a/src/API/getPlayer.ts +++ b/src/API/getPlayer.ts @@ -1,4 +1,5 @@ import { PlayerRequestOptions } from './API'; +import Error from '../Private/ErrorHandler'; import Endpoint from '../Private/Endpoint'; import Player from '../structures/Player'; import Client from '../Client'; @@ -11,11 +12,11 @@ class getPlayer extends Endpoint { } async execute(query: string, options?: PlayerRequestOptions): Promise { - if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID); + if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID, 'Fetching Player'); query = await this.client.requests.toUUID(query); const res = await this.client.requests.request(`/player?uuid=${query}`, options); if (res.options.raw) return res.data; - if (query && !res.data.player) throw new Error(this.client.errors.PLAYER_HAS_NEVER_LOGGED); + if (query && !res.data.player) throw new Error(this.client.errors.PLAYER_HAS_NEVER_LOGGED, 'Fetching Player'); return new Player(res.data.player, { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error diff --git a/src/API/getPlayerHouses.ts b/src/API/getPlayerHouses.ts index e16cea5..324a6ab 100644 --- a/src/API/getPlayerHouses.ts +++ b/src/API/getPlayerHouses.ts @@ -1,4 +1,5 @@ import { RequestOptions } from '../Private/Requests'; +import Error from '../Private/ErrorHandler'; import Endpoint from '../Private/Endpoint'; import House from '../structures/House'; import Client from '../Client'; @@ -11,7 +12,7 @@ class getPlayerHouses extends Endpoint { } async execute(query: string, options?: RequestOptions): Promise { - if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID); + if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID, 'Fetching Player Houses'); query = await this.client.requests.toUUID(query); const res = await this.client.requests.request(`/housing/houses?player=${query}`, options); if (res.options.raw) return res.data; diff --git a/src/API/getRecentGames.ts b/src/API/getRecentGames.ts index fef1a95..a73696c 100644 --- a/src/API/getRecentGames.ts +++ b/src/API/getRecentGames.ts @@ -1,5 +1,6 @@ import { RequestOptions } from '../Private/Requests'; import RecentGame from '../structures/RecentGame'; +import Error from '../Private/ErrorHandler'; import Endpoint from '../Private/Endpoint'; import Client from '../Client'; @@ -11,7 +12,7 @@ class getRecentGames extends Endpoint { } async execute(query: string, options?: RequestOptions): Promise { - if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID); + if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID, 'Fetching Recent Games'); query = await this.client.requests.toUUID(query); const res = await this.client.requests.request(`/recentgames?uuid=${query}`, options); if (res.options.raw) return res.data; diff --git a/src/API/getSkyblockAuction.ts b/src/API/getSkyblockAuction.ts index 91d88ba..7ffb27e 100644 --- a/src/API/getSkyblockAuction.ts +++ b/src/API/getSkyblockAuction.ts @@ -1,5 +1,6 @@ import Auction from '../structures/SkyBlock/Auctions/Auction'; import { AuctionRequestOptions } from './API'; +import Error from '../Private/ErrorHandler'; import Endpoint from '../Private/Endpoint'; import Client from '../Client'; @@ -24,12 +25,14 @@ class getSkyblockAction extends Endpoint { } else if ('auction' === type) { filter = 'uuid'; } else { - throw new Error(this.client.errors.BAD_AUCTION_FILTER); + throw new Error(this.client.errors.BAD_AUCTION_FILTER, 'Fetching Skyblock Auction'); } - if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID); + if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID, 'Fetching Skyblock Auction'); const res = await this.client.requests.request(`/skyblock/auction?${filter}=${query}`, options); if (res.options.raw) return res.data; - return res.data.auctions.map((a: any) => new Auction(a, options?.includeItemBytes ?? false)); + return res.data.auctions.map( + (a: Record) => new Auction(a, options?.includeItemBytes ?? false) + ); } } diff --git a/src/API/getSkyblockAuctions.ts b/src/API/getSkyblockAuctions.ts index fd2b3e3..1ba6408 100644 --- a/src/API/getSkyblockAuctions.ts +++ b/src/API/getSkyblockAuctions.ts @@ -1,6 +1,7 @@ import AuctionInfo from '../structures/SkyBlock/Auctions/AuctionInfo'; import Auction from '../structures/SkyBlock/Auctions/Auction'; import { AuctionRequestOptions } from './API'; +import Error from '../Private/ErrorHandler'; import Endpoint from '../Private/Endpoint'; import Client from '../Client'; @@ -16,9 +17,13 @@ class getSkyblockAuctions extends Endpoint { query: number | '*', options?: AuctionRequestOptions ): Promise<{ info: AuctionInfo; auctions: Auction[] }> { - if (!query) throw new Error(this.client.errors.INVALID_OPTION_VALUE); - if ('number' === typeof query && 0 >= query) throw new Error(this.client.errors.INVALID_OPTION_VALUE); - if ('number' !== typeof query && '*' !== query) throw new Error(this.client.errors.INVALID_OPTION_VALUE); + if (!query) throw new Error(this.client.errors.INVALID_OPTION_VALUE, 'Fetching Skyblock Auctions'); + if ('number' === typeof query && 0 >= query) { + throw new Error(this.client.errors.INVALID_OPTION_VALUE, 'Fetching Skyblock Auctions'); + } + if ('number' !== typeof query && '*' !== query) { + throw new Error(this.client.errors.INVALID_OPTION_VALUE, 'Fetching Skyblock Auctions'); + } this.options = this.parseOptions(options); if ('*' === query) return await this.getAllPages(); return await this.getPage(query); diff --git a/src/API/getSkyblockAuctionsByPlayer.ts b/src/API/getSkyblockAuctionsByPlayer.ts index 7f482a2..bf35f09 100644 --- a/src/API/getSkyblockAuctionsByPlayer.ts +++ b/src/API/getSkyblockAuctionsByPlayer.ts @@ -1,5 +1,6 @@ import Auction from '../structures/SkyBlock/Auctions/Auction'; import { AuctionRequestOptions } from './API'; +import Error from '../Private/ErrorHandler'; import Endpoint from '../Private/Endpoint'; import Client from '../Client'; @@ -11,7 +12,7 @@ class getSkyblockActionsByPlayer extends Endpoint { } async execute(query: string, options?: AuctionRequestOptions): Promise { - if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID); + if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID, 'Fetching Skyb,oock Auctions By Player'); query = await this.client.requests.toUUID(query); const res = await this.client.requests.request(`/skyblock/auction?player=${query}`); if (res.options.raw) return res.data; diff --git a/src/API/getSkyblockGarden.ts b/src/API/getSkyblockGarden.ts index cba1b0d..af49ef6 100644 --- a/src/API/getSkyblockGarden.ts +++ b/src/API/getSkyblockGarden.ts @@ -1,5 +1,6 @@ import SkyblockGarden from '../structures/SkyBlock/SkyblockGarden'; import { RequestOptions } from '../Private/Requests'; +import Error from '../Private/ErrorHandler'; import Endpoint from '../Private/Endpoint'; import Client from '../Client'; @@ -11,7 +12,7 @@ class getSkyblockGarden extends Endpoint { } async execute(profileId: string, options?: RequestOptions): Promise { - if (!profileId) throw new Error(this.client.errors.NO_UUID); + if (!profileId) throw new Error(this.client.errors.NO_UUID, 'Fetching Skyblock Garden'); const res = await this.client.requests.request(`/skyblock/garden?profile=${profileId}`, options); if (res.options.raw) return res.data; return new SkyblockGarden(res.data); diff --git a/src/API/getSkyblockMember.ts b/src/API/getSkyblockMember.ts index df38001..64f08b0 100644 --- a/src/API/getSkyblockMember.ts +++ b/src/API/getSkyblockMember.ts @@ -1,5 +1,6 @@ import SkyblockMember from '../structures/SkyBlock/SkyblockMember'; import { SkyblockRequestOptions } from './API'; +import Error from '../Private/ErrorHandler'; import Endpoint from '../Private/Endpoint'; import Client from '../Client'; @@ -11,11 +12,13 @@ class getSkyblockMember extends Endpoint { } async execute(query: string, options?: SkyblockRequestOptions): Promise> { - if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID); + if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID, 'Fetching Skyblock Member'); query = await this.client.requests.toUUID(query); const res = await this.client.requests.request(`/skyblock/profiles?uuid=${query}`, options); if (res.options.raw) return res.data; - if (!res.data.profiles || !res.data.profiles.length) throw new Error(this.client.errors.NO_SKYBLOCK_PROFILES); + if (!res.data.profiles || !res.data.profiles.length) { + throw new Error(this.client.errors.NO_SKYBLOCK_PROFILES, 'Fetching Skyblock Member'); + } const memberByProfileName = new Map(); for (const profile of res.data.profiles) { memberByProfileName.set( diff --git a/src/API/getSkyblockMuseum.ts b/src/API/getSkyblockMuseum.ts index e3af11a..cf236ea 100644 --- a/src/API/getSkyblockMuseum.ts +++ b/src/API/getSkyblockMuseum.ts @@ -1,5 +1,6 @@ import SkyblockMuseum from '../structures/SkyBlock/SkyblockMuseum'; import { RequestOptions } from '../Private/Requests'; +import Error from '../Private/ErrorHandler'; import Endpoint from '../Private/Endpoint'; import Client from '../Client'; @@ -11,7 +12,7 @@ class getSkyblockMuseum extends Endpoint { } async execute(query: string, profileId: string, options?: RequestOptions): Promise { - if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID); + if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID, 'Fetching Skyblock Museum'); query = await this.client.requests.toUUID(query); const res = await this.client.requests.request(`/skyblock/museum?uuid=${query}&profile=${profileId}`, options); if (res.options.raw) return res.data; diff --git a/src/API/getSkyblockProfiles.ts b/src/API/getSkyblockProfiles.ts index 8a86bab..a8e6294 100644 --- a/src/API/getSkyblockProfiles.ts +++ b/src/API/getSkyblockProfiles.ts @@ -1,5 +1,6 @@ import SkyblockProfile from '../structures/SkyBlock/SkyblockProfile'; import { SkyblockRequestOptions } from './API'; +import Error from '../Private/ErrorHandler'; import Endpoint from '../Private/Endpoint'; import Client from '../Client'; @@ -11,11 +12,13 @@ class getSkyblockProfiles extends Endpoint { } async execute(query: string, options?: SkyblockRequestOptions): Promise { - if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID); + if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID, 'Fetching Skyblock Profiles'); query = await this.client.requests.toUUID(query); const res = await this.client.requests.request(`/skyblock/profiles?uuid=${query}`, options); if (res.options.raw) return res.data; - if (!res.data.profiles || !res.data.profiles.length) throw new Error(this.client.errors.NO_SKYBLOCK_PROFILES); + if (!res.data.profiles || !res.data.profiles.length) { + throw new Error(this.client.errors.NO_SKYBLOCK_PROFILES, 'Fetching Skyblock Profiles'); + } const profiles = []; for (let i = 0; i < res.data.profiles.length; i++) { profiles.push({ diff --git a/src/Client.test.ts b/src/Client.test.ts index 9b6c7dc..3446762 100644 --- a/src/Client.test.ts +++ b/src/Client.test.ts @@ -4,7 +4,7 @@ import { ClientOptions } from './typings/Client'; import Requests from './Private/Requests'; import Updater from './Private/Updater'; import Client from './Client'; -import Errors from './Errors'; +import Errors from './Private/ErrorHandler'; const errors = new Errors(); test('Client (No Key)', () => { diff --git a/src/Client.ts b/src/Client.ts index 8714cbb..3329621 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -1,9 +1,9 @@ +import Error, { Errors } from './Private/ErrorHandler'; import CacheHandler from './Private/CacheHandler'; import { ClientOptions } from './typings/Client'; import RateLimit from './Private/RateLimit'; import Requests from './Private/Requests'; import Updater from './Private/Updater'; -import Errors from './Errors'; import API from './API'; const clients: Client[] = []; @@ -20,7 +20,7 @@ class Client { constructor(key: string, options?: ClientOptions) { this.key = key; this.errors = new Errors(); - if (!this.key.length) throw new Error(this.errors.NO_API_KEY); + if (!this.key.length) throw new Error(this.errors.NO_API_KEY, 'Initializing Client'); this.options = this.parasOptions(options); this.requests = new Requests(this); this.cacheHandler = new CacheHandler(this); diff --git a/src/Errors.ts b/src/Errors.ts deleted file mode 100644 index 74ff6a9..0000000 --- a/src/Errors.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* eslint-disable max-len */ -class Errors { - INVALID_API_KEY: string = - '[Hypixel-API-Reborn] Invalid API Key! For help join our Discord Server https://discord.gg/NSEBNMM'; - NO_API_KEY: string = - '[Hypixel-API-Reborn] No API Key specified! For help join our Discord Server https://discord.gg/NSEBNMM'; - ERROR_CODE_CAUSE: string = - '[Hypixel-API-Reborn] Code: {code} - {cause}! For help join our Discord Server https://discord.gg/NSEBNMM'; - ERROR_STATUSTEXT: string = - '[Hypixel-API-Reborn] {statustext}! For help join our Discord Server https://discord.gg/NSEBNMM'; - NO_NICKNAME_UUID: string = '[Hypixel-API-Reborn] No nickname or uuid specified.'; - NO_UUID: string = '[Hypixel-API-Reborn] No uuid specified.'; - UUID_NICKNAME_MUST_BE_A_STRING: string = '[Hypixel-API-Reborn] Nickname or uuid must be a string.'; - MALFORMED_UUID: string = '[Hypixel-API-Reborn] Malformed UUID!'; - PLAYER_HAS_NEVER_LOGGED: string = '[Hypixel-API-Reborn] Player has never logged into Hypixel.'; - NO_GUILD_QUERY: string = '[Hypixel-API-Reborn] No guild search query specified.'; - INVALID_GUILD_ID: string = '[Hypixel-API-Reborn] Specified Guild ID is invalid.'; - INVALID_GUILD_SEARCH_PARAMETER: string = - "[Hypixel-API-Reborn] getGuild() searchParameter must be 'id'; 'guild' or 'player'."; - SOMETHING_WENT_WRONG: string = '[Hypixel-API-Reborn] Something went wrong. {cause}'; - GUILD_DOES_NOT_EXIST: string = '[Hypixel-API-Reborn] Guild does not exist.'; - PAGE_INDEX_ERROR: string = - '[Hypixel-API-Reborn] Invalid page index. Must be an integer, an array of 2 integers, or a keyword. For help join our Discord Server https://discord.gg/NSEBNMM'; - INVALID_OPTION_VALUE: string = - '[Hypixel-API-Reborn] Invalid option value! For help join our Discord Server https://discord.gg/NSEBNMM'; - UPDATER_REQUEST_NOT_OK: string = '[Hypixel-API-Reborn] Something went wrong while checking for updates.'; - MULTIPLE_INSTANCES: string = - '[Hypixel-API-Reborn] Multiple instances of hypixel-api-reborn are found so we merged them for you. Please refrain from spawning multiple instances in the future. For more information, join our Discord Server https://discord.gg/NSEBNMM.'; - UNEXPECTED_ERROR: string = - "[Hypixel-API-Reborn] The data provided to hypixel API is malformed and thus not recognized by hypixel, but this shouldn't be your fault. Please report this error in our Discord Server https://discord.gg/NSEBNMM or GitHub. "; - RATE_LIMIT_EXCEEDED: string = - "[Hypixel-API-Reborn] The rate limitations on your API Key has been exceeded. There might be an outage (Check Hypixel's status page), or you simply did too many requests in a short time. Hint: Enable rate limit options! They can help you avoid this error! For help join our Discord Server https://discord.gg/NSEBNMM"; - NO_SKYBLOCK_PROFILES: string = '[Hypixel-API-Reborn] The player has no skyblock profiles.'; - BAD_AUCTION_FILTER: string = - '[Hypixel-API-Reborn] Unexpected filter for Client#getSkyblockAuction. Expected one of "PLAYER", "AUCTION", "PROFILE", but got something else.'; - NOT_IMPLEMENTED: string = - '[Hypixel-API-Reborn] Endpoint execute method is not implemented yet! Please report this https://discord.gg/NSEBNMM'; - RATE_LIMIT_INIT_ERROR: string = - '[hypixel-api-reborn] An error happened whilst initializing rate limit. We strongly recommend restarting the code as this can lead to desynchronization.'; -} - -export default Errors; diff --git a/src/Private/Endpoint.ts b/src/Private/Endpoint.ts index 3adeded..9226487 100644 --- a/src/Private/Endpoint.ts +++ b/src/Private/Endpoint.ts @@ -1,3 +1,4 @@ +import Error from '../Private/ErrorHandler'; import Client from '../Client'; class Endpoint { @@ -7,7 +8,7 @@ class Endpoint { } execute(...args: any[]): Promise | any { - throw new Error(this.client.errors.NOT_IMPLEMENTED); + throw new Error(this.client.errors.NOT_IMPLEMENTED, 'Unknown endpoint'); } } diff --git a/src/Private/ErrorHandler.ts b/src/Private/ErrorHandler.ts new file mode 100644 index 0000000..53ff035 --- /dev/null +++ b/src/Private/ErrorHandler.ts @@ -0,0 +1,57 @@ +/* eslint-disable max-len */ + +const baseVariables = { + discordInvite: 'https://discord.gg/NSEBNMM', + packageName: 'hypixel-api-reborn' +}; + +export class Errors { + NO_API_KEY: string = 'No API Key specified! For help join our Discord Server {discordInvite}'; + ERROR_CODE_CAUSE: string = 'Code: {code} - {cause}! For help join our Discord Server {discordInvite}'; + ERROR_STATUSTEXT: string = '{statusCode}! For help join our Discord Server {discordInvite}'; + NO_NICKNAME_UUID: string = 'No nickname or uuid specified.'; + NO_UUID: string = 'No uuid specified.'; + UUID_NICKNAME_MUST_BE_A_STRING: string = 'Nickname or uuid must be a string.'; + MALFORMED_UUID: string = 'Malformed UUID!'; + PLAYER_HAS_NEVER_LOGGED: string = 'Player has never logged into Hypixel.'; + NO_GUILD_QUERY: string = 'No guild search query specified.'; + INVALID_GUILD_ID: string = 'Specified Guild ID is invalid.'; + INVALID_GUILD_SEARCH_PARAMETER: string = "getGuild() searchParameter must be 'id'; 'guild' or 'player'."; + SOMETHING_WENT_WRONG: string = 'Something went wrong. {cause}'; + GUILD_DOES_NOT_EXIST: string = 'Guild does not exist.'; + PAGE_INDEX_ERROR: string = + 'Invalid page index. Must be an integer, an array of 2 integers, or a keyword. For help join our Discord Server {discordInvite}'; + INVALID_OPTION_VALUE: string = 'Invalid option value! For help join our Discord Server {discordInvite}'; + UPDATER_REQUEST_NOT_OK: string = 'Something went wrong while checking for updates.'; + MULTIPLE_INSTANCES: string = + 'Multiple instances of hypixel-api-reborn are found so we merged them for you. Please refrain from spawning multiple instances in the future. For more information, join our Discord Server {discordInvite}.'; + UNEXPECTED_ERROR: string = + "The data provided to hypixel API is malformed and thus not recognized by hypixel, but this shouldn't be your fault. Please report this error in our Discord Server {discordInvite} or GitHub."; + RATE_LIMIT_EXCEEDED: string = + "The rate limitations on your API Key has been exceeded. There might be an outage (Check Hypixel's status page), or you simply did too many requests in a short time. Hint: Enable rate limit options! They can help you avoid this error! For help join our Discord Server {discordInvite}"; + NO_SKYBLOCK_PROFILES: string = 'The player has no skyblock profiles.'; + BAD_AUCTION_FILTER: string = + 'Unexpected filter for Client#getSkyblockAuction. Expected one of "PLAYER", "AUCTION", "PROFILE", but got something else.'; + NOT_IMPLEMENTED: string = 'Endpoint execute method is not implemented yet! Please report this {discordInvite}'; + RATE_LIMIT_INIT_ERROR: string = + 'An error happened whilst initializing rate limit. We strongly recommend restarting the code as this can lead to desynchronization.'; +} + +// Credit: https://github.com/DuckySoLucky/hypixel-discord-chat-bridge/blob/4926194c7fb99dd7773a78b9cb827ec029d11bd8/src/contracts/helperFunctions.js#L264-L266 +export function replaceVariables(template: string, variables: Record): string { + return template.replace(/\{(\w+)\}/g, (match, name) => variables[name] ?? match); +} +class HypixelAPIRebornError extends Error { + source: string; + constructor(message: string, source: string, variables: Record = {}) { + super(replaceVariables(message, { ...baseVariables, ...variables })); + this.name = 'HypixelAPIReborn'; + this.source = replaceVariables(source, { ...baseVariables, ...variables }); + } + + toString() { + return this.message; + } +} + +export default HypixelAPIRebornError; diff --git a/src/Private/RateLimit.ts b/src/Private/RateLimit.ts index 3faaa16..3b44c2c 100644 --- a/src/Private/RateLimit.ts +++ b/src/Private/RateLimit.ts @@ -1,3 +1,4 @@ +import Error from '../Private/ErrorHandler'; import Client from '../Client'; class RateLimit { @@ -17,7 +18,7 @@ class RateLimit { async sync() { const { headers } = await this.client.requests.request('/boosters', { raw: true }); if (headers?.['ratelimit-limit'] === undefined || headers?.['ratelimit-remaining'] === undefined) { - throw new Error(this.client.errors.RATE_LIMIT_INIT_ERROR); + throw new Error(this.client.errors.RATE_LIMIT_INIT_ERROR, 'initializing rate limit.'); } this.requests = headers['ratelimit-limit'] - headers['ratelimit-remaining']; this.limit = Number(headers['ratelimit-limit']); diff --git a/src/Private/Requests.test.ts b/src/Private/Requests.test.ts index 927dc04..2871011 100644 --- a/src/Private/Requests.test.ts +++ b/src/Private/Requests.test.ts @@ -31,7 +31,7 @@ test('Requests (Invalid API Key)', () => { expectTypeOf(client.requests.request).toBeFunction(); const mockRequest = { status: 403, data: {} }; vi.spyOn(axios, 'get').mockResolvedValue(mockRequest); - expect(() => client.requests.request('/boosters')).rejects.toThrowError(client.errors.INVALID_API_KEY); + expect(() => client.requests.request('/boosters')).rejects.toThrowError('Invalid API Key!'); vi.restoreAllMocks(); client.destroy(); }); diff --git a/src/Private/Requests.ts b/src/Private/Requests.ts index 5a588d6..c978a46 100644 --- a/src/Private/Requests.ts +++ b/src/Private/Requests.ts @@ -1,5 +1,6 @@ const BASE_URL = 'https://api.hypixel.net/v2'; import isUUID from '../utils/isUUID'; +import Error from './ErrorHandler'; import Client from '../Client'; import axios from 'axios'; @@ -53,27 +54,25 @@ class Requests { } const res = await axios.get(BASE_URL + endpoint, { headers: { 'API-Key': this.client.key } }); if (500 <= res.status && 528 > res.status) { - throw new Error( - this.client.errors.ERROR_STATUSTEXT.replace(/{statustext}/, `Server Error : ${res.status} ${res.statusText}`) - ); + throw new Error(this.client.errors.ERROR_STATUSTEXT, 'Hypixel API Returned {statusCode} Invalid API Key.', { + statusCode: `${res.status}` + }); } const parsedRes = await res.data; if (400 === res.status) { - throw new Error( - this.client.errors.ERROR_CODE_CAUSE.replace(/{code}/, '400 Bad Request').replace( - /{cause}/, - parsedRes.cause || '' - ) - ); + throw new Error(this.client.errors.ERROR_CODE_CAUSE, 'Fetching Hypixel API', { + code: '400 Bad Request', + cause: parsedRes.cause || '' + }); } - if (403 === res.status) throw new Error(this.client.errors.INVALID_API_KEY); - if (422 === res.status) throw new Error(this.client.errors.UNEXPECTED_ERROR); - if (429 === res.status) throw new Error(this.client.errors.RATE_LIMIT_EXCEEDED); + if (403 === res.status) throw new Error('Invalid API Key!', 'Hypixel API Returned 403 Invalid API Key.'); + if (422 === res.status) throw new Error(this.client.errors.UNEXPECTED_ERROR, 'Fetching Hypixel API'); + if (429 === res.status) throw new Error(this.client.errors.RATE_LIMIT_EXCEEDED, 'Fetching Hypixel API'); if (200 !== res.status) { - throw new Error(this.client.errors.ERROR_STATUSTEXT.replace(/{statustext}/, res.statusText)); + throw new Error(this.client.errors.ERROR_STATUSTEXT, 'Fetching the Hypixel API', { statustext: res.statusText }); } if (!parsedRes.success && !endpoint.startsWith('/housing')) { - throw new Error(this.client.errors.SOMETHING_WENT_WRONG.replace(/{cause}/, res.statusText)); + throw new Error(this.client.errors.SOMETHING_WENT_WRONG, 'Fetching the Hypixel API', { cause: res.statusText }); } this.client.rateLimit.requests++; const requestData = new RequestData(parsedRes, res.headers, { @@ -90,8 +89,10 @@ class Requests { } async toUUID(input: string): Promise { - if (!input) throw new Error(this.client.errors.NO_NICKNAME_UUID); - if ('string' !== typeof input) throw new Error(this.client.errors.UUID_NICKNAME_MUST_BE_A_STRING); + if (!input) throw new Error(this.client.errors.NO_NICKNAME_UUID, 'Converting a nickname to UUID'); + if ('string' !== typeof input) { + throw new Error(this.client.errors.UUID_NICKNAME_MUST_BE_A_STRING, 'Converting a nickname to UUID'); + } if (isUUID(input)) return input.replace(/-/g, ''); const url = `https://mowojang.matdoes.dev/${input}`; if (this.client.cacheHandler.has(url)) { @@ -99,28 +100,27 @@ class Requests { } const res = await axios.get(url); if (500 <= res.status && 528 > res.status) { - throw new Error( - this.client.errors.ERROR_STATUSTEXT.replace(/{statustext}/, `Server Error : ${res.status} ${res.statusText}`) - ); + throw new Error(this.client.errors.ERROR_STATUSTEXT, 'Hypixel API Returned {statusCode} Invalid API Key.', { + statusCode: `${res.status}` + }); } const parsedRes = await res.data; if (400 === res.status) { - throw new Error( - this.client.errors.ERROR_CODE_CAUSE.replace(/{code}/, '400 Bad Request').replace( - /{cause}/, - parsedRes.cause || '' - ) - ); + throw new Error(this.client.errors.ERROR_CODE_CAUSE, 'Converting nickname to UUI', { + code: '400 Bad Request', + cause: parsedRes.cause || '' + }); } + if (422 === res.status) throw new Error(this.client.errors.UNEXPECTED_ERROR, 'Converting a nickname to UUID'); if (200 !== res.status) { - throw new Error(this.client.errors.ERROR_STATUSTEXT.replace(/{statustext}/, res.statusText)); + throw new Error(this.client.errors.ERROR_STATUSTEXT, 'Converting nickname to UUID', { + statustext: res.statusText + }); } if ('string' !== typeof parsedRes.id || 'string' !== typeof parsedRes.name) { - throw new Error(this.client.errors.MALFORMED_UUID); - } - if (this.client.options.cache) { - this.client.cacheHandler.set(url, parsedRes.id); + throw new Error(this.client.errors.MALFORMED_UUID, 'Converting a nickname to UUID'); } + if (this.client.options.cache) this.client.cacheHandler.set(url, parsedRes.id); return parsedRes.id; } } diff --git a/src/Private/Updater.ts b/src/Private/Updater.ts index 971028d..efdc7f5 100644 --- a/src/Private/Updater.ts +++ b/src/Private/Updater.ts @@ -1,5 +1,6 @@ /* eslint-disable no-console */ import { version } from '../../package.json'; +import Error from '../Private/ErrorHandler'; import Client from '../Client'; import axios from 'axios'; @@ -27,7 +28,9 @@ class Updater { async getLatestVersion(): Promise { const request = await axios.get('https://registry.npmjs.org/hypixel-api-reborn'); - if (200 !== request.status) throw new Error(this.client.errors.UPDATER_REQUEST_NOT_OK); + if (200 !== request.status) { + throw new Error(this.client.errors.UPDATER_REQUEST_NOT_OK, 'Fetching Latest {packageName} version'); + } return request.data['dist-tags'].latest; } diff --git a/src/index.ts b/src/index.ts index 3a53515..2fef227 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ export * from './Client'; -export * from './Errors'; +export * from './Private/ErrorHandler'; export * from './Private/CacheHandler'; export * from './Private/Updater'; export * from './Private/Endpoint'; diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index e9f4edf..7c131ca 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -2,7 +2,7 @@ import Requests, { RequestOptions } from '../Private/Requests'; import CacheHandler from '../Private/CacheHandler'; import { ClientOptions } from '../Client'; import Updater from '../Private/Updater'; -import Errors from '../Errors'; +import Errors from '../Private/ErrorHandler'; import Achievements from '../structures/Static/Achievements'; import House from '../structures/House'; import Booster from '../structures/Boosters/Booster'; From 64c670fdae3442a8f019dcde8ff83a10004ba8f2 Mon Sep 17 00:00:00 2001 From: Jacob Date: Mon, 7 Oct 2024 11:25:56 +0800 Subject: [PATCH 2/2] Prettier --- src/API/getPlayer.ts | 2 +- src/Client.ts | 6 +++--- src/Private/Updater.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/API/getPlayer.ts b/src/API/getPlayer.ts index c303227..59b1e0f 100644 --- a/src/API/getPlayer.ts +++ b/src/API/getPlayer.ts @@ -15,7 +15,7 @@ class getPlayer extends Endpoint { this.client = client; } - async execute(query: string, options?: PlayerRequestOptions): Promise { + async execute(query: string, options?: PlayerRequestOptions): Promise { if (!query) throw new Error(this.client.errors.NO_NICKNAME_UUID, 'Fetching Player'); query = await this.client.requestHandler.toUUID(query); const res = await this.client.requestHandler.request(`/player?uuid=${query}`, options); diff --git a/src/Client.ts b/src/Client.ts index 2b5afbb..9c94c26 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -214,7 +214,7 @@ class Client { query: string, options?: SkyblockRequestOptions ): Promise | RequestData> { - throw new Error(this.errors.ENDPOINT_NOT_LOADED, "Fetching getSkyblockMember"); + throw new Error(this.errors.ENDPOINT_NOT_LOADED, 'Fetching getSkyblockMember'); } public getSkyblockMuseum( @@ -222,7 +222,7 @@ class Client { profileId: string, options?: RequestOptions ): Promise { - throw new Error(this.errors.ENDPOINT_NOT_LOADED, "Fetching getSkyblockMuseum"); + throw new Error(this.errors.ENDPOINT_NOT_LOADED, 'Fetching getSkyblockMuseum'); } public getSkyblockNews(options?: RequestOptions): Promise { @@ -233,7 +233,7 @@ class Client { query: string, options?: SkyblockRequestOptions ): Promise { - throw new Error(this.errors.ENDPOINT_NOT_LOADED, "Fetching getSkyblockProfiles"); + throw new Error(this.errors.ENDPOINT_NOT_LOADED, 'Fetching getSkyblockProfiles'); } public getStatus(query: string, options?: RequestOptions): Promise { diff --git a/src/Private/Updater.ts b/src/Private/Updater.ts index 3a70c98..d20bd1e 100644 --- a/src/Private/Updater.ts +++ b/src/Private/Updater.ts @@ -27,7 +27,7 @@ class Updater { async getLatestVersion(): Promise { const request = await this.client.requestHandler.fetchExternalData('https://registry.npmjs.org/hypixel-api-reborn'); - if (200 !== request.status) { + if (200 !== request.statusCode) { throw new Error(this.client.errors.UPDATER_REQUEST_NOT_OK, 'Fetching Latest {packageName} version'); } return request.data['dist-tags'].latest;