Skip to content

Commit

Permalink
feat(bingo)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kathund committed Feb 21, 2024
1 parent 83ebcb2 commit 6320ca7
Show file tree
Hide file tree
Showing 11 changed files with 298 additions and 13 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,5 @@ testing/
.circleci/
changelog.md
master.json
.env
.env
.vscode/settings.json
8 changes: 8 additions & 0 deletions src/API/skyblock/getSkyblockBingo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = async function () {
const BingoData = require('../../structures/Skyblock/Static/BingoData.js');

const res = await this._makeRequest('/resources/skyblock/election');
if (res.raw) return res;

return new BingoData(res);
};
14 changes: 14 additions & 0 deletions src/API/skyblock/getSkyblockBingoByPlayer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const getSkyblockBingo = require('./getSkyblockBingo');
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 getSkyblockBingo.call(this);

return new PlayerBingo(data, bingoData);
};
22 changes: 22 additions & 0 deletions src/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,20 @@ class Client extends EventEmitter {
* console.log(products[0].productId); // INK_SACK:3
* })
* .catch(console.log);
*/ /**
* Allows you to get bingo data
* @method
* @name Client#getSkyblockBingo
* @param {MethodOptions} [options={}] Options
* @return {Promise<BingoData>}
*/
/**
* Allows you to get bingo data of a player
* @method
* @name Client#getSkyblockBingoByPlayer
* @param {string} query UUID / IGN of player
* @param {PlayerBingoOptions} [options={}] Options
* @return {Promise<PlayerBingo>}
*/
/**
* Allows you to get SB government
Expand Down Expand Up @@ -411,4 +425,12 @@ const defaultCache = require('./Private/defaultCache.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;
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ module.exports = {
SkyblockPet: require('./structures/SkyBlock/SkyblockPet'),
SkyblockGovernment: require('./structures/Skyblock/Static/Government.js'),
SkyblockCandidate: require('./structures/Skyblock/Static/Candidate.js'),
SkyblockBingoData: require('./structures/Skyblock/Static/BingoData.js'),
SkyblockBingo: require('./structures/Skyblock/Static/Bingo.js'),

/* Skyblock Auctions */
BaseAuction: require('./structures/SkyBlock/Auctions/BaseAuction.js'),
Expand Down
65 changes: 65 additions & 0 deletions src/structures/SkyBlock/PlayerBingo.js
Original file line number Diff line number Diff line change
@@ -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;
106 changes: 106 additions & 0 deletions src/structures/SkyBlock/Static/Bingo.js
Original file line number Diff line number Diff line change
@@ -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;
45 changes: 45 additions & 0 deletions src/structures/SkyBlock/Static/BingoData.js
Original file line number Diff line number Diff line change
@@ -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;
7 changes: 7 additions & 0 deletions src/structures/SkyBlock/Static/Government.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ class GovernmentData {
*/
this.currentElectionFor = parseInt(data.current.year, 10) || null;
}
/**
* As string
* @return {string}
*/
toString() {
return this.name;
}
}

module.exports = GovernmentData;
17 changes: 17 additions & 0 deletions tests/Client#getSkyblockBingo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable no-undef */
const { SkyblockBingoData } = require('../src');
const { client } = require('./Client.js');
const { expect } = require('chai');

describe('Client#getSkyblockBingo', async () => {
let bingo;
it('expect not to throw', async () => {
bingo = await client.getSkyblockBingo();
});
it('should be an objecct', () => {
expect(bingo).to.be.an('object');
});
it('required keys should exist', () => {
expect(bingo).instanceOf(SkyblockBingoData);
});
});
22 changes: 10 additions & 12 deletions tests/Client#getSkyblockGoverment.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
/* eslint-disable no-undef */
const { SkyblockGovernment } = require('../src/');
const { SkyblockGovernment } = require('../src');
const { client } = require('./Client.js');
const { expect } = require('chai');

describe('Client#getSkyblockMember', async () => {
describe('Client#getSkyblockGoverment', async () => {
let goverment;
describe('Random (1)', async () => {
it('expect not to throw', async () => {
goverment = await client.getSkyblockGoverment();
});
it('should be an objecct', () => {
expect(goverment).to.be.an('object');
});
it('required keys should exist', () => {
expect(goverment).instanceOf(SkyblockGovernment);
});
it('expect not to throw', async () => {
goverment = await client.getSkyblockGoverment();
});
it('should be an objecct', () => {
expect(goverment).to.be.an('object');
});
it('required keys should exist', () => {
expect(goverment).instanceOf(SkyblockGovernment);
});
});

0 comments on commit 6320ca7

Please sign in to comment.