255 lines
5.4 KiB
TypeScript
255 lines
5.4 KiB
TypeScript
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<InADayPlayer | null> {
|
|
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',
|
|
}),
|
|
});
|
|
}
|
|
}
|