feat: extended player profile
This commit is contained in:
parent
66136ee097
commit
0c9539dca9
|
@ -2,11 +2,11 @@ const preambles = {
|
|||
'extended-player-profile': `// ==UserScript==
|
||||
// @name Extended player profile
|
||||
// @version 1.0.0
|
||||
// @description Adds additional info to player profile.
|
||||
// @description Adds additional info and actions to a player's profile.
|
||||
// @author Dawid Wysokiński - Kichiyaki - contact@dwysokinski.me
|
||||
// @match https://*/game.php?*screen=info_player*
|
||||
// @downloadURL ${process.env.PUBLIC_URL}/extendedPlayerProfile.js
|
||||
// @updateURL ${process.env.PUBLIC_URL}/extendedPlayerProfile.js
|
||||
// @downloadURL ${process.env.PUBLIC_URL}/userscripts/extendedPlayerProfile.js
|
||||
// @updateURL ${process.env.PUBLIC_URL}/userscripts/extendedPlayerProfile.js
|
||||
// @icon https://www.google.com/s2/favicons?domain=plemiona.pl
|
||||
// @grant none
|
||||
// @run-at document-end
|
||||
|
|
|
@ -3,8 +3,12 @@
|
|||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build-single": "PUBLIC_URL=https://scripts.tribalwarshelp.com parcel build --dist-dir dist",
|
||||
"build:extended-player-profile": "PREAMBLE=extended-player-profile yarn build-single src/extendedPlayerProfile.ts",
|
||||
"build-userscript": "PUBLIC_URL=https://scripts.tribalwarshelp.com parcel build --dist-dir dist/userscripts",
|
||||
"build-quickbar": "PUBLIC_URL=https://scripts.tribalwarshelp.com parcel build --dist-dir dist/quickbar",
|
||||
"build:userscript:extended-player-profile": "PREAMBLE=extended-player-profile yarn build-userscript src/extendedPlayerProfile.ts",
|
||||
"build:quickbar:extended-player-profile": "yarn build-quickbar src/extendedPlayerProfile.ts",
|
||||
"build:userscript": "npm-run-all build:userscript:*",
|
||||
"build:quickbar": "npm-run-all build:quickbar:*",
|
||||
"build": "npm-run-all build:*"
|
||||
},
|
||||
"author": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ListResult } from '../lib/twhelp';
|
||||
import { createTranslationFunc } from '../utils';
|
||||
|
||||
const translations: Record<string, Record<string, string>> = {
|
||||
const t = createTranslationFunc({
|
||||
pl_PL: {
|
||||
Loading: 'Wczytywanie',
|
||||
'Previous page': 'Poprzednia strona',
|
||||
|
@ -8,17 +8,18 @@ const translations: Record<string, Record<string, string>> = {
|
|||
'Something went wrong while loading the data':
|
||||
'Coś poszło nie tak podczas wczytywania danych',
|
||||
},
|
||||
};
|
||||
|
||||
const t = (s: string) => {
|
||||
return translations[window.game_data.locale]?.[s] ?? s;
|
||||
};
|
||||
});
|
||||
|
||||
export type DialogTableColumn<T> = {
|
||||
header: string;
|
||||
accessor: (row: T, index: number, rows: T[]) => string;
|
||||
};
|
||||
|
||||
export type LoadDataResult<T> = {
|
||||
data: T[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
export class DialogTable<T> {
|
||||
prevPageId: string;
|
||||
selectId: string;
|
||||
|
@ -30,7 +31,7 @@ export class DialogTable<T> {
|
|||
private readonly loadData: (
|
||||
page: number,
|
||||
limit: number
|
||||
) => Promise<ListResult<T>>
|
||||
) => Promise<LoadDataResult<T>>
|
||||
) {
|
||||
this.prevPageId = `${this.id}_page_prev`;
|
||||
this.selectId = `${this.id}_page_select`;
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
// Extended player profile
|
||||
|
||||
import { Player, TWHelpClient } from './lib/twhelp';
|
||||
import { Player, PlayerSnapshot, TWHelpClient } from './lib/twhelp';
|
||||
import { DialogTable } from './common/DialogTable';
|
||||
import { InADayClient, InADayPlayerScore } from './lib/tw';
|
||||
import { InADayClient } from './lib/tw';
|
||||
import { createTranslationFunc } from './utils';
|
||||
|
||||
const SCREEN = 'info_player';
|
||||
const MODE = null;
|
||||
|
||||
const translations: Record<string, Record<string, string>> = {
|
||||
const t = createTranslationFunc({
|
||||
pl_PL: {
|
||||
'Joined at': 'Dołączył o',
|
||||
'Last activity at': 'Ostatnio aktywny o',
|
||||
|
@ -24,6 +22,7 @@ const translations: Record<string, Record<string, string>> = {
|
|||
Village: 'Wioska',
|
||||
'Old owner': 'Stary właściciel',
|
||||
'New owner': 'Nowy właściciel',
|
||||
Rank: 'Ranking',
|
||||
Points: 'Punkty',
|
||||
Barbarian: 'Barbarzyńska',
|
||||
Unknown: 'Nieznany',
|
||||
|
@ -34,10 +33,14 @@ const translations: Record<string, Record<string, string>> = {
|
|||
Date: 'Data',
|
||||
Tribe: 'Plemię',
|
||||
Villages: 'Wioski',
|
||||
OD: 'Pokonani ogólny',
|
||||
OD: 'Pokonani ogólnie',
|
||||
'OD - rank': 'Pokonani ogólnie - ranking',
|
||||
ODA: 'Pokonani agresor',
|
||||
'ODA - rank': 'Pokonani agresor - ranking',
|
||||
ODD: 'Pokonani obrońca',
|
||||
'ODD - rank': 'Pokonani obrońca - ranking',
|
||||
ODS: 'Pokonani wspierający',
|
||||
'ODS - rank': 'Pokonani wspierający - ranking',
|
||||
Type: 'Typ',
|
||||
Score: 'Wynik',
|
||||
'Units defeated while attacking': 'Jednostki pokonane w ataku',
|
||||
|
@ -47,12 +50,9 @@ const translations: Record<string, Record<string, string>> = {
|
|||
'Villages plundered': 'Splądrowane wioski',
|
||||
'Resources gathered': 'Zebrane surowce',
|
||||
'Villages conquered': 'Przejęte wioski',
|
||||
Changes: 'Zmiany',
|
||||
},
|
||||
};
|
||||
|
||||
const t = (s: string) => {
|
||||
return translations[window.game_data.locale]?.[s] ?? s;
|
||||
};
|
||||
});
|
||||
|
||||
class TWHelpConnector {
|
||||
constructor(
|
||||
|
@ -66,10 +66,15 @@ class TWHelpConnector {
|
|||
return this.client.player(this.version, this.server, this.id);
|
||||
}
|
||||
|
||||
async latestSnapshot() {
|
||||
const history = await this.playerHistory(1, 1);
|
||||
return history.data.length > 0 ? history.data[0] : null;
|
||||
}
|
||||
|
||||
playerTribeChanges(page: number, limit: number) {
|
||||
return this.client.playerTribeChanges(this.version, this.server, this.id, {
|
||||
offset: (page - 1) * limit,
|
||||
limit: limit,
|
||||
limit,
|
||||
sort: ['createdAt:desc', 'id:asc'],
|
||||
});
|
||||
}
|
||||
|
@ -77,7 +82,7 @@ class TWHelpConnector {
|
|||
playerEnnoblements(page: number, limit: number) {
|
||||
return this.client.playerEnnoblements(this.version, this.server, this.id, {
|
||||
offset: (page - 1) * limit,
|
||||
limit: limit,
|
||||
limit,
|
||||
sort: ['createdAt:desc'],
|
||||
});
|
||||
}
|
||||
|
@ -85,7 +90,7 @@ class TWHelpConnector {
|
|||
playerHistory(page: number, limit: number) {
|
||||
return this.client.playerHistory(this.version, this.server, this.id, {
|
||||
offset: (page - 1) * limit,
|
||||
limit: limit,
|
||||
limit,
|
||||
sort: ['date:desc'],
|
||||
});
|
||||
}
|
||||
|
@ -112,12 +117,14 @@ enum DialogId {
|
|||
class UI {
|
||||
constructor(
|
||||
private readonly player: Player,
|
||||
private readonly latestSnapshot: PlayerSnapshot | null,
|
||||
private readonly twhelpConnector: TWHelpConnector,
|
||||
private readonly inADayConnector: InADayConnector
|
||||
) {}
|
||||
|
||||
public render() {
|
||||
this.renderAdditionalInfo();
|
||||
this.renderStats();
|
||||
this.renderActions();
|
||||
}
|
||||
|
||||
|
@ -160,6 +167,106 @@ class UI {
|
|||
);
|
||||
}
|
||||
|
||||
private renderStats() {
|
||||
const td = document.querySelector(
|
||||
'#content_value td[valign="top"]:nth-child(2)'
|
||||
);
|
||||
if (!(td instanceof HTMLTableCellElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = [
|
||||
{
|
||||
header: t('Points'),
|
||||
value: this.player.points - (this.latestSnapshot?.points ?? 0),
|
||||
},
|
||||
{
|
||||
header: t('Rank'),
|
||||
value: this.player.rank - (this.latestSnapshot?.rank ?? 0),
|
||||
rank: true,
|
||||
},
|
||||
{
|
||||
header: t('Villages'),
|
||||
value:
|
||||
this.player.numVillages - (this.latestSnapshot?.numVillages ?? 0),
|
||||
},
|
||||
{
|
||||
header: t('ODA'),
|
||||
value: this.player.scoreAtt - (this.latestSnapshot?.scoreAtt ?? 0),
|
||||
},
|
||||
{
|
||||
header: t('ODA - rank'),
|
||||
value: this.player.rankAtt - (this.latestSnapshot?.rankAtt ?? 0),
|
||||
rank: true,
|
||||
},
|
||||
{
|
||||
header: t('ODD'),
|
||||
value: this.player.scoreDef - (this.latestSnapshot?.scoreDef ?? 0),
|
||||
},
|
||||
{
|
||||
header: t('ODD - rank'),
|
||||
value: this.player.rankDef - (this.latestSnapshot?.rankDef ?? 0),
|
||||
rank: true,
|
||||
},
|
||||
{
|
||||
header: t('ODS'),
|
||||
value: this.player.scoreSup - (this.latestSnapshot?.scoreSup ?? 0),
|
||||
},
|
||||
{
|
||||
header: t('ODS - rank'),
|
||||
value: this.player.rankSup - (this.latestSnapshot?.rankSup ?? 0),
|
||||
rank: true,
|
||||
},
|
||||
{
|
||||
header: t('OD'),
|
||||
value: this.player.scoreTotal - (this.latestSnapshot?.scoreTotal ?? 0),
|
||||
},
|
||||
{
|
||||
header: t('OD - rank'),
|
||||
value: this.player.rankTotal - (this.latestSnapshot?.rankTotal ?? 0),
|
||||
rank: true,
|
||||
},
|
||||
];
|
||||
|
||||
td.insertAdjacentHTML(
|
||||
'afterbegin',
|
||||
`
|
||||
<table style="width: 100%" class="vis">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
${t('Changes')}
|
||||
</th>
|
||||
</tr>
|
||||
${rows
|
||||
.map(
|
||||
(r) => `
|
||||
<tr>
|
||||
<td>${r.header}</td>
|
||||
<td style="color: #000; background-color: ${this.getStatBgColor(
|
||||
r.value,
|
||||
r.rank
|
||||
)}">${Math.abs(r.value).toLocaleString()}</td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
private getStatBgColor(val: number, rank?: boolean): string {
|
||||
if ((val > 0 && !rank) || (val < 0 && rank)) {
|
||||
return '#0f0';
|
||||
}
|
||||
if ((val < 0 && !rank) || (val > 0 && rank)) {
|
||||
return '#f00';
|
||||
}
|
||||
return '#808080';
|
||||
}
|
||||
|
||||
private renderActions() {
|
||||
const tbody = document
|
||||
.querySelector('#content_value a[href*="twstats"]')
|
||||
|
@ -396,6 +503,7 @@ class UI {
|
|||
class ExtendedPlayerProfile {
|
||||
private readonly twhelpConnector: TWHelpConnector;
|
||||
private readonly inADayConnector: InADayConnector;
|
||||
|
||||
constructor(twhelpClient: TWHelpClient, inADayClient: InADayClient) {
|
||||
this.twhelpConnector = new TWHelpConnector(
|
||||
twhelpClient,
|
||||
|
@ -412,7 +520,14 @@ class ExtendedPlayerProfile {
|
|||
|
||||
async render() {
|
||||
const player = await this.twhelpConnector.player();
|
||||
new UI(player, this.twhelpConnector, this.inADayConnector).render();
|
||||
const latestSnapshot = await this.twhelpConnector.latestSnapshot();
|
||||
|
||||
new UI(
|
||||
player,
|
||||
latestSnapshot,
|
||||
this.twhelpConnector,
|
||||
this.inADayConnector
|
||||
).render();
|
||||
}
|
||||
|
||||
private getPlayerId() {
|
||||
|
@ -433,7 +548,10 @@ class ExtendedPlayerProfile {
|
|||
}
|
||||
|
||||
(async () => {
|
||||
if (window.game_data.screen !== SCREEN || window.game_data.mode !== MODE) {
|
||||
if (
|
||||
window.game_data.screen !== 'info_player' ||
|
||||
window.game_data.mode !== null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -95,10 +95,6 @@ class InADayParser {
|
|||
}
|
||||
}
|
||||
|
||||
export type InADayClientOptions = {
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
type InADayParams = {
|
||||
name?: string;
|
||||
page?: number;
|
||||
|
@ -131,9 +127,9 @@ export type InADayPlayer = {
|
|||
|
||||
export class InADayClient {
|
||||
client: AxiosInstance;
|
||||
constructor(opts?: InADayClientOptions) {
|
||||
constructor(timeout?: number) {
|
||||
this.client = axios.create({
|
||||
timeout: opts?.timeout ?? DEFAULT_TIMEOUT,
|
||||
timeout: timeout ?? DEFAULT_TIMEOUT,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -190,7 +186,9 @@ export class InADayClient {
|
|||
});
|
||||
}
|
||||
|
||||
await wait(random(200, 400));
|
||||
if (key !== urlsAndKeys[urlsAndKeys.length - 1].key) {
|
||||
await wait(random(200, 400));
|
||||
}
|
||||
}
|
||||
|
||||
if (scores.length === 0) {
|
||||
|
|
|
@ -12,6 +12,17 @@ export type Version = {
|
|||
|
||||
export type Player = {
|
||||
id: number;
|
||||
points: number;
|
||||
rank: number;
|
||||
numVillages: number;
|
||||
scoreAtt: number;
|
||||
rankAtt: number;
|
||||
scoreDef: number;
|
||||
rankDef: number;
|
||||
scoreSup: number;
|
||||
rankSup: number;
|
||||
scoreTotal: number;
|
||||
rankTotal: number;
|
||||
bestRank: number;
|
||||
bestRankAt: string;
|
||||
mostPoints: number;
|
||||
|
@ -101,17 +112,13 @@ export type ListPlayerSnapshotsParams = {
|
|||
sort?: string[];
|
||||
};
|
||||
|
||||
export type TWHelpClientOptions = {
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
export class TWHelpClient {
|
||||
client: AxiosInstance;
|
||||
|
||||
constructor(url: string, opts?: TWHelpClientOptions) {
|
||||
constructor(url: string, timeout?: number) {
|
||||
this.client = axios.create({
|
||||
baseURL: url,
|
||||
timeout: opts?.timeout ?? DEFAULT_TIMEOUT,
|
||||
timeout: timeout ?? DEFAULT_TIMEOUT,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export const createTranslationFunc = (
|
||||
translations: Record<string, Record<string, string>>
|
||||
) => {
|
||||
return (s: string) => {
|
||||
return translations[window.game_data.locale]?.[s] ?? s;
|
||||
};
|
||||
};
|
|
@ -1 +1,2 @@
|
|||
export * from './i18n';
|
||||
export * from './wait';
|
||||
|
|
Loading…
Reference in New Issue