feat: extended player profile
This commit is contained in:
parent
0026822768
commit
66136ee097
|
@ -17,6 +17,7 @@
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jquery": "^3.5.14",
|
"@types/jquery": "^3.5.14",
|
||||||
|
"@types/lodash": "^4.14.191",
|
||||||
"@types/node": "^18.11.17",
|
"@types/node": "^18.11.17",
|
||||||
"buffer": "^5.5.0",
|
"buffer": "^5.5.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.2.2",
|
"axios": "^1.2.2",
|
||||||
"date-fns": "^2.29.3"
|
"date-fns": "^2.29.3",
|
||||||
|
"lodash": "^4.17.21"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { Player, TWHelpClient } from './lib/twhelp';
|
import { Player, TWHelpClient } from './lib/twhelp';
|
||||||
import { DialogTable } from './common/DialogTable';
|
import { DialogTable } from './common/DialogTable';
|
||||||
|
import { InADayClient, InADayPlayerScore } from './lib/tw';
|
||||||
|
|
||||||
const SCREEN = 'info_player';
|
const SCREEN = 'info_player';
|
||||||
const MODE = null;
|
const MODE = null;
|
||||||
|
@ -13,6 +14,7 @@ const translations: Record<string, Record<string, string>> = {
|
||||||
'Best rank': 'Najlepszy ranking',
|
'Best rank': 'Najlepszy ranking',
|
||||||
'Most points': 'Najwięcej punktów',
|
'Most points': 'Najwięcej punktów',
|
||||||
'Most villages': 'Najwięcej wiosek',
|
'Most villages': 'Najwięcej wiosek',
|
||||||
|
'Show in a day ranks': 'Pokaż dzienne rankingi',
|
||||||
'Show tribe changes': 'Pokaż zmiany plemion',
|
'Show tribe changes': 'Pokaż zmiany plemion',
|
||||||
'Show history': 'Pokaż historię',
|
'Show history': 'Pokaż historię',
|
||||||
'Show ennoblements': 'Pokaż przejęcia',
|
'Show ennoblements': 'Pokaż przejęcia',
|
||||||
|
@ -36,6 +38,15 @@ const translations: Record<string, Record<string, string>> = {
|
||||||
ODA: 'Pokonani agresor',
|
ODA: 'Pokonani agresor',
|
||||||
ODD: 'Pokonani obrońca',
|
ODD: 'Pokonani obrońca',
|
||||||
ODS: 'Pokonani wspierający',
|
ODS: 'Pokonani wspierający',
|
||||||
|
Type: 'Typ',
|
||||||
|
Score: 'Wynik',
|
||||||
|
'Units defeated while attacking': 'Jednostki pokonane w ataku',
|
||||||
|
'Units defeated while defending': 'Jednostki pokonane w obronie',
|
||||||
|
'Units defeated while supporting': 'Jednostki pokonane jako wspierający',
|
||||||
|
'Resources plundered': 'Zrabowane surowce',
|
||||||
|
'Villages plundered': 'Splądrowane wioski',
|
||||||
|
'Resources gathered': 'Zebrane surowce',
|
||||||
|
'Villages conquered': 'Przejęte wioski',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,7 +91,19 @@ class TWHelpConnector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class InADayConnector {
|
||||||
|
constructor(
|
||||||
|
private readonly client: InADayClient,
|
||||||
|
private readonly name: string
|
||||||
|
) {}
|
||||||
|
|
||||||
|
player() {
|
||||||
|
return this.client.player(this.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum DialogId {
|
enum DialogId {
|
||||||
|
IN_A_DAY_RANKS = 'kichiyaki_in_a_day_ranks',
|
||||||
TRIBE_CHANGES = 'kichiyaki_tribe_changes',
|
TRIBE_CHANGES = 'kichiyaki_tribe_changes',
|
||||||
ENNOBLEMENTS = 'kichiyaki_ennoblements',
|
ENNOBLEMENTS = 'kichiyaki_ennoblements',
|
||||||
HISTORY = 'kichiyaki_history',
|
HISTORY = 'kichiyaki_history',
|
||||||
|
@ -89,7 +112,8 @@ enum DialogId {
|
||||||
class UI {
|
class UI {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly player: Player,
|
private readonly player: Player,
|
||||||
private readonly connector: TWHelpConnector
|
private readonly twhelpConnector: TWHelpConnector,
|
||||||
|
private readonly inADayConnector: InADayConnector
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
|
@ -145,6 +169,10 @@ class UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
name: t('Show in a day ranks'),
|
||||||
|
handler: this.showInADayRanks.bind(this),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: t('Show tribe changes'),
|
name: t('Show tribe changes'),
|
||||||
handler: this.showTribeChanges.bind(this),
|
handler: this.showTribeChanges.bind(this),
|
||||||
|
@ -169,6 +197,60 @@ class UI {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async showInADayRanks(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const translationKeyByType: Record<string, string> = {
|
||||||
|
killAtt: 'Units defeated while attacking',
|
||||||
|
killDef: 'Units defeated while defending',
|
||||||
|
killSup: 'Units defeated while supporting',
|
||||||
|
lootRes: 'Resources plundered',
|
||||||
|
lootVil: 'Villages plundered',
|
||||||
|
scavenge: 'Resources gathered',
|
||||||
|
conquer: 'Villages conquered',
|
||||||
|
};
|
||||||
|
|
||||||
|
await new DialogTable(
|
||||||
|
DialogId.IN_A_DAY_RANKS,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
header: t('Type'),
|
||||||
|
accessor: (s) =>
|
||||||
|
s.type in translationKeyByType
|
||||||
|
? t(translationKeyByType[s.type])
|
||||||
|
: s.type,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: t('Score'),
|
||||||
|
accessor: (s) => (s.score ? s.score.toLocaleString() : '0'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: t('Date'),
|
||||||
|
accessor: (s) => (s.date ? s.date : '-'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
30,
|
||||||
|
async () => {
|
||||||
|
const player = await this.inADayConnector.player();
|
||||||
|
if (!player) {
|
||||||
|
return {
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
data: Object.entries(player?.scores)
|
||||||
|
.map(([type, sc]) => ({
|
||||||
|
type,
|
||||||
|
...(sc ? sc : {}),
|
||||||
|
}))
|
||||||
|
.sort((a, b) => (a.type > b.type ? 1 : -1)),
|
||||||
|
total: Object.entries(player?.scores).length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
).render();
|
||||||
|
}
|
||||||
|
|
||||||
private async showTribeChanges(e: Event) {
|
private async showTribeChanges(e: Event) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
@ -196,7 +278,7 @@ class UI {
|
||||||
],
|
],
|
||||||
30,
|
30,
|
||||||
(page: number, limit: number) => {
|
(page: number, limit: number) => {
|
||||||
return this.connector.playerTribeChanges(page, limit);
|
return this.twhelpConnector.playerTribeChanges(page, limit);
|
||||||
}
|
}
|
||||||
).render();
|
).render();
|
||||||
}
|
}
|
||||||
|
@ -252,7 +334,7 @@ class UI {
|
||||||
],
|
],
|
||||||
30,
|
30,
|
||||||
(page: number, limit: number) => {
|
(page: number, limit: number) => {
|
||||||
return this.connector.playerHistory(page, limit);
|
return this.twhelpConnector.playerHistory(page, limit);
|
||||||
}
|
}
|
||||||
).render();
|
).render();
|
||||||
}
|
}
|
||||||
|
@ -305,26 +387,32 @@ class UI {
|
||||||
],
|
],
|
||||||
30,
|
30,
|
||||||
(page: number, limit: number) => {
|
(page: number, limit: number) => {
|
||||||
return this.connector.playerEnnoblements(page, limit);
|
return this.twhelpConnector.playerEnnoblements(page, limit);
|
||||||
}
|
}
|
||||||
).render();
|
).render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExtendedPlayerProfile {
|
class ExtendedPlayerProfile {
|
||||||
connector: TWHelpConnector;
|
private readonly twhelpConnector: TWHelpConnector;
|
||||||
constructor(client: TWHelpClient) {
|
private readonly inADayConnector: InADayConnector;
|
||||||
this.connector = new TWHelpConnector(
|
constructor(twhelpClient: TWHelpClient, inADayClient: InADayClient) {
|
||||||
client,
|
this.twhelpConnector = new TWHelpConnector(
|
||||||
|
twhelpClient,
|
||||||
window.game_data.market,
|
window.game_data.market,
|
||||||
window.game_data.world,
|
window.game_data.world,
|
||||||
this.getPlayerId()
|
this.getPlayerId()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.inADayConnector = new InADayConnector(
|
||||||
|
inADayClient,
|
||||||
|
this.getPlayerName()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async render() {
|
async render() {
|
||||||
const player = await this.connector.player();
|
const player = await this.twhelpConnector.player();
|
||||||
new UI(player, this.connector).render();
|
new UI(player, this.twhelpConnector, this.inADayConnector).render();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPlayerId() {
|
private getPlayerId() {
|
||||||
|
@ -334,6 +422,14 @@ class ExtendedPlayerProfile {
|
||||||
}
|
}
|
||||||
return parseInt(str);
|
return parseInt(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getPlayerName() {
|
||||||
|
const header = document.querySelector('#map_toggler')?.closest('th');
|
||||||
|
if (!(header instanceof HTMLTableCellElement)) {
|
||||||
|
return window.game_data.player.name;
|
||||||
|
}
|
||||||
|
return header.innerText.trim();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
|
@ -342,7 +438,8 @@ class ExtendedPlayerProfile {
|
||||||
}
|
}
|
||||||
|
|
||||||
await new ExtendedPlayerProfile(
|
await new ExtendedPlayerProfile(
|
||||||
new TWHelpClient(process.env.TWHELP_API_BASE_URL ?? '')
|
new TWHelpClient(process.env.TWHELP_API_BASE_URL ?? ''),
|
||||||
|
new InADayClient()
|
||||||
)
|
)
|
||||||
.render()
|
.render()
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
1
src/global.d.ts
vendored
1
src/global.d.ts
vendored
|
@ -6,6 +6,7 @@ declare global {
|
||||||
group_id: number;
|
group_id: number;
|
||||||
player: {
|
player: {
|
||||||
id: number;
|
id: number;
|
||||||
|
name: string;
|
||||||
};
|
};
|
||||||
village: {
|
village: {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
256
src/lib/tw.ts
Normal file
256
src/lib/tw.ts
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
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, ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InADayClientOptions = {
|
||||||
|
timeout?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
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(opts?: InADayClientOptions) {
|
||||||
|
this.client = axios.create({
|
||||||
|
timeout: opts?.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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
export type BuildURLParams = {
|
|
||||||
entity: 'player';
|
|
||||||
id: number;
|
|
||||||
server: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const buildURL = (params: BuildURLParams) => {
|
|
||||||
switch (params.entity) {
|
|
||||||
case 'player':
|
|
||||||
return `https://www.twstats.com/in/${params.server}/player/${params.id}`;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown entity: ${params.entity}`);
|
|
||||||
}
|
|
||||||
};
|
|
1
src/utils/index.ts
Normal file
1
src/utils/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './wait';
|
2
src/utils/wait.ts
Normal file
2
src/utils/wait.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export const wait = (timeout: number) =>
|
||||||
|
new Promise((resolve) => setTimeout(resolve, timeout));
|
|
@ -6,6 +6,7 @@
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"strictFunctionTypes": true,
|
"strictFunctionTypes": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"noImplicitAny": true
|
"noImplicitAny": true,
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -722,6 +722,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/sizzle" "*"
|
"@types/sizzle" "*"
|
||||||
|
|
||||||
|
"@types/lodash@^4.14.191":
|
||||||
|
version "4.14.191"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa"
|
||||||
|
integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==
|
||||||
|
|
||||||
"@types/node@^18.11.17":
|
"@types/node@^18.11.17":
|
||||||
version "18.11.17"
|
version "18.11.17"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.17.tgz#5c009e1d9c38f4a2a9d45c0b0c493fe6cdb4bcb5"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.17.tgz#5c009e1d9c38f4a2a9d45c0b0c493fe6cdb4bcb5"
|
||||||
|
@ -1475,6 +1480,11 @@ load-json-file@^4.0.0:
|
||||||
pify "^3.0.0"
|
pify "^3.0.0"
|
||||||
strip-bom "^3.0.0"
|
strip-bom "^3.0.0"
|
||||||
|
|
||||||
|
lodash@^4.17.21:
|
||||||
|
version "4.17.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||||
|
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||||
|
|
||||||
mdn-data@2.0.14:
|
mdn-data@2.0.14:
|
||||||
version "2.0.14"
|
version "2.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
|
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user