diff --git a/__tests__/hiscores.test.ts b/__tests__/hiscores.test.ts index 9ecbaa8..5a2ddaa 100644 --- a/__tests__/hiscores.test.ts +++ b/__tests__/hiscores.test.ts @@ -1,4 +1,5 @@ -import { parseStats, getRSNFormat } from '../src/index'; +import { parseStats, getRSNFormat, getSkillPage } from '../src/index'; +import { PlayerSkillRow } from '../src/types'; test('Parse CSV to json', () => { const csv = `40258,2063,218035714 @@ -88,3 +89,122 @@ test('Get rsn format', async done => { getRSNFormat('lYnX tiTaN').then(callback); }); + +test('Get attack top page', async done => { + const callback = (data: PlayerSkillRow[]) => { + expect(data).toStrictEqual([ + { rsn: 'Heur', rank: 1, level: 99, xp: 200000000, dead: false }, + { + rsn: 'Unohdettu2', + rank: 2, + level: 99, + xp: 200000000, + dead: false, + }, + { rsn: 'Drakon', rank: 3, level: 99, xp: 200000000, dead: false }, + { + rsn: 'Ame Umehara', + rank: 4, + level: 99, + xp: 200000000, + dead: false, + }, + { rsn: 'Jakee', rank: 5, level: 99, xp: 200000000, dead: false }, + { rsn: 'Hitsuji', rank: 6, level: 99, xp: 200000000, dead: false }, + { rsn: 'Howson', rank: 7, level: 99, xp: 200000000, dead: false }, + { rsn: 'Dr PFAFF', rank: 8, level: 99, xp: 200000000, dead: false }, + { + rsn: 'Malt Lickeys', + rank: 9, + level: 99, + xp: 200000000, + dead: false, + }, + { rsn: 'Burned', rank: 10, level: 99, xp: 200000000, dead: false }, + { + rsn: 'Blue Limes', + rank: 11, + level: 99, + xp: 200000000, + dead: false, + }, + { + rsn: 'Mini Finbarr', + rank: 12, + level: 99, + xp: 200000000, + dead: false, + }, + { + rsn: 'Unohdettu3', + rank: 13, + level: 99, + xp: 200000000, + dead: false, + }, + { + rsn: 'Eslihero', + rank: 14, + level: 99, + xp: 200000000, + dead: false, + }, + { + rsn: 'Lynx Titan', + rank: 15, + level: 99, + xp: 200000000, + dead: false, + }, + { + rsn: 'AndrewWigins', + rank: 16, + level: 99, + xp: 200000000, + dead: false, + }, + { rsn: 'iMelee', rank: 17, level: 99, xp: 200000000, dead: false }, + { + rsn: 'Portuguese', + rank: 18, + level: 99, + xp: 200000000, + dead: false, + }, + { + rsn: 'MarkoOSRS', + rank: 19, + level: 99, + xp: 200000000, + dead: false, + }, + { rsn: 'Cairo', rank: 20, level: 99, xp: 200000000, dead: false }, + { + rsn: 'Hey Jase', + rank: 21, + level: 99, + xp: 200000000, + dead: false, + }, + { + rsn: 'Sleighur', + rank: 22, + level: 99, + xp: 200000000, + dead: false, + }, + { + rsn: 'KMSat200mALL', + rank: 23, + level: 99, + xp: 200000000, + dead: false, + }, + { rsn: 'Yumemi', rank: 24, level: 99, xp: 200000000, dead: false }, + { rsn: 'Fiiggy', rank: 25, level: 99, xp: 200000000, dead: false }, + ]); + done(); + }; + + getSkillPage('main', 'attack', 1).then(callback); +}); diff --git a/src/hiscores.ts b/src/hiscores.ts index 8eebfdc..88c0e64 100644 --- a/src/hiscores.ts +++ b/src/hiscores.ts @@ -11,6 +11,8 @@ import { Clues, Gamemode, Category, + SkillName, + PlayerSkillRow, } from './types'; import { getStatsURL, @@ -19,9 +21,11 @@ import { CLUES, MODES, getPlayerTableURL, - getHiscoresPageURL, + getSkillPageURL, GAMEMODES, OTHER, + numberFromElement, + rsnFromElement, } from './utils'; export async function getStats( @@ -111,59 +115,42 @@ export async function getStats( } } -// export async function getHiscores( -// mode: Gamemode, -// category: Category, -// page: number -// ) { -// if (GAMEMODES.includes(mode) || mode.toLowerCase() === 'full') { -// throw Error('Invalid game mode'); -// } else if (!Number.isInteger(page) || page < 1) { -// throw Error('Page must be an integer greater than 0'); -// } else if ([...SKILLS, ...OTHER].includes(category)) { -// throw Error('Invalid category'); -// } -// const url = getHiscoresPageURL(mode, category, page); +export const getSkillPage = async ( + mode: Gamemode, + skill: SkillName, + page: number +): Promise => { + if (!GAMEMODES.includes(mode)) { + throw Error('Invalid game mode'); + } else if (!Number.isInteger(page) || page < 1) { + throw Error('Page must be an integer greater than 0'); + } else if (!SKILLS.includes(skill)) { + throw Error('Invalid skill'); + } + const url = getSkillPageURL(mode, skill, page); -// const players = []; -// const response = await axios(url); -// const $ = cheerio.load(response.data); -// const playersHTML = $('.personal-hiscores__row').toArray(); + const response = await axios(url); + const $ = cheerio.load(response.data); + const playersHTML = $('.personal-hiscores__row').toArray(); -// for (const player of playersHTML) { -// const attributes = player.children.filter(node => node.name === 'td'); + const players: PlayerSkillRow[] = playersHTML.map(row => { + const cells = row.children.filter(el => el.name === 'td'); + const [rankEl, nameCell, levelEl, xpEl] = cells; + const [nameEl] = nameCell.children.filter(el => el.name === 'a'); -// let playerInfo = { -// mode, -// category, -// rank: (attributes[0].children[0].data || '').slice(1, -1), -// rsn: (attributes[1].children[1].children[0].data || '').replace( -// /\uFFFD/g, -// ' ' -// ), -// }; + return { + rsn: rsnFromElement(nameEl), + rank: numberFromElement(rankEl), + level: numberFromElement(levelEl), + xp: numberFromElement(xpEl), + dead: nameCell.children.length === 4, + }; + }); -// SKILLS.includes(category) -// ? (playerInfo = Object.assign( -// { -// level: attributes[2].children[0].data.slice(1, -1), -// xp: attributes[3].children[0].data.slice(1, -1), -// }, -// playerInfo -// )) -// : (playerInfo.score = attributes[2].children[0].data.slice(1, -1)); + return players; +}; -// if (mode === 'hc') { -// playerInfo.dead = attributes[1].children.length > 1; -// } - -// players.push(playerInfo); -// } - -// return players; -// } - -export const getRSNFormat = async (rsn: string) => { +export const getRSNFormat = async (rsn: string): Promise => { const url = getPlayerTableURL('main', rsn); try { diff --git a/src/types.ts b/src/types.ts index 2c1b44d..26db078 100644 --- a/src/types.ts +++ b/src/types.ts @@ -85,3 +85,13 @@ export interface Player extends Modes { deulted: boolean; deironed: boolean; } + +export interface PlayerSkillRow extends Skill { + rsn: string; + dead: boolean; +} + +export interface PlayerActivityRow extends Activity { + rsn: string; + dead: boolean; +} diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 4b28d1b..498dbe9 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,4 +1,4 @@ -import { Gamemode, Category } from '../types'; +import { Gamemode, Category, SkillName } from '../types'; import { BASE_URL, GAMEMODE_URL, @@ -16,15 +16,22 @@ export const getPlayerTableURL = (gamemode: Gamemode, rsn: string) => GAMEMODE_URL[gamemode] }${SCORES_URL}table=0&user=${encodeURIComponent(rsn)}`; -export const getHiscoresPageURL = ( +export const getSkillPageURL = ( gamemode: Gamemode, - category: Category, + skill: SkillName, page: number -) => { - const table = [...SKILLS, ...OTHER]; - return `${BASE_URL}${GAMEMODE_URL[gamemode]}${SCORES_URL}${ - table.includes(category) - ? `table=${table.indexOf(category)}` - : `category_type=1&table=${OTHER.indexOf(category)}` - }&page=${page}`; +) => + `${BASE_URL}${GAMEMODE_URL[gamemode]}${SCORES_URL}table=${SKILLS.indexOf( + skill + )}&page=${page}`; + +export const numberFromElement = (el: CheerioElement) => { + const innerText = el.firstChild.data; + const number = innerText ? innerText.replace(/[\n|,]/g, '') : '-1'; + return parseInt(number, 10); +}; + +export const rsnFromElement = (el: CheerioElement) => { + const innerText = el.firstChild.data; + return innerText ? innerText.replace(/\uFFFD/g, ' ') : ''; };