import axios, { AxiosInstance } from 'axios'; import random from 'lodash/random'; import { wait } from '../utils'; const DEFAULT_TIMEOUT = 10000; type InADayScore = { player: { id: number; name: string; profileUrl: string; }; tribe: { id: number; tag: string; profileUrl: string; } | null; rank: number; score: number; date: string; }; class InADayParser { private readonly doc: Document; constructor(html: string) { this.doc = new DOMParser().parseFromString(html, 'text/html'); } parse() { const results: InADayScore[] = []; for (const row of this.doc.querySelectorAll( '#in_a_day_ranking_table tbody tr' )) { if (!(row instanceof HTMLTableRowElement)) { continue; } const res = this.parseRow(row); if (!res) { continue; } results.push(res); } return results; } private parseRow(row: HTMLTableRowElement): InADayScore | null { const cells = [...row.children].filter( (cell): cell is HTMLTableCellElement => cell instanceof HTMLTableCellElement ); if (cells.length !== 5) { return null; } const playerProfileUrl = cells[1].querySelector('a')?.href; if (!playerProfileUrl) { return null; } const tribeProfileUrl = cells[2].querySelector('a')?.href; return { player: { id: this.getIdFromUrl(playerProfileUrl), name: cells[1].innerText.trim(), profileUrl: playerProfileUrl, }, ...(tribeProfileUrl ? { tribe: { id: this.getIdFromUrl(tribeProfileUrl), tag: cells[2].innerText.trim(), profileUrl: tribeProfileUrl, }, } : { tribe: null, }), rank: parseInt(cells[0].innerText.trim()), score: this.parseScore(cells[3].innerText.trim()), date: cells[4].innerText.trim(), }; } private getIdFromUrl(u: string) { return parseInt(new URL(u).searchParams.get('id') ?? ''); } private parseScore(s: string): number { return parseInt(s.trim().replace(/\./g, '')); } } type InADayParams = { name?: string; page?: number; }; export type InADayPlayerScore = { score: number; date: string; }; export type InADayPlayer = { id: number; name: string; profileUrl: string; tribe: { id: number; tag: string; profileUrl: string; } | null; scores: { killAtt: InADayPlayerScore | null; killDef: InADayPlayerScore | null; killSup: InADayPlayerScore | null; lootRes: InADayPlayerScore | null; lootVil: InADayPlayerScore | null; scavenge: InADayPlayerScore | null; conquer: InADayPlayerScore | null; }; }; export class InADayClient { client: AxiosInstance; constructor(timeout?: number) { this.client = axios.create({ timeout: timeout ?? DEFAULT_TIMEOUT, }); } async player(name: string): Promise { const params: InADayParams = { name, }; const urlsAndKeys: { url: string; key: keyof InADayPlayer['scores']; }[] = [ { url: this.buildUrl('kill_att', params), key: 'killAtt', }, { url: this.buildUrl('kill_def', params), key: 'killDef', }, { url: this.buildUrl('kill_sup', params), key: 'killSup', }, { url: this.buildUrl('loot_res', params), key: 'lootRes', }, { url: this.buildUrl('loot_vil', params), key: 'lootVil', }, { url: this.buildUrl('scavenge', params), key: 'scavenge', }, { url: this.buildUrl('conquer', params), key: 'conquer', }, ]; const scores: { key: keyof InADayPlayer['scores']; score: InADayScore; }[] = []; for (const { url, key } of urlsAndKeys) { const resp = await this.client.get(url); const score = new InADayParser(resp.data).parse()[0]; if (score && score.player.name === name) { scores.push({ key, score, }); } if (key !== urlsAndKeys[urlsAndKeys.length - 1].key) { await wait(random(200, 400)); } } if (scores.length === 0) { return null; } const player: InADayPlayer = { id: 0, name: '', profileUrl: '', tribe: null, scores: { conquer: null, killAtt: null, killDef: null, killSup: null, lootRes: null, lootVil: null, scavenge: null, }, }; scores.forEach(({ score, key }) => { if (!player.id) { player.id = score.player.id; } if (!player.name) { player.name = score.player.name; } if (!player.profileUrl) { player.profileUrl = score.player.profileUrl; } if (!player.tribe && score.tribe) { player.tribe = score.tribe; } player.scores[key] = { score: score.score, date: score.date, }; }); return player; } private buildUrl(type: string, params?: InADayParams) { return window.TribalWars.buildURL('GET', 'ranking', { mode: 'in_a_day', type, ...(params?.name ? { name: params.name, } : {}), ...(params?.page ? { offset: ((params.page - 1) * 25).toString(), } : { offset: '0', }), }); } }