scripts/src/lib/tw.ts

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',
}),
});
}
}