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