diff --git a/.github/workflows/ci-cd-16x.yml b/.github/workflows/ci-cd-16x.yml index 04cb9c49..e1266b07 100644 --- a/.github/workflows/ci-cd-16x.yml +++ b/.github/workflows/ci-cd-16x.yml @@ -25,7 +25,7 @@ jobs: run: npm i - name: es-lint - run: npm run eslint + run: npm run lint tests: runs-on: ubuntu-latest diff --git a/.github/workflows/ci-cd-18x.yml b/.github/workflows/ci-cd-18x.yml index 3163a2ee..d0164108 100644 --- a/.github/workflows/ci-cd-18x.yml +++ b/.github/workflows/ci-cd-18x.yml @@ -25,7 +25,7 @@ jobs: run: npm i - name: es-lint - run: npm run eslint + run: npm run lint tests: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 106b6bfe..d183da86 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,5 @@ testing/ .circleci/ changelog.md master.json -.env \ No newline at end of file +.env +.vscode/settings.json diff --git a/package.json b/package.json index b867bbd1..ca15a5fd 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,10 @@ "url": "https://github.com/Hypixel-API-Reborn/hypixel-api-reborn/issues" }, "scripts": { - "eslint": "npx eslint src/ typings/", - "eslint:fix": "npx eslint src/ typings/ --fix", + "lint": "npx eslint src/ typings/", + "lint:fix": "npx eslint src/ typings/ --fix", "lint:dev": "npm run eslint && npm run dtslint", - "tests": "npx mocha tests --exit", + "tests": "npx mocha tests --exit --recursive", "docgen": "npx docgen -s src -o ./master.json", "dtslint": "npx dtslint typings", "prettier": "npx prettier --write src/ typings/", diff --git a/src/API/getAchievements.js b/src/API/getAchievements.js new file mode 100644 index 00000000..60c05fff --- /dev/null +++ b/src/API/getAchievements.js @@ -0,0 +1,6 @@ +module.exports = async function () { + const Achievements = require('../structures/Static/Achievements'); + const res = await this._makeRequest('/resources/achievements'); + if (res.raw) return res; + return new Achievements(res); +}; diff --git a/src/API/getChallenges.js b/src/API/getChallenges.js new file mode 100644 index 00000000..75f0eb3f --- /dev/null +++ b/src/API/getChallenges.js @@ -0,0 +1,6 @@ +module.exports = async function () { + const Challenges = require('../structures/Static/Challenges'); + const res = await this._makeRequest('/resources/challenges'); + if (res.raw) return res; + return new Challenges(res); +}; diff --git a/src/API/getGuildAchievements.js b/src/API/getGuildAchievements.js new file mode 100644 index 00000000..7dbf479d --- /dev/null +++ b/src/API/getGuildAchievements.js @@ -0,0 +1,6 @@ +module.exports = async function () { + const GuildAchievements = require('../structures/Static/GuildAchievements'); + const res = await this._makeRequest('/resources/guilds/achievements'); + if (res.raw) return res; + return new GuildAchievements(res); +}; diff --git a/src/API/getQuests.js b/src/API/getQuests.js new file mode 100644 index 00000000..0aac9662 --- /dev/null +++ b/src/API/getQuests.js @@ -0,0 +1,6 @@ +module.exports = async function () { + const Quests = require('../structures/Static/Quests'); + const res = await this._makeRequest('/resources/quests'); + if (res.raw) return res; + return new Quests(res); +}; diff --git a/src/API/index.js b/src/API/index.js index 9be62465..e2cfef2f 100644 --- a/src/API/index.js +++ b/src/API/index.js @@ -1,20 +1,32 @@ module.exports = { - getAPIStatus: require('./getAPIStatus'), - getBoosters: require('./getBoosters'), - getGameCounts: require('./getGameCounts'), - getGuild: require('./getGuild'), - getLeaderboards: require('./getLeaderboards'), - getPlayer: require('./getPlayer'), - getRecentGames: require('./getRecentGames'), - getServerInfo: require('./getServerInfo'), - getStatus: require('./getStatus'), - getWatchdogStats: require('./getWatchdogStats'), - getEndedSkyblockAuctions: require('./skyblock/getEndedSkyblockAuctions'), - getSkyblockAuctions: require('./skyblock/getSkyblockAuctions'), - getSkyblockAuctionsByPlayer: require('./skyblock/getSkyblockAuctionsByPlayer'), - getSkyblockBazaar: require('./skyblock/getSkyblockBazaar'), - getSkyblockMember: require('./skyblock/getSkyblockMember'), - getSkyblockNews: require('./skyblock/getSkyblockNews'), - getSkyblockProfiles: require('./skyblock/getSkyblockProfiles'), - getSkyblockMuseum: require('./skyblock/getSkyblockMuseum') + getAchievements: require('./getAchievements.js'), + getAPIStatus: require('./getAPIStatus.js'), + getBoosters: require('./getBoosters.js'), + getChallenges: require('./getChallenges.js'), + getGameCounts: require('./getGameCounts.js'), + getGuild: require('./getGuild.js'), + getGuildAchievements: require('./getGuildAchievements.js'), + getLeaderboards: require('./getLeaderboards.js'), + getPlayer: require('./getPlayer.js'), + getQuests: require('./getQuests.js'), + getRecentGames: require('./getRecentGames.js'), + getServerInfo: require('./getServerInfo.js'), + getStatus: require('./getStatus.js'), + getWatchdogStats: require('./getWatchdogStats.js'), + + skyblock: { + getAuction: require('./skyblock/getAuction.js'), + getAuctions: require('./skyblock/getAuctions.js'), + getAuctionsByPlayer: require('./skyblock/getAuctionsByPlayer.js'), + getBazaar: require('./skyblock/getBazaar.js'), + getBingo: require('./skyblock/getBingo.js'), + getBingoByPlayer: require('./skyblock/getBingoByPlayer.js'), + getEndedAuctions: require('./skyblock/getEndedAuctions.js'), + getFireSales: require('./skyblock/getFireSales.js'), + getGovernment: require('./skyblock/getGovernment.js'), + getMember: require('./skyblock/getMember.js'), + getMuseum: require('./skyblock/getMuseum.js'), + getNews: require('./skyblock/getNews.js'), + getProfiles: require('./skyblock/getProfiles.js') + } }; diff --git a/src/API/skyblock/getAuction.js b/src/API/skyblock/getAuction.js new file mode 100644 index 00000000..2353fb82 --- /dev/null +++ b/src/API/skyblock/getAuction.js @@ -0,0 +1,19 @@ +const Errors = require('../../Errors'); +const toUuid = require('../../utils/toUuid'); +module.exports = async function (type, query, includeItemBytes = false) { + if (!query) throw new Error(Errors.NO_NICKNAME_UUID); + const Auction = require('../../structures/SkyBlock/Auctions/Auction'); + let filter = ''; + if (type === 'PROFILE') { + filter = 'profile'; + } else if (type === 'PLAYER') { + query = await toUuid(query); + filter = 'player'; + } else if (type === 'AUCTION') { + filter = 'uuid'; + } else throw new Error(Errors.BAD_AUCTION_FILTER); + const res = await this._makeRequest(`/skyblock/auction?${filter}=${query}`); + if (res.raw) return res; + + return res.auctions.length ? res.auctions.map((a) => new Auction(a, includeItemBytes)) : []; +}; diff --git a/src/API/skyblock/getSkyblockAuctions.js b/src/API/skyblock/getAuctions.js similarity index 100% rename from src/API/skyblock/getSkyblockAuctions.js rename to src/API/skyblock/getAuctions.js diff --git a/src/API/skyblock/getSkyblockAuctionsByPlayer.js b/src/API/skyblock/getAuctionsByPlayer.js similarity index 88% rename from src/API/skyblock/getSkyblockAuctionsByPlayer.js rename to src/API/skyblock/getAuctionsByPlayer.js index c28e6537..d92aad1d 100644 --- a/src/API/skyblock/getSkyblockAuctionsByPlayer.js +++ b/src/API/skyblock/getAuctionsByPlayer.js @@ -3,7 +3,7 @@ const toUuid = require('../../utils/toUuid'); module.exports = async function (query, includeItemBytes = false) { if (!query) throw new Error(Errors.NO_NICKNAME_UUID); const Auction = require('../../structures/SkyBlock/Auctions/Auction'); - query = await toUuid(query, this.options.mojangCacheTime); + query = await toUuid(query); const res = await this._makeRequest(`/skyblock/auction?player=${query}`); if (res.raw) return res; diff --git a/src/API/skyblock/getSkyblockBazaar.js b/src/API/skyblock/getBazaar.js similarity index 100% rename from src/API/skyblock/getSkyblockBazaar.js rename to src/API/skyblock/getBazaar.js diff --git a/src/API/skyblock/getBingo.js b/src/API/skyblock/getBingo.js new file mode 100644 index 00000000..4c2baa1c --- /dev/null +++ b/src/API/skyblock/getBingo.js @@ -0,0 +1,8 @@ +module.exports = async function () { + const BingoData = require('../../structures/SkyBlock/Static/BingoData'); + + const res = await this._makeRequest('/resources/skyblock/bingo'); + if (res.raw) return res; + + return new BingoData(res); +}; diff --git a/src/API/skyblock/getBingoByPlayer.js b/src/API/skyblock/getBingoByPlayer.js new file mode 100644 index 00000000..51414d58 --- /dev/null +++ b/src/API/skyblock/getBingoByPlayer.js @@ -0,0 +1,14 @@ +const getBingo = require('./getBingo'); +const toUuid = require('../../utils/toUuid'); +const Errors = require('../../Errors'); +module.exports = async function (query, { fetchBingoData = false }) { + if (!query) throw new Error(Errors.NO_NICKNAME_UUID); + const PlayerBingo = require('../../structures/SkyBlock/PlayerBingo'); + query = await toUuid(query); + const res = await this._makeRequest(`/skyblock/uuid?player=${query}`); + if (res.raw) return res; + let bingoData = null; + if (fetchBingoData) bingoData = await getBingo.call(this); + + return new PlayerBingo(data, bingoData); +}; diff --git a/src/API/skyblock/getEndedSkyblockAuctions.js b/src/API/skyblock/getEndedAuctions.js similarity index 100% rename from src/API/skyblock/getEndedSkyblockAuctions.js rename to src/API/skyblock/getEndedAuctions.js diff --git a/src/API/skyblock/getFireSales.js b/src/API/skyblock/getFireSales.js new file mode 100644 index 00000000..cd9f8e49 --- /dev/null +++ b/src/API/skyblock/getFireSales.js @@ -0,0 +1,7 @@ +module.exports = async function () { + const FireSale = require('../../structures/SkyBlock/Static/FireSale'); + const res = await this._makeRequest('/skyblock/firesales'); + if (res.raw) return res; + + return res.sales.length ? res.sales.map((a) => new FireSale(a)) : []; +}; diff --git a/src/API/skyblock/getGovernment.js b/src/API/skyblock/getGovernment.js new file mode 100644 index 00000000..7f60aed5 --- /dev/null +++ b/src/API/skyblock/getGovernment.js @@ -0,0 +1,8 @@ +module.exports = async function () { + const GovernmentData = require('../../structures/SkyBlock/Static/Government.js'); + + const res = await this._makeRequest('/resources/skyblock/election'); + if (res.raw) return res; + + return new GovernmentData(res); +}; diff --git a/src/API/skyblock/getSkyblockMember.js b/src/API/skyblock/getMember.js similarity index 95% rename from src/API/skyblock/getSkyblockMember.js rename to src/API/skyblock/getMember.js index 907a6d85..c532d807 100644 --- a/src/API/skyblock/getSkyblockMember.js +++ b/src/API/skyblock/getMember.js @@ -3,7 +3,7 @@ const toUuid = require('../../utils/toUuid'); const getPlayer = require('../getPlayer'); module.exports = async function (query, options = { fetchPlayer: false, getMuseum: false }) { const SkyblockMember = require('../../structures/SkyBlock/SkyblockMember'); - const getSkyblockMuseum = require('../skyblock/getSkyblockMuseum'); + const getSkyblockMuseum = require('../skyblock/getMuseum'); if (!query) throw new Error(Errors.NO_NICKNAME_UUID); query = await toUuid(query, this.options.mojangCacheTime); const res = await this._makeRequest(`/skyblock/profiles?uuid=${query}`); diff --git a/src/API/skyblock/getSkyblockMuseum.js b/src/API/skyblock/getMuseum.js similarity index 100% rename from src/API/skyblock/getSkyblockMuseum.js rename to src/API/skyblock/getMuseum.js diff --git a/src/API/skyblock/getSkyblockNews.js b/src/API/skyblock/getNews.js similarity index 100% rename from src/API/skyblock/getSkyblockNews.js rename to src/API/skyblock/getNews.js diff --git a/src/API/skyblock/getSkyblockProfiles.js b/src/API/skyblock/getProfiles.js similarity index 95% rename from src/API/skyblock/getSkyblockProfiles.js rename to src/API/skyblock/getProfiles.js index 91c2f503..12c2cba7 100644 --- a/src/API/skyblock/getSkyblockProfiles.js +++ b/src/API/skyblock/getProfiles.js @@ -3,7 +3,7 @@ const toUuid = require('../../utils/toUuid'); const getPlayer = require('../getPlayer'); module.exports = async function (query, options = { fetchPlayer: false, getMuseum: false }) { const SkyblockProfile = require('../../structures/SkyBlock/SkyblockProfile'); - const getSkyblockMuseum = require('../skyblock/getSkyblockMuseum'); + const getSkyblockMuseum = require('../skyblock/getMuseum'); if (!query) throw new Error(Errors.NO_NICKNAME_UUID); query = await toUuid(query, this.options.mojangCacheTime); const res = await this._makeRequest(`/skyblock/profiles?uuid=${query}`); diff --git a/src/Client.js b/src/Client.js index a25799d6..5b040322 100644 --- a/src/Client.js +++ b/src/Client.js @@ -32,11 +32,26 @@ class Client extends EventEmitter { validate.validateOptions(this.options); // eslint-disable-next-line guard-for-in for (const func in API) { - Client.prototype[func] = function (...args) { + if (func === 'skyblock') continue; + Client.prototype[func] = (...args) => { const lastArg = args[args.length - 1]; return API[func].apply( { - _makeRequest: this._makeRequest.bind(this, { ...(validate.cacheSuboptions(lastArg) ? lastArg : {}) }), + _makeRequest: this._makeRequest.bind(this, validate.cacheSuboptions(lastArg) ? lastArg : {}), + ...this + }, + args + ); + }; + } + Client.prototype.skyblock = {}; + // eslint-disable-next-line guard-for-in + for (const func in API.skyblock) { + Client.prototype.skyblock[func] = (...args) => { + const lastArg = args[args.length - 1]; + return API.skyblock[func].apply( + { + _makeRequest: this._makeRequest.bind(this, validate.cacheSuboptions(lastArg) ? lastArg : {}), ...this }, args @@ -176,7 +191,7 @@ class Client extends EventEmitter { /** * Allows you to get all skyblock profiles of player * @method - * @name Client#getSkyblockProfiles + * @name Client.skyblock#getProfiles * @param {string} query Player nickname or UUID * @param {SkyblockMethodOptions} [options={}] Method options * @return {Promise>} @@ -190,7 +205,7 @@ class Client extends EventEmitter { /** * Allows you to get a player's skyblock member data from all their profiles * @method - * @name Client#getSkyblockMember + * @name Client.skyblock#getMember * @param {string} query Player nickname or UUID * @param {SkyblockMethodOptions} [options={}] Method options * @return {Promise>} @@ -205,7 +220,7 @@ class Client extends EventEmitter { /** * Allows you to get a player's skyblock profile museum * @method - * @name Client#getSkyblockMuseum + * @name Client.skyblock#getMuseum * @param {string} query Player nickname or UUID * @param {string} profileId Profile ID * @return {Promise} @@ -275,7 +290,7 @@ class Client extends EventEmitter { /** * Allows you to get skyblock auctions * @method - * @name Client#getSkyblockAuctions + * @name Client.skyblock#getAuctions * @param {string|number|number[]} page - "*", a page number, or an array with the start and the end page number ( automatically sorted ) * @param {auctionsOptions} [options={}] Options * @return {Promise<{info:AuctionInfo,auctions:Auction[]}>} @@ -288,7 +303,7 @@ class Client extends EventEmitter { /** * Allows you to get player's skyblock auctions * @method - * @name Client#getSkyblockAuctionsByPlayer + * @name Client.skyblock#getAuctionsByPlayer * @param {string} query - player nickname or uuid * @param {boolean} [includeItemBytes=false] - include item bytes (optional) * @param {MethodOptions} [options={}] Options @@ -315,7 +330,7 @@ class Client extends EventEmitter { /** * Allows you to get list of products * @method - * @name Client#getSkyblockBazaar + * @name Client.skyblock#getBazaar * @param {MethodOptions} [options={}] Options * @return {Promise} * @example @@ -323,11 +338,39 @@ class Client extends EventEmitter { * console.log(products[0].productId); // INK_SACK:3 * }) * .catch(console.log); + */ /** + * Allows you to get bingo data + * @method + * @name Client.skyblock#getBingo + * @param {MethodOptions} [options={}] Options + * @return {Promise} + */ + /** + * Allows you to get bingo data of a player + * @method + * @name Client.skyblock#getBingoByPlayer + * @param {string} query UUID / IGN of player + * @param {PlayerBingoOptions} [options={}] Options + * @return {Promise} + */ + /** + * Allows you to get SB government + * @method + * @name Client.skyblock#getGovernment + * @param {MethodOptions} [options={}] Options + * @return {Promise} + */ + /** + * Allows you to get SB government + * @method + * @name Client.skyblock#getFireSale + * @param {MethodOptions} [options={}] Options + * @return {Promise} */ /** * Allows you to get skyblock news * @method - * @name Client#getSkyblockNews + * @name Client.skyblock#getNews * @param {MethodOptions} [options={}] Options * @return {Promise} * @example @@ -417,4 +460,12 @@ const SkyblockMuseum = require('./structures/SkyBlock/SkyblockMuseum.js'); * @property {boolean} [includeItemBytes=false] Whether to include item bytes in the result * @prop {object} [headers={}] Extra Headers ( like User-Agent ) to add to request. Overrides the headers globally provided. */ +/** + * @typedef {object} PlayerBingoOptions + * @property {boolean} [raw=false] Raw data + * @property {boolean} [noCacheCheck=false] Disable/Enable cache checking + * @property {boolean} [noCaching=false] Disable/Enable writing to cache + * @property {boolean} [fetchBingoData=false] Fetches bingo data to give more information + * @prop {object} [headers={}] Extra Headers ( like User-Agent ) to add to request. Overrides the headers globally provided. + */ module.exports = Client; diff --git a/src/Errors.js b/src/Errors.js index 4bdb238d..1edc0128 100644 --- a/src/Errors.js +++ b/src/Errors.js @@ -43,5 +43,6 @@ module.exports = { "[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: '[hypixel-api-reborn] The player has no skyblock profiles.', INVALID_SILENT_OPTION: '[hypixel-api-reborn] Invalid Value for silent : must be a boolean', - INVALID_UPDATE_OPTION: '[hypixel-api-reborn] Invalid Value for silent : must be a boolean' + INVALID_UPDATE_OPTION: '[hypixel-api-reborn] Invalid Value for update : must be a boolean', + BAD_AUCTION_FILTER: '[hypixel-api-reborn] Unexpected filter for Client#getSkyblockAuction. Expected one of "PLAYER", "AUCTION", "PROFILE", but got something else.' }; diff --git a/src/index.js b/src/index.js index be9d51cd..00b37af5 100644 --- a/src/index.js +++ b/src/index.js @@ -14,6 +14,10 @@ module.exports = { Pets: require('./structures/Pets'), PlayerCosmetics: require('./structures/PlayerCosmetics'), + /* Challenges */ + Challenges: require('./structures/Static/Challenges.js'), + GameChallenges: require('./structures/Static/GameChallenges.js'), + /* Watchdog */ WatchdogStats: require('./structures/Watchdog/Stats.js'), @@ -27,6 +31,10 @@ module.exports = { SkyblockMember: require('./structures/SkyBlock/SkyblockMember.js'), SkyblockInventoryItem: require('./structures/SkyBlock/SkyblockInventoryItem.js'), SkyblockPet: require('./structures/SkyBlock/SkyblockPet'), + GovernmentData: require('./structures/SkyBlock/Static/Government.js'), + Candidate: require('./structures/SkyBlock/Static/Candidate.js'), + BingoData: require('./structures/SkyBlock/Static/BingoData.js'), + Bingo: require('./structures/SkyBlock/Static/Bingo.js'), /* Skyblock Auctions */ BaseAuction: require('./structures/SkyBlock/Auctions/BaseAuction.js'), @@ -34,6 +42,7 @@ module.exports = { Auction: require('./structures/SkyBlock/Auctions/Auction.js'), AuctionInfo: require('./structures/SkyBlock/Auctions/AuctionInfo.js'), Bid: require('./structures/SkyBlock/Auctions/Bid.js'), + FireSale: require('./structures/SkyBlock/Static/FireSale.js'), /* Skyblock Bazaar */ Product: require('./structures/SkyBlock/Bazzar/Product.js'), diff --git a/src/structures/SkyBlock/Auctions/Auction.js b/src/structures/SkyBlock/Auctions/Auction.js index 473b67d9..06f6c3f6 100644 --- a/src/structures/SkyBlock/Auctions/Auction.js +++ b/src/structures/SkyBlock/Auctions/Auction.js @@ -1,5 +1,5 @@ -const Bid = require('./Bid'); const BaseAuction = require('./BaseAuction'); +const Bid = require('./Bid'); /** * Auction class */ @@ -89,6 +89,7 @@ class Auction extends BaseAuction { return this.item; } } + /** * @typedef {string} Rarity * * `VERY_SPECIAL` @@ -101,4 +102,5 @@ class Auction extends BaseAuction { * * `UNCOMMON` * * `COMMON` */ + module.exports = Auction; diff --git a/src/structures/SkyBlock/Auctions/AuctionInfo.js b/src/structures/SkyBlock/Auctions/AuctionInfo.js index f2b61461..003471c4 100644 --- a/src/structures/SkyBlock/Auctions/AuctionInfo.js +++ b/src/structures/SkyBlock/Auctions/AuctionInfo.js @@ -51,4 +51,5 @@ class AuctionInfo { return `${this.page} / ${this.totalPages}`; } } + module.exports = AuctionInfo; diff --git a/src/structures/SkyBlock/Auctions/BaseAuction.js b/src/structures/SkyBlock/Auctions/BaseAuction.js index 0d119ef1..946d32b8 100644 --- a/src/structures/SkyBlock/Auctions/BaseAuction.js +++ b/src/structures/SkyBlock/Auctions/BaseAuction.js @@ -42,4 +42,5 @@ class BaseAuction { return this.auctionId; } } + module.exports = BaseAuction; diff --git a/src/structures/SkyBlock/Auctions/Bid.js b/src/structures/SkyBlock/Auctions/Bid.js index ea6cadbf..dc884522 100644 --- a/src/structures/SkyBlock/Auctions/Bid.js +++ b/src/structures/SkyBlock/Auctions/Bid.js @@ -45,4 +45,5 @@ class Bid { return `${this.bidder} bid ${this.amount} coins`; } } + module.exports = Bid; diff --git a/src/structures/SkyBlock/Auctions/PartialAuction.js b/src/structures/SkyBlock/Auctions/PartialAuction.js index 0b98717b..a6265710 100644 --- a/src/structures/SkyBlock/Auctions/PartialAuction.js +++ b/src/structures/SkyBlock/Auctions/PartialAuction.js @@ -22,4 +22,5 @@ class PartialAuction extends BaseAuction { this.price = parseInt(data.price, 10) || 0; } } + module.exports = PartialAuction; diff --git a/src/structures/SkyBlock/PlayerBingo.js b/src/structures/SkyBlock/PlayerBingo.js new file mode 100644 index 00000000..eb7930ab --- /dev/null +++ b/src/structures/SkyBlock/PlayerBingo.js @@ -0,0 +1,65 @@ +/** + * @typedef {import('./Static/BingoData.js')} BingoData + * @typedef {import('./Static/Bingo.js')} Bingo + */ + +/** + * Player Bingo Class + */ +class PlayerBingo { + /** + * Constructor + * @param {Object} data data + * @param {BingoData|null} bingoData bingo data + */ + constructor(data, bingoData) { + const events = data.success && Array.isArray(data.events) ? data.events : []; + /** + * Data per event + * @type {PlayerBingoDataPerEvent} + */ + this.dataPerEvent = events.map((eventData) => { + let doneGoals = eventData.completed_goals; + if (!Array.isArray(doneGoals)) doneGoals = []; + const enrichable = parseInt(eventData.key, 10) === bingoData?.id; + if (enrichable) doneGoals = populateGoals(doneGoals, bingoData.goals); + return { + eventId: parseInt(eventData.key, 10) || null, + points: parseInt(eventData.points, 10) || 0, + goalsCompleted: doneGoals, + enrichedGoals: enrichable + }; + }); + } +} + +/** + * Populate goals + * For compatibility and lazy handling, uncompleted goals will be hidden in a property + * @param {string[]} achieved achieved goals + * @param {Bingo[]} all All goals + * @returns {SpecialBingoArray} + */ +function populateGoals(achieved, all) { + const populatedAchieved = []; + const unachieved = []; + for (const goal of all) { + if (achieved.find((str) => str === goal.name)) populatedAchieved.push(goal); + else unachieved.push(goal); + } + populatedAchieved.unachievedGoals = unachieved; + return populatedAchieved; +} + +/** + * @typedef {Bingo[] & {'unachievedGoals': Bingo[]}} SpecialBingoArray + */ +/** + * @typedef {Object} PlayerBingoDataPerEvent + * @property {number} eventId ID of event + * @property {number} points Points acquired + * @property {boolean} enrichedGoals Whether the goals are enriched (populated with data from static skyblock bingp data) + * @property {SpecialBingoArray|string[]} goalsCompleted Special Bingo Array if enrichedGoals is true. You can however always treat SpecialBingoArray as string[] + */ + +module.exports = PlayerBingo; diff --git a/src/structures/SkyBlock/Static/Bingo.js b/src/structures/SkyBlock/Static/Bingo.js new file mode 100644 index 00000000..1c4c429e --- /dev/null +++ b/src/structures/SkyBlock/Static/Bingo.js @@ -0,0 +1,106 @@ +/** + * Bingo class + */ +class Bingo { + /** + * Constructor + * @param {Object} data data + * @param {number} position Position + */ + constructor(data, position = 0) { + /** + * Name of this bingo goal + * @type {string} + */ + this.name = data.name; + /** + * string ID (code name) + * @type {string} + */ + this.id = data.id; + const [row, column] = parsePosition(position); + /** + * 1-indexed row + * @type {number|null} + */ + this.row = row; + /** + * 1-indexed colmun + * @type {number|null} + */ + this.column = column; + /** + * Bingo lore, with color codes + * @type {string} + */ + this.rawLore = data.lore; + /** + * Bingo lore in plain text + * @type {string} + */ + this.lore = data.lore?.replace?.(/§([1-9]|[a-l])|§/gm, '') || null; + /** + * Only available for TIERED bingos + * Shows you the requirement for each tier of this achievement + * @type {number[]} + */ + this.tiers = Array.isArray(data.tiers) ? data.tiers.map((x) => parseInt(x, 10) || 0) : null; + /** + * Only available for TIERED bingos + * Difference between each tier requirement, if it is constant + * @type {number|null} + */ + this.tierStep = this.#getTierStep(); + /** + * Only available for ONE_TIERED bingos + * @type {number|null} + */ + this.requiredAmount = parseInt(data.requiredAmount, 10) ?? null; + /** + * Type of Bingo + * ONE_TIME means the goal doesn't have a specific amount + * ONE_TIER means the goal specifies 1 amount to achieve + * TIERED means the goal specifies more than 1 amount to achieve + * @type {'ONE_TIME'|'ONE_TIER'|'TIERED'} + */ + this.type = this.tiers ? 'TIERED' : this.requiredAmount ? 'ONE_TIER' : 'ONE_TIME'; + } + /** + * As string + * BEWARE this returns ID to assure compatibility with PlayerBingo + * @return {string} + */ + toString() { + return this.id; + } + /** + * Gets tier step, if constant + * @private + * @returns {number|null} + */ + #getTierStep() { + if (this.type !== 'TIERED') return null; + // No step possible + if (this.tiers.length < 2) return null; + const hypotheticStep = this.tiers[1] - this.tiers[0]; + // Check if every 2 elements have the same step + const isConstant = this.tiers.slice(1).every((el, index) => { + return hypotheticStep === this.tiers[index - 1] - el; + }); + if (!isConstant) return null; + return hypotheticStep; + } +} + +/** + * Parses row and col from position, assuming bingo table is 5x5 + * @param {number} position Position + * @returns {number[]} + */ +function parsePosition(position) { + const x = (position % 5) + 1; + const y = Math.floor(position / 5) + 1; + return [x, y]; +} + +module.exports = Bingo; diff --git a/src/structures/SkyBlock/Static/BingoData.js b/src/structures/SkyBlock/Static/BingoData.js new file mode 100644 index 00000000..e82c2b0d --- /dev/null +++ b/src/structures/SkyBlock/Static/BingoData.js @@ -0,0 +1,45 @@ +const Bingo = require('./Bingo.js'); + +/** + * SB Bingo Class + */ +class BingoData { + /** + * constructor + * @param {Object} data + */ + constructor(data) { + /** + * Last time this resource was updated + * @type {number} + */ + this.lastUpdatedTimestamp = parseInt(data.lastUpdated, 10); + /** + * Last time this resource was updated, as Date + * @type {Date|null} + */ + this.lastUpdatedAt = new Date(this.lastUpdatedTimestamp); + /** + * Bingo ID + * @type {number|null} + */ + this.id = parseInt(data.id, 10) || null; + /** + * Goals + * @type {Bingo[]|null} + */ + this.goals = Array.isArray(data.goals) ? data.goals.map((goal, index) => new Bingo(goal, index)) : null; + } + /** + * Gets a goal on the bingo table by row and column + * @param {number} column Column number (starts at 1) + * @param {number} row Row number (starts at 1) + * @returns {Bingo|undefined} + */ + getGoal(column, row) { + if (!this.goals || this.goals.length < 1) return; + return this.goals.find((goal) => goal.row === row && goal.column === column); + } +} + +module.exports = BingoData; diff --git a/src/structures/SkyBlock/Static/Candidate.js b/src/structures/SkyBlock/Static/Candidate.js new file mode 100644 index 00000000..63a13c44 --- /dev/null +++ b/src/structures/SkyBlock/Static/Candidate.js @@ -0,0 +1,39 @@ +/** + * Candidate class + */ +class Candidate { + /** + * Constructor + * @param {Object} data data + * @param {boolean} [isMayor=false] if this candidate is the current mayor + */ + constructor(data, isMayor = false) { + /** + * Mayor's name + * @type {string} + */ + this.name = data.name; + /** + * Mayor's Key Benefit (in 1 word) + * @type {string} + */ + this.keyBenefit = data.key; + /** + * Perks + * @type {Record<'name'|'description',string>[]} + */ + this.perks = data.perks; + /** + * If this candidate is the current mayor + * @type {boolean} + */ + this.isMayor = isMayor || false; + /** + * The number of votes received by this candidate + * @type {number} + */ + this.votesReceived = parseInt(data.votes, 10) || 0; + } +} + +module.exports = Candidate; diff --git a/src/structures/SkyBlock/Static/FireSale.js b/src/structures/SkyBlock/Static/FireSale.js new file mode 100644 index 00000000..20dfa18c --- /dev/null +++ b/src/structures/SkyBlock/Static/FireSale.js @@ -0,0 +1,55 @@ +/** + * SB Fire Sale + */ +class FireSale { + /** + * constructor + * @param {Object} data + */ + constructor(data) { + /** + * Item ID + * @type {string|null} + */ + this.itemId = data.item_id || null; + /** + * Start Timestamp as a unix + * @type {number} + */ + this.startTimestamp = parseInt(data.start, 10); + /** + * Start Date + * @type {Date} + */ + this.startAt = new Date(this.startTimestamp); + /** + * End Timestamp as a unix + * @type {number} + */ + this.endTimestamp = parseInt(data.end, 10); + /** + * End Date + * @type {Date} + */ + this.endAt = new Date(this.endTimestamp); + /** + * Amount of items being sold + * @type {number} + */ + this.amount = data.amount || 0; + /** + * Price + * @type {number} + */ + this.price = data.price || 0; + } + /** + * Item Id + * @return {string|null} + */ + toString() { + return this.itemId; + } +} + +module.exports = FireSale; diff --git a/src/structures/SkyBlock/Static/Government.js b/src/structures/SkyBlock/Static/Government.js new file mode 100644 index 00000000..9aeae4ca --- /dev/null +++ b/src/structures/SkyBlock/Static/Government.js @@ -0,0 +1,74 @@ +const Candidate = require('./Candidate'); + +/** + * SB Government Class + */ +class GovernmentData { + /** + * constructor + * @param {Object} data + */ + constructor(data) { + /** + * Last time this resource was updated + * @type {number} + */ + this.lastUpdatedTimestamp = parseInt(data.lastUpdated, 10); + /** + * Last time this resource was updated, as Date + * @type {Date|null} + */ + this.lastUpdatedAt = new Date(this.lastUpdatedTimestamp); + const lastElectionResults = data.mayor.election.candidates.map((x) => new Candidate(x, x.name === data.mayor.name)); + /** + * A map of last election results for each candidate + * Sorted ascendingly by votes received + * @type {Map} + */ + this.lastElectionResults = new Map( + lastElectionResults + .sort((a, b) => a.votesReceived - b.votesReceived) + .reverse() + .map((x) => [x.name, x]) + ); + /** + * The mayor + * @type {Candidate} + */ + this.mayor = this.lastElectionResults.get(data.mayor.name); + /** + * The year the mayor will be running for + * @type {number} + */ + this.runningYear = parseInt(data.mayor.election.year, 10) || 0; + const thisElection = data.current?.candidates.map((x) => new Candidate(x, x.name === data.mayor.name)) || null; + /** + * Current elections, valid for next year + * Sorted ascendingly by votes received + * RESULTS MIGHT BE TEMPORARY + * @type {Map|null} + */ + this.currentElectionResults = thisElection + ? new Map( + thisElection + .sort((a, b) => a.votesReceived - b.votesReceived) + .reverse() + .map((x) => [x.name, x]) + ) + : null; + /** + * The year the current election will be effective for + * @type {number|null} + */ + this.currentElectionFor = parseInt(data.current?.year, 10) || null; + } + /** + * Current Mayor + * @return {string} + */ + toString() { + return this.mayor.name; + } +} + +module.exports = GovernmentData; diff --git a/src/structures/Static/Achievement.js b/src/structures/Static/Achievement.js new file mode 100644 index 00000000..ea44cc48 --- /dev/null +++ b/src/structures/Static/Achievement.js @@ -0,0 +1,89 @@ +const AchievementTier = require('./AchievementTier'); + +/** + * Achievement Class + */ +class Achievement { + /** + * constructor + * @param {string} achievementName Name of achievement + * @param {Object} data + */ + constructor(achievementName, data) { + /** + * Name of achievement, trimmed trailing spaces + * @type {string} + */ + this.name = data.name.trim(); + /** + * Code name of achievement + * @type {string} + */ + this.codeName = achievementName; + /** + * Description, trimmed trailing spaces + * @type {string} + */ + this.description = data.description.trim(); + /** + * Type of achievement + * @type {'ONE_TIME'|'TIERED'} + */ + this.type = data.tiers ? 'TIERED' : 'ONE_TIME'; + /** + * ONLY AVAILABLE IN PERSONAL ONE TIME ACHIEVEMENTS, last checked April 26th + * Unlock rate of this achievement + * Local : Fraction of players that have played the game and gotten this achievement (0 to 1 inclusive) + * Global : Fraction of players that have played Hypixel and gotten this achievement (0 to 1 inclusive) + * ...percentage : In percentage (0 to 100 inclusive) + * @type {Record<'local'|'localPercentage'|'global'|'globalPercentage', number>|null} + */ + this.rarity = { + local: parseFloat(data.gamePercentUnlocked) || 0, + localPercentage: parseFloat(data.gamePercentUnlocked) * 100 || 0, + global: data.globalPercentUnlocked, + globalPercentage: parseFloat(data.globalPercentUnlocked) * 100 || 0 + }; + /** + * ONLY AVAILABLE FOR TIERED + * @type {AchievementTier|null} + */ + this.tierInformation = this.type === 'TIERED' ? new AchievementTier(data.tiers) : null; + + const { totalPoints, totalAmount } = this.type === 'TIERED' ? collectAll(this.tierInformation) : {}; + /** + * Total points worth (sum of all tiers if tiered) + * This is always 0 for Guild Achievements + * @type {number} + */ + this.points = this.type === 'ONE_TIME' ? parseInt(data.points, 10) : totalPoints; + /** + * Total amount required to reach max tier, only for tiered + * @type {number|null} + */ + this.totalAmountRequired = this.type === 'TIERED' ? totalAmount : null; + } + /** + * As string + * @return {string} + */ + toString() { + return this.achievementName; + } +} + +/** + * @param {AchievementTier} data + * @returns {number} + */ +function collectAll(data) { + const mTier = data.maxTier; + let totalPoints = 0; + let totalAmount = 0; + for (let i = 1; i <= mTier; i++) { + totalPoints += data.getTier(i).pointsRewarded; + totalAmount += data.getTier(i).amountRequired; + } + return { totalPoints, totalAmount }; +} +module.exports = Achievement; diff --git a/src/structures/Static/AchievementTier.js b/src/structures/Static/AchievementTier.js new file mode 100644 index 00000000..a79532e8 --- /dev/null +++ b/src/structures/Static/AchievementTier.js @@ -0,0 +1,33 @@ +/** + * AchievementTier class + */ +class AchievementTier { + /** + * @param {Record[]} data data + */ + constructor(data) { + /** + * Maximum tier reachable + * getTier will be take any integer from 1 to this number (inclusive) + * @type {number} + */ + this.maxTier = data.length; + // Still make sure it is well sorted + this._tierInfo = data.sort(({ tier: tierA }, { tier: tierB }) => Number(tierA) - Number(tierB)); + } + /** + * Gets information for tier + * @param {number} tier Tier number (1-indexed!) + * @returns {Record<'pointsRewarded'|'amountRequired', number>} + */ + getTier(tier) { + const index = tier - 1; + const info = this._tierInfo[index]; + return { + pointsRewarded: parseInt(info.points, 10) || 0, + amountRequired: parseInt(info.amount, 10) || 0 + }; + } +} + +module.exports = AchievementTier; diff --git a/src/structures/Static/Achievements.js b/src/structures/Static/Achievements.js new file mode 100644 index 00000000..05c8ac44 --- /dev/null +++ b/src/structures/Static/Achievements.js @@ -0,0 +1,32 @@ +const GameAchievements = require('./GameAchievements.js'); + +/** + * Achievement class + */ +class Achievements { + /** + * @param {object} data data + */ + constructor(data) { + /** + * Last time this resource was updated + * @type {number} + */ + this.lastUpdatedTimestamp = parseInt(data.lastUpdated, 10); + /** + * Last time this resource was updated, as Date + * @type {Date|null} + */ + this.lastUpdatedAt = new Date(this.lastUpdatedTimestamp); + /** + * @type {Record} + */ + this.achievementsPerGame = Object.fromEntries(Object.entries(data.achievements).map(([game, data]) => [game, new GameAchievements(game, data)])); + } +} + +/** + * @typedef {import('../../utils/Constants.js').gamesStatic} StaticGameNames + */ + +module.exports = Achievements; diff --git a/src/structures/Static/Challenges.js b/src/structures/Static/Challenges.js new file mode 100644 index 00000000..e86744b6 --- /dev/null +++ b/src/structures/Static/Challenges.js @@ -0,0 +1,27 @@ +const GameChallenges = require('./GameChallenges.js'); +/** + * Achievement class + */ +class Challenges { + /** + * @param {object} data data + */ + constructor(data) { + /** + * Last time this resource was updated + * @type {number} + */ + this.lastUpdatedTimestamp = parseInt(data.lastUpdated, 10); + /** + * Last time this resource was updated, as Date + * @type {Date|null} + */ + this.lastUpdatedAt = new Date(this.lastUpdatedTimestamp); + /** + * @type {Record} + */ + this.challengesPerGame = Object.fromEntries(Object.entries(data.challenges).map(([game, data]) => [game, new GameChallenges(game, data)])); + } +} + +module.exports = Challenges; diff --git a/src/structures/Static/GameAchievements.js b/src/structures/Static/GameAchievements.js new file mode 100644 index 00000000..01194cc2 --- /dev/null +++ b/src/structures/Static/GameAchievements.js @@ -0,0 +1,37 @@ +const Achievement = require('./Achievement'); + +/** + * Game achievements class + */ +class GameAchievements { + /** + * @param {string} name game name + * @param {object} data data + */ + constructor(name, data) { + /** + * Name of game/category + * @type {StaticGameNames} + */ + this.category = name; + /** + * Total points possible from all achievements in this game + * @type {number} + */ + this.totalPoints = parseInt(data.total_points, 10) || 0; + /** + * Total legacy points possible from all achievements in this game + * @type {number} + */ + this.totalLegacyPoints = parseInt(data.total_legacy_points, 10) || 0; + /** + * @type {Achievement[]} + */ + this.achievements = Object.entries({ ...(data.one_time || {}), ...(data.tiered || {}) }).map(([name, data]) => new Achievement(name, data)); + } +} + +/** + * @typedef {import('../../utils/Constants.js').gamesStatic} StaticGameNames + */ +module.exports = GameAchievements; diff --git a/src/structures/Static/GameChallenges.js b/src/structures/Static/GameChallenges.js new file mode 100644 index 00000000..3254aa9f --- /dev/null +++ b/src/structures/Static/GameChallenges.js @@ -0,0 +1,40 @@ +/** + * Game challenges class + */ +class GameChallenges { + /** + * @param {string} name game name + * @param {object} data data + */ + constructor(name, data) { + /** + * Name of game/category + * @type {StaticGameNames} + */ + this.category = name; + /** + * @type {Map} + */ + this.challenges = new Map(); + + data.forEach((challenge) => { + const content = { + id: challenge.id, + name: challenge.name, + reward: parseInt(challenge.rewards[0].amount, 10) || 0, + rewardType: challenge.rewards[0].type + }; + this.challenges.set(challenge.id, content); + }); + } +} + +/** + * @typedef {Object} ChallengeData + * @property {string} id String ID of the challenge + * @property {string} name String name of the challenge + * @property {number} reward Amount of XP upon challenge completion, always 3700 XP. + * @property {string} rewardType Type of reward. Always "MultipliedExperienceReward" here + */ + +module.exports = GameChallenges; diff --git a/src/structures/Static/GameQuests.js b/src/structures/Static/GameQuests.js new file mode 100644 index 00000000..8ce51c06 --- /dev/null +++ b/src/structures/Static/GameQuests.js @@ -0,0 +1,27 @@ +const Quest = require('./Quest'); + +/** + * Game quests class + */ +class GameQuests { + /** + * @param {string} name game name + * @param {object} data data + */ + constructor(name, data) { + /** + * Name of game + * @type {StaticGameNames} + */ + this.game = name; + /** + * @type {Quest[]} + */ + this.quests = (data || []).map((x) => new Quest(x)); + } +} + +/** + * @typedef {import('../../utils/Constants.js').gamesStatic} StaticGameNames + */ +module.exports = GameQuests; diff --git a/src/structures/Static/GuildAchievements.js b/src/structures/Static/GuildAchievements.js new file mode 100644 index 00000000..92f83b4f --- /dev/null +++ b/src/structures/Static/GuildAchievements.js @@ -0,0 +1,29 @@ +const Achievement = require('./Achievement.js'); + +/** + * Achievement class + */ +class GuildAchievements { + /** + * @param {object} data data + */ + constructor(data) { + /** + * Last time this resource was updated + * @type {number} + */ + this.lastUpdatedTimestamp = parseInt(data.lastUpdated, 10); + /** + * Last time this resource was updated, as Date + * @type {Date|null} + */ + this.lastUpdatedAt = new Date(this.lastUpdatedTimestamp); + /** + * Achievements + * @type {Record} + */ + this.achievements = Object.fromEntries(Object.entries({ ...(data.tiered || {}), ...(data.one_time || {}) }).map(([name, value]) => [name, new Achievement(name, value)])); + } +} + +module.exports = GuildAchievements; diff --git a/src/structures/Static/Quest.js b/src/structures/Static/Quest.js new file mode 100644 index 00000000..1f402671 --- /dev/null +++ b/src/structures/Static/Quest.js @@ -0,0 +1,66 @@ +/** + * Quest Class + */ +class Quest { + /** + * constructor + * @param {Object} data + */ + constructor(data) { + /** + * Name of quest, trimmed trailing spaces + * @type {string} + */ + this.questName = data.name.trim(); + /** + * ID of quest + * @type {string} + */ + this.questID = data.id; + /** + * Description, trimmed trailing spaces + * @type {string} + */ + this.description = data.description.trim(); + /** + * Type of quest + * @type {'DAILY'|'WEEKLY'} + */ + this.type = data.requirements?.[0].type === 'DailyResetQuestRequirement' ? 'DAILY' : 'WEEKLY'; + /** + * Objectives + * @type {Objective[]} + */ + this.objectives = data.objectives.map((objective) => ({ + id: objective.id, + type: objective.type === 'IntegerObjective' ? 'Integer' : 'Boolean', + amountNeeded: parseInt(objective.integer || '1', 10) + })); + /** + * Rewards for this quest + * @type {QuestReward[]} + */ + this.rewards = data.rewards || []; + } + /** + * As string + * @return {string} + */ + toString() { + return this.questName; + } +} + +/** + * @typedef {Object} Objective + * @property {string} id ID + * @property {'Integer'|'Boolean'} type Integer: a certain amount of something (i.e kills) is needed; Boolean: a condition needs to be fulfilled + * @property {number} amountNeeded a Boolean-typed objective will have this set to 1. (instead of null in API) + */ +/** + * @typedef {Object} QuestReward + * @property {string} type Types of reward. + * @property {number} amount Amount + */ + +module.exports = Quest; diff --git a/src/structures/Static/Quests.js b/src/structures/Static/Quests.js new file mode 100644 index 00000000..88446d05 --- /dev/null +++ b/src/structures/Static/Quests.js @@ -0,0 +1,33 @@ +const GameQuests = require('./GameQuests.js'); + +/** + * Quest class + */ +class Quests { + /** + * @param {object} data data + */ + constructor(data) { + /** + * Last time this resource was updated + * @type {number} + */ + this.lastUpdatedTimestamp = parseInt(data.lastUpdated, 10); + /** + * Last time this resource was updated, as Date + * @type {Date|null} + */ + this.lastUpdatedAt = new Date(this.lastUpdatedTimestamp); + /** + * Quests per game + * @type {Record} + */ + this.questsPerGame = Object.fromEntries(Object.entries(data.quests).map(([game, data]) => [game, new GameQuests(game, data)])); + } +} + +/** + * @typedef {import('../../utils/Constants.js').gamesStatic} StaticGameNames + */ + +module.exports = Quests; diff --git a/tests/Client#getChallenges.js b/tests/Client#getChallenges.js new file mode 100644 index 00000000..c352d141 --- /dev/null +++ b/tests/Client#getChallenges.js @@ -0,0 +1,42 @@ +/* eslint-disable no-undef */ +const { GameChallenges } = require('../src/index.js'); +const { client } = require('./Client.js'); +const { expect } = require('chai'); + +describe('Client#getChallenges', async () => { + let challenges; + it('expect not to throw', async () => { + challenges = await client.getChallenges(); + }); + it('should be an objecct', () => { + expect(challenges).to.be.an('object'); + }); + it('required keys should exist', () => { + expect(challenges.lastUpdatedTimestamp).to.be.a('number'); + expect(challenges.lastUpdatedAt).to.be.a('date'); + expect(challenges.challengesPerGame).to.be.an('object'); + expect(challenges.challengesPerGame.arcade).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.arena).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.bedwars).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.hungergames).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.buildbattle).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.truecombat).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.duels).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.mcgo).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.murdermystery).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.paintball).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.quake).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.skyclash).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.skywars).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.supersmash).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.speeduhc).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.gingerbread).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.tntgames).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.uhc).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.vampirez).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.walls3).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.walls).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.battleground).instanceOf(GameChallenges); + expect(challenges.challengesPerGame.woolgames).instanceOf(GameChallenges); + }); +}); diff --git a/tests/Client#getGuild.js b/tests/Client#getGuild.js index ee20d596..5ee4ab44 100644 --- a/tests/Client#getGuild.js +++ b/tests/Client#getGuild.js @@ -397,7 +397,7 @@ describe('Client#getGuild', async () => { }); describe('Player not in guild', async () => { it('expect to throw', async () => { - player = await client.getGuild('player', '337a48bf57e944eb8acb83b885936e83'); + player = await client.getGuild('player', '49e9ffd075e6413395ba96051b3fea84'); expect(player).to.be.null; }).timeout(5000); }); diff --git a/tests/skyblock/Client.skyblock#getBingo.js b/tests/skyblock/Client.skyblock#getBingo.js new file mode 100644 index 00000000..39c79c4e --- /dev/null +++ b/tests/skyblock/Client.skyblock#getBingo.js @@ -0,0 +1,17 @@ +/* eslint-disable no-undef */ +const { BingoData } = require('../../src'); +const { client } = require('../Client.js'); +const { expect } = require('chai'); + +describe('Client.skyblock#getBingo', async () => { + let bingo; + it('expect not to throw', async () => { + bingo = await client.skyblock.getBingo(); + }); + it('should be an objecct', () => { + expect(bingo).to.be.an('object'); + }); + it('required keys should exist', () => { + expect(bingo).instanceOf(BingoData); + }); +}); diff --git a/tests/skyblock/Client.skyblock#getFireSales.js b/tests/skyblock/Client.skyblock#getFireSales.js new file mode 100644 index 00000000..afd290a5 --- /dev/null +++ b/tests/skyblock/Client.skyblock#getFireSales.js @@ -0,0 +1,19 @@ +/* eslint-disable no-undef */ +const { FireSale } = require('../../src/index.js'); +const { client } = require('../Client.js'); +const { expect } = require('chai'); + +describe('Client.skyblock#getFireSales', async () => { + let fireSales; + it('expect not to throw', async () => { + fireSales = await client.skyblock.getFireSales(); + }); + it('should be an objecct', () => { + expect(fireSales).to.be.an('array'); + }); + it('required keys should exist', () => { + fireSales.forEach((sale) => { + expect(sale).instanceOf(FireSale); + }); + }); +}); diff --git a/tests/skyblock/Client.skyblock#getGovernment.js b/tests/skyblock/Client.skyblock#getGovernment.js new file mode 100644 index 00000000..664eba01 --- /dev/null +++ b/tests/skyblock/Client.skyblock#getGovernment.js @@ -0,0 +1,30 @@ +/* eslint-disable no-undef */ +const { Candidate } = require('../../src'); +const { client } = require('../Client.js'); +const { expect } = require('chai'); + +describe('Client.skyblock#getGovernment', async () => { + let government; + it('expect not to throw', async () => { + government = await client.skyblock.getGovernment(); + }); + it('should be an objecct', () => { + expect(government).to.be.an('object'); + }); + it('required keys should exist', () => { + expect(government.lastUpdatedTimestamp).to.be.a('number'); + expect(government.lastUpdatedAt).to.be.a('date'); + government.lastElectionResults.forEach((mayor) => { + expect(mayor).instanceOf(Candidate); + }); + expect(government.mayor).instanceOf(Candidate); + expect(government.runningYear).to.be.a('number'); + expect(government.currentElectionResults).to.be.oneOf([null, 'map']); + if (government.currentElectionResults) { + government.currentElectionResults.forEach((mayor) => { + expect(mayor).instanceOf(Candidate); + }); + } + expect(government.currentElectionFor).to.be.oneOf(['number', null]); + }); +}); diff --git a/tests/Client#getSkyblockMember.js b/tests/skyblock/Client.skyblock#getMember.js similarity index 79% rename from tests/Client#getSkyblockMember.js rename to tests/skyblock/Client.skyblock#getMember.js index dd663dcc..e5eefe59 100644 --- a/tests/Client#getSkyblockMember.js +++ b/tests/skyblock/Client.skyblock#getMember.js @@ -1,6 +1,6 @@ /* eslint-disable no-undef */ -const { SkyblockMember, Errors } = require('../src'); -const { client } = require('./Client.js'); +const { SkyblockMember, Errors } = require('../../src'); +const { client } = require('../Client.js'); const { expect } = require('chai'); const uuids = [ @@ -14,13 +14,13 @@ const uuids = [ const usernames = ['kathund', 'StavZDev', 'Plancke', 'SoupyRaccn', 'duckysoskilled', 'Altpapier']; -describe('Client#getSkyblockMember', async () => { +describe('Client.skyblock#getMember', async () => { describe('Valid', async () => { uuids.forEach((uuid) => { let member; describe(`UUID Test ${uuids.indexOf(uuid) + 1}`, async () => { it('expect not to throw', async () => { - member = await client.getSkyblockMember(uuid); + member = await client.skyblock.getMember(uuid); }); it('should be an map', () => { expect(member).to.be.an('map'); @@ -34,9 +34,9 @@ describe('Client#getSkyblockMember', async () => { }); usernames.forEach((username) => { let member; - describe(`Username Test ${uuids.indexOf(username) + 1}`, async () => { + describe(`Username Test ${usernames.indexOf(username) + 1}`, async () => { it('expect not to throw', async () => { - member = await client.getSkyblockMember(username); + member = await client.skyblock.getMember(username); }); it('should be an map', () => { expect(member).to.be.an('map'); @@ -53,7 +53,7 @@ describe('Client#getSkyblockMember', async () => { describe('Never Played skyblock', async () => { it('expect to throw', async () => { try { - member = await client.getSkyblockMember('b45add7b081443909fb00aa9a3e15eb0'); + member = await client.skyblock.getMember('b45add7b081443909fb00aa9a3e15eb0'); throw new Error('Expected an error to be thrown, but no error was thrown.'); } catch (error) { expect(error.message).to.equal(Errors.NO_SKYBLOCK_PROFILES); diff --git a/tests/Client#getSkyblockNews.js b/tests/skyblock/Client.skyblock#getNews.js similarity index 77% rename from tests/Client#getSkyblockNews.js rename to tests/skyblock/Client.skyblock#getNews.js index aac76cd9..0e27a4b4 100644 --- a/tests/Client#getSkyblockNews.js +++ b/tests/skyblock/Client.skyblock#getNews.js @@ -1,11 +1,11 @@ /* eslint-disable no-undef */ -const { client } = require('./Client.js'); +const { client } = require('../Client.js'); const { expect } = require('chai'); -describe('Client#getSkyblockNews', async () => { +describe('Client.skyblock#getNews', async () => { let news; it('expect not to throw', async () => { - news = await client.getSkyblockNews(); + news = await client.skyblock.getNews(); }); it('Should be an array', () => { expect(news).to.be.an('array'); diff --git a/tests/Client#getSkyblockProfiles.js b/tests/skyblock/Client.skyblock#getProfiles.js similarity index 89% rename from tests/Client#getSkyblockProfiles.js rename to tests/skyblock/Client.skyblock#getProfiles.js index 15df490a..528565d8 100644 --- a/tests/Client#getSkyblockProfiles.js +++ b/tests/skyblock/Client.skyblock#getProfiles.js @@ -1,6 +1,6 @@ /* eslint-disable no-undef */ -const { SkyblockMember, Errors } = require('../src'); -const { client } = require('./Client.js'); +const { SkyblockMember, Errors } = require('../../src'); +const { client } = require('../Client.js'); const { expect } = require('chai'); const uuids = [ @@ -14,13 +14,13 @@ const uuids = [ const usernames = ['kathund', 'StavZDev', 'Plancke', 'SoupyRaccn', 'duckysoskilled', 'Altpapier']; -describe('Client#getSkyblockProfiles', async () => { +describe('Client.skyblock#getProfiles', async () => { describe('Valid', async () => { uuids.forEach((uuid) => { describe(`UUID Test ${uuids.indexOf(uuid) + 1}`, async () => { let profiles; it('expect not to throw', async () => { - profiles = await client.getSkyblockProfiles(uuid); + profiles = await client.skyblock.getProfiles(uuid); }); it('should be an array', () => { expect(profiles).to.be.an('array'); @@ -64,7 +64,7 @@ describe('Client#getSkyblockProfiles', async () => { describe(`Username Test ${usernames.indexOf(username) + 1}`, async () => { let profiles; it('expect not to throw', async () => { - profiles = await client.getSkyblockProfiles(username); + profiles = await client.skyblock.getProfiles(username); }); it('should be an array', () => { expect(profiles).to.be.an('array'); @@ -108,7 +108,7 @@ describe('Client#getSkyblockProfiles', async () => { describe('Never played skyblock', async () => { it('expect to throw', async () => { try { - member = await client.getSkyblockMember('b45add7b081443909fb00aa9a3e15eb0'); + member = await client.skyblock.getProfiles('b45add7b081443909fb00aa9a3e15eb0'); throw new Error('Expected an error to be thrown, but no error was thrown.'); } catch (error) { expect(error.message).to.equal(Errors.NO_SKYBLOCK_PROFILES); diff --git a/typings/index.d.ts b/typings/index.d.ts index 4d2ec0ae..6f9e7793 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -1,7 +1,7 @@ /* eslint-disable indent */ +/* eslint-disable max-len */ /* eslint-disable camelcase */ /* eslint-disable require-jsdoc */ -/* eslint-disable max-len */ // Minimum TypeScript Version: 3.6 import { NetworthResult } from 'skyhelper-networth'; @@ -137,6 +137,48 @@ export type SkyblockRarity = 'VERY_SPECIAL' | 'SPECIAL' | 'SUPREME' | 'MYTHIC' | export type SOCIAL_MEDIA_ID = 'YOUTUBE' | 'DISCORD' | 'HYPIXEL' | 'TWITTER' | 'INSTAGRAM' | 'TWITCH'; export type SKYWARS_KIT_TYPE = 'basic' | 'supporting' | 'mining' | 'defending' | 'attacking' | 'advanced' | 'enderchest'; export type SKYWARS_KIT_GAMEMODE = 'solo' | 'team'; +export type BINGO_TYPE = 'ONE_TIME' | 'ONE_TIER' | 'TIERED'; +export type ACHIEVEMENT_TYPE = 'ONE_TIME' | 'TIERED'; +export type QUEST_TYPE = 'DAILY' | 'WEEKLY'; +export type GAME_STATIC = + | 'arcade' + | 'arena' + | 'bedwars' + | 'hungergames' + | 'buildbattle' + | 'truecombat' + | 'duels' + | 'mcgo' + | 'murdermystery' + | 'paintball' + | 'quake' + | 'skyclash' + | 'skywars' + | 'supersmash' + | 'speeduhc' + | 'gingerbread' + | 'tntgames' + | 'uhc' + | 'vampirez' + | 'walls3' + | 'walls' + | 'battleground' + | 'woolgames'; +export interface ChallengeData { + id: string; + name: string; + rewardType: string; + reward: number; +} +export interface Objective { + id: string; + type: 'Integer' | 'Boolean'; + amountNeeded: number; +} +export interface QuestReward { + type: string; + amount: number; +} export type SKYBLOCK_BESTIARY = number; export interface SKYBLOCK_BESTIARY_CATEGORY { [key: string]: { @@ -214,6 +256,9 @@ export interface auctionsOptions extends methodOptions { race?: boolean; includeItemBytes?: boolean; } +export interface playerBingoOptions extends methodOptions { + fetchBingoData?: boolean; +} declare module 'hypixel-api-reborn' { const version: string; const Errors: { @@ -822,7 +867,7 @@ declare module 'hypixel-api-reborn' { * @param searchParameter - 'name', 'player' or 'id' * @param query - guild name, player nickname or guild id */ - getGuild(searchParameter: 'name' | 'player' | 'id', query: string, options: methodOptions): Promise; + getGuild(searchParameter: 'name' | 'player' | 'id', query: string, options?: methodOptions): Promise; /** * @description Allows you to get statistics of watchdog anticheat */ @@ -842,13 +887,19 @@ declare module 'hypixel-api-reborn' { */ getSkyblockMember(query: string, options?: skyblockMemberOptions): Promise>; /** - * @description Allows you to get all skyblock auctions - * @param page - "*", a page number, or an array with the start and the end page number ( automatically sorted ) - * @param options Options + * Allows you to get filtered skyblock auctions + * Using auction ID will return an array of at most 1 element + * @method + * @name Client#getSkyblockAuction + * @param type - Filter to use + * @param query - uuid of profile, player, or auction. IGN can be used as well + * @param includeItemBytes - include item bytes (optional) + * @param options - Options */ - getSkyblockAuctions(page?: '*' | number | [number, number], options?: auctionsOptions): Promise<{ info?: AuctionInfo; auctions?: Auction[] }>; - /** - * @description Allows you to get all ended auctions in around the last 60 seconds + getSkyblockAuction(type: 'PROFILE' | 'PLAYER' | 'AUCTION', query: string, includeItemBytes?: boolean, options?: methodOptions): Promise; /** + * @description Allows you to get all auctions of player + * @deprecated Use Client#getSkyblockAuction + * @param query - player nickname or uuid * @param includeItemBytes - include item bytes (optional) */ getEndedSkyblockAuctions(includeItemBytes?: boolean, options?: methodOptions): Promise<{ info: AuctionInfo; auctions: PartialAuction[] }>; @@ -862,6 +913,23 @@ declare module 'hypixel-api-reborn' { * @description Allows you to get list of products */ getSkyblockBazaar(options?: methodOptions): Promise; + /** + * @description Gets bingo data + */ + getSkyblockBingo(options?: methodOptions): Promise; + /** + * @description Gets bingo data of a player + * @param query - UUID/IGN of player + */ + getSkyblockBingoByPlayer(query: string, options?: playerBingoOptions): Promise; + /** + * @description Gets data of skyblock government + */ + getSkyblockGovernment(options?: methodOptions): Promise; + /** + * @description Gets data of skyblock government + */ + getSkyblockGovernment(options?: methodOptions): Promise; /** * @description Allows you to get skyblock news */ @@ -916,6 +984,22 @@ declare module 'hypixel-api-reborn' { * @description Parses the RSS feed from status.hypixel.net */ getAPIStatus(): Promise; + /** + * @description Allows you to get information about hypixel challenges [NO KEY REQUIRED] + */ + getChallenges(options?: methodOptions): Promise; + /** + * @description Allows you to get information about hypixel quests [NO KEY REQUIRED] + */ + getQuests(options?: methodOptions): Promise; + /** + * Allows you to get information about hypixel achievements [NO KEY REQUIRED] + */ + getAchievements(options?: methodOptions): Promise; + /** + * @description Allows you to get information about hypixel guild achievements [NO KEY REQUIRED] + */ + getGuildAchievements(options?: methodOptions): Promise; /** * @param amount - Amount of cache entries to delete * @description Allows you to clear cache @@ -1881,6 +1965,57 @@ declare module 'hypixel-api-reborn' { totalPrice: number; orders: number; } + class BingoData { + constructor(data: Record); + lastUpdatedTimestamp: number; + lastUpdatedAt: Date | null; + id: number | null; + goals: Bingo[] | null; + getGoal(column: number, row: number): Bingo | undefined; + } + class PlayerBingo { + constructor(data: Record, bingoData: BingoData | null); + dataPerEvent: PlayerBingoDataPerEvent[]; + } + type PlayerBingoDataPerEvent = { + eventId: number; + points: number; + enrichedGoals: boolean; + goalsCompleted: SpecialBingoArray | string[]; + }; + type SpecialBingoArray = Bingo[] & { + unachievedGoals: Bingo[]; + }; + class GovernmentData { + constructor(data: Record); + lastUpdatedTimestamp: number; + lastUpdatedAt: Date | null; + lastElectionResults: Map; + mayor: Candidate; + runningYear: number; + currentElectionResults: Map | null; + currentElectionFor: number | null; + } + class Candidate { + constructor(data: Record, isMayor?: boolean | undefined); + name: string; + keyBenefit: string; + perks: Record<'name' | 'description', string>[]; + isMayor: boolean; + votesReceived: number; + toString(): string; + } + class FireSale { + constructor(data: Record); + itemId: string; + startTimestamp: number; + startAt: Date; + endTimestamp: number; + endAt: Date; + amount: number; + price: number; + toString(): string; + } class WatchdogStats { constructor(data: Record); byWatchdogTotal: number; @@ -3678,4 +3813,281 @@ declare module 'hypixel-api-reborn' { favicon: Buffer; ping: number; } + + class Achievements { + lastUpdatedTimestamp: number; + lastUpdatedAt: Date; + achievementsPerGame: { + arcade: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + arena: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + bedwars: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + blitz: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + buildbattle: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + christmas2017: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + copsandcrims: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + duels: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + easter: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + general: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + gingerbread: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + halloween2017: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + housing: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + murdermystery: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + paintball: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + pit: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + quake: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + skyblock: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + skyclash: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + skywars: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + speeduhc: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + summer: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + supersmash: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + tntgames: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + truecombat: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + uhc: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + vampirez: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + walls: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + walls3: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + warlords: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + woolgames: { + category: string; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + }; + }; + } + class Achievement { + constructor(data: Record); + name: string; + codeName: string; + description: string; + type: ACHIEVEMENT_TYPE; + rarity: { + local?: number; + localPercentage?: number; + global?: number; + globalPercentage?: number; + }; + tierInformation?: AchievementTier; + points: number; + totalAmountRequired?: number; + toString(): string; + } + class AchievementTier { + constructor(data: Record); + maxTier: number; + getTier(tier: number): { + pointsRewarded?: number; + amountRequired?: number; + }; + } + class Challenges { + constructor(data: Record); + lastUpdatedTimestamp: number; + lastUpdatedAt: Date; + challengesPerGame: Record; + } + class GameAchievement { + constructor(data: Record); + category: GAME_STATIC; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + } + class GameChallenges { + constructor(data: Record); + category: GAME_STATIC; + challenges: Map; + } + class GameQuests { + constructor(data: Record); + game: GAME_STATIC; + quests: Quest[]; + } + class GuildAchievements { + constructor(data: Record); + lastUpdatedTimestamp: number; + lastUpdatedAt: Date; + achievements: Record; + } + class Quest { + constructor(data: Record); + questName: string; + questID: string; + description: string; + type: QUEST_TYPE; + objectives: Objective[]; + rewards: QuestReward[]; + toString(): string; + } + class Quests { + constructor(data: Record); + lastUpdatedTimestamp: number; + lastUpdatedAt: Date; + questsPerGame: Record; + } + class Bingo { + constructor(data: Record); + name: string; + id: string; + row?: number; + column?: number; + rawLore: string; + lore: string; + tiers: number[]; + type: BINGO_TYPE; + tierStep?: number; + requiredAmount?: number; + toString(): string; + } }