diff --git a/.gitignore b/.gitignore index bf0f85c9c..c0550b4f9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,8 @@ package-lock.json /playwright-report/ /playwright/.cache/ dist/ +dist.* yarn-error.log -dist.zip commands.txt /test.* /docs/.vitepress/dist diff --git a/.vscode/settings.json b/.vscode/settings.json index 847ba49b0..dee1574f6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,10 +10,13 @@ "fullstats", "fuzzysort", "kranvagn", + "Leaderboard", + "leaderboards", "outfile", "ownedtanks", "playerachievements", "playerinfo", + "ratingsleaderboard", "Resvg", "roboto", "searchclans", @@ -23,10 +26,12 @@ "Smri", "startcase", "statsfull", + "Stringifying", "strvk", "tankaverages", "tankopedia", "tankstats", + "tresabhi", "Unicum", "Unregistering", "vickers", diff --git a/package.json b/package.json index 296a85345..7a8020c04 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "build:bot": "node scripts/build --build=bot", "build:server": "node scripts/build --build=server", "build:docs": "vitepress build docs", + "build:ratingsleaderboard": "node scripts/generateRatingsLeaderboard", "build": "node scripts/build --build=all", "dev:bot": "yarn build:bot --dev && yarn start:bot", "dev:server": "yarn build:server --dev && yarn start:server", @@ -15,6 +16,7 @@ "start:bot": "node --enable-source-maps --trace-warnings dist/bot.cjs", "start:server": "node --enable-source-maps --trace-warnings dist/server.cjs", "host:docs": "vitepress preview docs", + "publish:ratingsleaderboard": "node scripts/generateRatingsLeaderboard --publish", "zip": "zip -r dist.zip dist", "lint": "npx tsc", "format": "prettier --write \"**/*.@(json|js|jsx|css|md|ts|tsx|html|xml|yml|gltf)\"" @@ -36,6 +38,7 @@ "vitepress-plugin-auto-sidebar": "^1.1.0" }, "devDependencies": { + "@octokit/rest": "^20.0.1", "@total-typescript/ts-reset": "^0.4.2", "@types/escape-html": "^1.0.2", "@types/express": "^4.17.17", diff --git a/scripts/build.js b/scripts/build.js index a49c88c09..59e812ddb 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -4,22 +4,22 @@ import { argv } from 'process'; const isProd = !argv.includes('--dev'); const buildAll = argv.includes('--build=all'); -const buildBot = argv.includes('--build=bot') | buildAll; -const buildServer = argv.includes('--build=server') | buildAll; +const buildBot = argv.includes('--build=bot') || buildAll; +const buildServer = argv.includes('--build=server') || buildAll; if (isProd) { // only remove in production to ensure no time is wasted in dev - console.log('🟡 Removing dist...'); + console.log('Removing dist...'); await rm('dist', { recursive: true, force: true }); - console.log('✅ Dist removed'); + console.log('Dist removed'); - console.log('🟡 Creating dist...'); + console.log('Creating dist...'); await mkdir('dist'); - console.log('✅ Dist created'); + console.log('Dist created'); - console.log('🟡 Copying package.json...'); + console.log('Copying package.json...'); copyFile('package.dist.json', 'dist/package.json').then(() => - console.log('✅ package.json copied'), + console.log('package.json copied'), ); } @@ -40,25 +40,25 @@ const commonOptions = { }; if (buildBot) { - console.log('🟡 Building bot...'); + console.log('Building bot...'); build({ ...commonOptions, entryPoints: ['src/bot.ts'], outfile: 'dist/bot.cjs', }).then(() => { - console.log('✅ Bot built'); + console.log('Bot built'); }); } if (buildServer) { - console.log('🟡 Building server...'); + console.log('Building server...'); build({ ...commonOptions, entryPoints: ['src/server.ts'], outfile: 'dist/server.cjs', }).then(() => { - console.log('✅ Server built'); + console.log('Server built'); }); } diff --git a/scripts/commitMultipleFiles.js b/scripts/commitMultipleFiles.js new file mode 100644 index 000000000..58d6a6cdd --- /dev/null +++ b/scripts/commitMultipleFiles.js @@ -0,0 +1,67 @@ +export default async function commitMultipleFiles( + octokit, + owner, + repo, + branch, + message, + changes, +) { + // Get the latest commit SHA on the specified branch + const { data: refData } = await octokit.git.getRef({ + owner, + repo, + ref: `heads/${branch}`, + }); + const latestCommitSha = refData.object.sha; + + // Get the tree SHA for the latest commit + const { data: commitData } = await octokit.git.getCommit({ + owner, + repo, + commit_sha: latestCommitSha, + }); + const treeSha = commitData.tree.sha; + + // Create blobs for each file + const blobs = await Promise.all( + changes.map(async (change) => { + const { data: blobData } = await octokit.git.createBlob({ + owner, + repo, + content: change.content, + encoding: 'utf-8', + }); + return { + sha: blobData.sha, + path: change.path, + mode: '100644', + type: 'blob', + }; + }), + ); + + // Create a new tree with the new blobs + const { data: treeData } = await octokit.git.createTree({ + owner, + repo, + base_tree: treeSha, + tree: blobs, + }); + + // Create a new commit with the new tree + const { data: newCommitData } = await octokit.git.createCommit({ + owner, + repo, + message, + tree: treeData.sha, + parents: [latestCommitSha], + }); + + // Update the branch reference to point to the new commit + await octokit.git.updateRef({ + owner, + repo, + ref: `heads/${branch}`, + sha: newCommitData.sha, + }); +} diff --git a/scripts/generateRatingsLeaderboard.js b/scripts/generateRatingsLeaderboard.js new file mode 100644 index 000000000..488be99d2 --- /dev/null +++ b/scripts/generateRatingsLeaderboard.js @@ -0,0 +1,111 @@ +import { Octokit } from '@octokit/rest'; +import { rm, writeFile } from 'fs/promises'; +import { argv, env } from 'process'; +import commitMultipleFiles from './commitMultipleFiles.js'; + +const publish = argv.includes('--publish'); +const server = 'na'; + +const NEIGHBORS = 1000; +const PLAYERS = await fetch( + `https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`, +) + .then((response) => response.json()) + .then((data) => data.count); + +const players = {}; +const leaderboard = []; +let registered = 0; + +async function branchFromPlayer(id) { + const { neighbors } = await fetch( + `https://${server}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}`, + ).then((response) => response.json()); + + const firstPlayer = neighbors[0]; + const lastPlayer = neighbors[neighbors.length - 1]; + + const branches = []; + if (!players[firstPlayer.number]) + branches.push(branchFromPlayer(firstPlayer.spa_id)); + if (!players[lastPlayer.number]) + branches.push(branchFromPlayer(lastPlayer.spa_id)); + + neighbors.forEach((neighbor) => { + if (players[neighbor.number]) return; + + players[neighbor.number] = neighbor; + registered++; + }); + + console.log( + `Registered ${registered} / ${PLAYERS} (${( + (registered / PLAYERS) * + 100 + ).toFixed(2)}%)`, + ); + + await Promise.all(branches); +} + +async function branchFromLeague(id) { + const { result } = await fetch( + `https://${server}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`, + ).then((response) => response.json()); + + const firstPlayer = result[0]; + const lastPlayer = result[result.length - 1]; + + await Promise.all([ + branchFromPlayer(firstPlayer.spa_id), + branchFromPlayer(lastPlayer.spa_id), + ]); +} + +console.log('Branching form leagues...'); +await Promise.all([ + branchFromLeague(0), + branchFromLeague(1), + branchFromLeague(2), + branchFromLeague(3), + branchFromLeague(4), +]); + +console.log('Converting to an array...'); +for (let index = 0; index < PLAYERS; index++) { + leaderboard[index] = players[index + 1]; + if (!leaderboard[index]) console.warn(`${index} had no player`); +} + +const leaderboardJSON = JSON.stringify(leaderboard); + +if (publish) { + const octokit = new Octokit({ auth: env.GITHUB_TOKEN }); + + const info = await fetch( + `https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`, + ).then((response) => response.json()); + const time = Math.round(Date.now() / 1000); + const normalizedServer = server === 'na' ? 'com' : server; + + const infoPath = `${normalizedServer}/ratings/seasons/${info.current_season}.json`; + const leaderboardPath = `${normalizedServer}/ratings/leaderboards/${info.current_season}/${time}.json`; + const infoJSON = JSON.stringify(info); + + console.log(`Publishing to season ${info.current_season}...`); + commitMultipleFiles( + octokit, + 'tresabhi', + 'blitzkrieg-db', + 'main', + `${info.current_season}/${time}`, + [ + { path: infoPath, content: infoJSON }, + { path: leaderboardPath, content: leaderboardJSON }, + ], + ); +} else { + console.log('Writing to disk...'); + await rm('dist.json', { force: true }); + await writeFile('dist.json', leaderboardJSON); +} diff --git a/src/commands/inactive.tsx b/src/commands/inactive.tsx index 4b904833d..33a6ef8b6 100644 --- a/src/commands/inactive.tsx +++ b/src/commands/inactive.tsx @@ -4,7 +4,7 @@ import NoData, { NoDataType } from '../components/NoData'; import PoweredBy, { PoweredByType } from '../components/PoweredBy'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { BLITZ_SERVERS } from '../constants/servers'; +import { REGION_DOMAIN_NAMES } from '../constants/regions'; import getBlitzClan from '../core/blitz/getBlitzClan'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import addClanChoices from '../core/discord/addClanChoices'; @@ -72,7 +72,7 @@ export const inactiveCommand: CommandRegistry = { nameDiscriminator="(Inactivity)" image={`https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData.emblem_set_id}.png`} description={`${threshold}+ Days • ${new Date().toDateString()} • ${ - BLITZ_SERVERS[server] + REGION_DOMAIN_NAMES[server] }`} /> diff --git a/src/commands/ownedTanks.tsx b/src/commands/ownedTanks.tsx index b43aafd68..0a4cbcfde 100644 --- a/src/commands/ownedTanks.tsx +++ b/src/commands/ownedTanks.tsx @@ -4,7 +4,7 @@ import PoweredBy, { PoweredByType } from '../components/PoweredBy'; import * as Tanks from '../components/Tanks'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { BLITZ_SERVERS } from '../constants/servers'; +import { REGION_DOMAIN_NAMES } from '../constants/regions'; import getTankStats from '../core/blitz/getTankStats'; import getTreeType from '../core/blitz/getTreeType'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; @@ -105,7 +105,7 @@ export const ownedTanksCommand: CommandRegistry = { : undefined } description={`Owned tanks • ${new Date().toDateString()} • ${ - BLITZ_SERVERS[server] + REGION_DOMAIN_NAMES[server] }`} /> diff --git a/src/commands/searchClans.ts b/src/commands/searchClans.ts index 6b0e9581c..f89ea8f78 100644 --- a/src/commands/searchClans.ts +++ b/src/commands/searchClans.ts @@ -1,6 +1,6 @@ import { SlashCommandBuilder } from 'discord.js'; import markdownEscape from 'markdown-escape'; -import { BLITZ_SERVERS, BlitzServer } from '../constants/servers'; +import { REGION_DOMAIN_NAMES, RegionDomain } from '../constants/regions'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import addClanChoices from '../core/discord/addClanChoices'; import addServerChoices from '../core/discord/addServerChoices'; @@ -28,7 +28,7 @@ export const searchClansCommand: CommandRegistry = { ), async handler(interaction) { - const server = interaction.options.getString('server') as BlitzServer; + const server = interaction.options.getString('server') as RegionDomain; const clan = interaction.options.getString('clan')!; const limit = interaction.options.getInteger('limit') ?? 25; const clanList = await getWargamingResponse( @@ -36,7 +36,9 @@ export const searchClansCommand: CommandRegistry = { ); return embedInfo( - `Clan search for "${markdownEscape(clan)}" in ${BLITZ_SERVERS[server]}`, + `Clan search for "${markdownEscape(clan)}" in ${ + REGION_DOMAIN_NAMES[server] + }`, clanList.length === 0 ? 'No clans found.' : `\`\`\`\n${clanList diff --git a/src/commands/searchPlayers.ts b/src/commands/searchPlayers.ts index ad6f0bbc5..4b430aea4 100644 --- a/src/commands/searchPlayers.ts +++ b/src/commands/searchPlayers.ts @@ -1,6 +1,6 @@ import { SlashCommandBuilder } from 'discord.js'; import markdownEscape from 'markdown-escape'; -import { BLITZ_SERVERS, BlitzServer } from '../constants/servers'; +import { REGION_DOMAIN_NAMES, RegionDomain } from '../constants/regions'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import { usernamePattern } from '../core/blitz/listPlayers'; import addServerChoices from '../core/discord/addServerChoices'; @@ -31,7 +31,7 @@ export const searchPlayersCommand: CommandRegistry = { ), async handler(interaction) { - const server = interaction.options.getString('server') as BlitzServer; + const server = interaction.options.getString('server') as RegionDomain; const name = interaction.options.getString('username')!; const limit = interaction.options.getInteger('limit') ?? 25; const trimmedSearch = name.trim(); @@ -44,7 +44,9 @@ export const searchPlayersCommand: CommandRegistry = { : []; return embedInfo( - `Player search for "${markdownEscape(name)}" in ${BLITZ_SERVERS[server]}`, + `Player search for "${markdownEscape(name)}" in ${ + REGION_DOMAIN_NAMES[server] + }`, `\`\`\`${ players.length === 0 ? 'No players found.' diff --git a/src/constants/regions.ts b/src/constants/regions.ts new file mode 100644 index 000000000..59602911c --- /dev/null +++ b/src/constants/regions.ts @@ -0,0 +1,14 @@ +export type RegionDomain = 'com' | 'eu' | 'asia'; +export type RegionSubdomain = 'na' | 'eu' | 'asia'; + +export const REGION_DOMAIN_NAMES: Record = { + com: 'North America', + eu: 'Europe', + asia: 'Asia', +}; + +export const REGION_SUBDOMAIN_NAMES: Record = { + na: 'North America', + eu: 'Europe', + asia: 'Asia', +}; diff --git a/src/constants/servers.ts b/src/constants/servers.ts deleted file mode 100644 index 593125389..000000000 --- a/src/constants/servers.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type BlitzServer = 'com' | 'eu' | 'asia'; - -export const BLITZ_SERVERS: Record = { - com: 'North America', - eu: 'Europe', - asia: 'Asia', -}; diff --git a/src/core/blitz/getBlitzClan.ts b/src/core/blitz/getBlitzClan.ts index fb4785996..be7281baa 100644 --- a/src/core/blitz/getBlitzClan.ts +++ b/src/core/blitz/getBlitzClan.ts @@ -1,4 +1,4 @@ -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import { serverAndIdPattern } from '../discord/resolvePlayerFromCommand'; import throwError from '../node/throwError'; import listClans from './listClans'; @@ -6,7 +6,7 @@ import listClans from './listClans'; export default async function getBlitzClan(clan: string) { if (serverAndIdPattern.test(clan)) { const [server, accountId] = clan.split('/'); - return { server: server as BlitzServer, id: Number(accountId) }; + return { server: server as RegionDomain, id: Number(accountId) }; } else { const accounts = await listClans(clan); diff --git a/src/core/blitz/getTankStats.ts b/src/core/blitz/getTankStats.ts index c8b2e7508..09b2b58c4 100644 --- a/src/core/blitz/getTankStats.ts +++ b/src/core/blitz/getTankStats.ts @@ -1,10 +1,10 @@ -import { BLITZ_SERVERS, BlitzServer } from '../../constants/servers'; +import { REGION_DOMAIN_NAMES, RegionDomain } from '../../constants/regions'; import { TanksStats } from '../../types/tanksStats'; import { WARGAMING_APPLICATION_ID } from '../node/arguments'; import throwError from '../node/throwError'; import getWargamingResponse from './getWargamingResponse'; -export default async function getTankStats(server: BlitzServer, id: number) { +export default async function getTankStats(server: RegionDomain, id: number) { const tankStats = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`, ); @@ -12,7 +12,7 @@ export default async function getTankStats(server: BlitzServer, id: number) { if (tankStats[id] === null) { throw throwError( 'No tank stats available', - `Wargaming says there is no tank stats for this account. This account may not have any battles/tanks or exist in the ${BLITZ_SERVERS[server]} server.`, + `Wargaming says there is no tank stats for this account. This account may not have any battles/tanks or exist in the ${REGION_DOMAIN_NAMES[server]} server.`, ); } diff --git a/src/core/blitz/listClans.ts b/src/core/blitz/listClans.ts index 5c83d6c48..5ae7c3f25 100644 --- a/src/core/blitz/listClans.ts +++ b/src/core/blitz/listClans.ts @@ -1,4 +1,4 @@ -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import { Clan, ClanList } from '../../types/clanList'; import { WARGAMING_APPLICATION_ID } from '../node/arguments'; import getWargamingResponse from './getWargamingResponse'; @@ -21,7 +21,7 @@ export default async function listClans(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'com' as BlitzServer, + server: 'com' as RegionDomain, })), ), getWargamingResponse( @@ -31,7 +31,7 @@ export default async function listClans(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'eu' as BlitzServer, + server: 'eu' as RegionDomain, })), ), getWargamingResponse( @@ -41,7 +41,7 @@ export default async function listClans(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'asia' as BlitzServer, + server: 'asia' as RegionDomain, })), ), ]) diff --git a/src/core/blitz/listPlayers.ts b/src/core/blitz/listPlayers.ts index 4ee46bcd0..2571c7f14 100644 --- a/src/core/blitz/listPlayers.ts +++ b/src/core/blitz/listPlayers.ts @@ -1,4 +1,4 @@ -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import { Account, AccountList } from '../../types/accountList'; import { WARGAMING_APPLICATION_ID } from '../node/arguments'; import getWargamingResponse from './getWargamingResponse'; @@ -25,7 +25,7 @@ export default async function listPlayers(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'com' as BlitzServer, + server: 'com' as RegionDomain, })), ), getWargamingResponse( @@ -35,7 +35,7 @@ export default async function listPlayers(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'eu' as BlitzServer, + server: 'eu' as RegionDomain, })), ), getWargamingResponse( @@ -45,7 +45,7 @@ export default async function listPlayers(search: string, limit = 9) { value && value.map((account) => ({ ...account, - server: 'asia' as BlitzServer, + server: 'asia' as RegionDomain, })), ), ]) diff --git a/src/core/blitzstars/getDiffedTankStats.ts b/src/core/blitzstars/getDiffedTankStats.ts index bdbab3773..2b1e3f269 100644 --- a/src/core/blitzstars/getDiffedTankStats.ts +++ b/src/core/blitzstars/getDiffedTankStats.ts @@ -1,4 +1,4 @@ -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import { AllStats } from '../../types/accountInfo'; import getTankHistories, { TankHistories, @@ -35,7 +35,7 @@ export const emptyTankHistoryNode: TankHistoryRaw = { }; export default async function getDiffedTankStats( - server: BlitzServer, + server: RegionDomain, id: number, start: number, end: number, diff --git a/src/core/blitzstars/getPeriodStart.ts b/src/core/blitzstars/getPeriodStart.ts index 8357aab44..0faae2fba 100644 --- a/src/core/blitzstars/getPeriodStart.ts +++ b/src/core/blitzstars/getPeriodStart.ts @@ -1,9 +1,9 @@ -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import { Period } from '../discord/addPeriodSubCommands'; import getTimeDaysAgo from './getTimeDaysAgo'; export default function getPeriodStart( - server: BlitzServer, + server: RegionDomain, period: Exclude, ) { if (period === 'career') { diff --git a/src/core/blitzstars/getPlayerHistories.ts b/src/core/blitzstars/getPlayerHistories.ts index ac0419b93..895ba7edf 100644 --- a/src/core/blitzstars/getPlayerHistories.ts +++ b/src/core/blitzstars/getPlayerHistories.ts @@ -1,4 +1,4 @@ -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import { AccountInfo, AllStats } from '../../types/accountInfo'; import { GetHistoriesOptions, @@ -25,13 +25,13 @@ export interface PlayerHistoryRaw { account_id: number; [key: string]: number; }; - region: BlitzServer; + region: RegionDomain; } export type PlayerHistoriesRaw = PlayerHistoryRaw[]; export default async function getPlayerHistories( - server: BlitzServer, + server: RegionDomain, id: number, options?: Partial, ) { diff --git a/src/core/blitzstars/getTankHistories.ts b/src/core/blitzstars/getTankHistories.ts index e6fb78415..96f9b8e03 100644 --- a/src/core/blitzstars/getTankHistories.ts +++ b/src/core/blitzstars/getTankHistories.ts @@ -1,4 +1,4 @@ -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import { GetHistoriesOptions, Histories, @@ -32,7 +32,7 @@ export const getTankHistoriesDefaultOptions: GetTankHistoriesOptions = { }; export default async function getTankHistories( - server: BlitzServer, + server: RegionDomain, id: number, options?: Partial, ) { diff --git a/src/core/blitzstars/getTimeDaysAgo.ts b/src/core/blitzstars/getTimeDaysAgo.ts index ff8452ec2..421fec09f 100644 --- a/src/core/blitzstars/getTimeDaysAgo.ts +++ b/src/core/blitzstars/getTimeDaysAgo.ts @@ -1,12 +1,12 @@ -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; -export const TIME_ZONE_MAPPINGS: Record = { +export const TIME_ZONE_MAPPINGS: Record = { com: -5, // Central North American Time eu: +1, // Central European Time asia: +7, // Central Asia Standard Time }; -export default function getTimeDaysAgo(server: BlitzServer, daysAgo: number) { +export default function getTimeDaysAgo(server: RegionDomain, daysAgo: number) { const now = new Date(); if (daysAgo === 0) now.getTime() / 1000; diff --git a/src/core/discord/addServerChoices.ts b/src/core/discord/addServerChoices.ts index e3f806aa9..e702c85d7 100644 --- a/src/core/discord/addServerChoices.ts +++ b/src/core/discord/addServerChoices.ts @@ -1,14 +1,14 @@ import { SlashCommandStringOption } from 'discord.js'; -import { BLITZ_SERVERS } from '../../constants/servers'; +import { REGION_DOMAIN_NAMES } from '../../constants/regions'; export default function addServerChoices(option: SlashCommandStringOption) { return option .setName('server') .setDescription('The Blitz server you are in') .addChoices( - { name: BLITZ_SERVERS.com, value: 'com' }, - { name: BLITZ_SERVERS.eu, value: 'eu' }, - { name: BLITZ_SERVERS.asia, value: 'asia' }, + { name: REGION_DOMAIN_NAMES.com, value: 'com' }, + { name: REGION_DOMAIN_NAMES.eu, value: 'eu' }, + { name: REGION_DOMAIN_NAMES.asia, value: 'asia' }, ) .setRequired(true); } diff --git a/src/core/discord/autocompleteClan.ts b/src/core/discord/autocompleteClan.ts index fb5296ddc..a06497e6a 100644 --- a/src/core/discord/autocompleteClan.ts +++ b/src/core/discord/autocompleteClan.ts @@ -3,7 +3,7 @@ import { AutocompleteInteraction, CacheType, } from 'discord.js'; -import { BLITZ_SERVERS } from '../../constants/servers'; +import { REGION_DOMAIN_NAMES } from '../../constants/regions'; import listClans from '../blitz/listClans'; export default async function autocompleteClan( @@ -18,7 +18,7 @@ export default async function autocompleteClan( ? players.map( (player) => ({ - name: `${player.name} (${BLITZ_SERVERS[player.server]})`, + name: `${player.name} (${REGION_DOMAIN_NAMES[player.server]})`, value: `${player.server}/${player.clan_id}`, }) satisfies ApplicationCommandOptionChoiceData, ) diff --git a/src/core/discord/autocompleteUsername.ts b/src/core/discord/autocompleteUsername.ts index 2ee5b3464..4664baf10 100644 --- a/src/core/discord/autocompleteUsername.ts +++ b/src/core/discord/autocompleteUsername.ts @@ -3,7 +3,7 @@ import { AutocompleteInteraction, CacheType, } from 'discord.js'; -import { BLITZ_SERVERS } from '../../constants/servers'; +import { REGION_DOMAIN_NAMES } from '../../constants/regions'; import listPlayers from '../blitz/listPlayers'; export default async function autocompleteUsername( @@ -19,7 +19,9 @@ export default async function autocompleteUsername( ? players.map( (player) => ({ - name: `${player.nickname} (${BLITZ_SERVERS[player.server]})`, + name: `${player.nickname} (${ + REGION_DOMAIN_NAMES[player.server] + })`, value: `${player.server}/${player.account_id}`, }) satisfies ApplicationCommandOptionChoiceData, ) diff --git a/src/core/discord/resolvePeriodFromButton.ts b/src/core/discord/resolvePeriodFromButton.ts index 0c31e65e4..1e7236857 100644 --- a/src/core/discord/resolvePeriodFromButton.ts +++ b/src/core/discord/resolvePeriodFromButton.ts @@ -1,10 +1,10 @@ import { ButtonInteraction, CacheType } from 'discord.js'; import { CYCLIC_API } from '../../constants/cyclic'; -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import resolvePeriodFromURL from '../express/resolvePeriodFromURL'; export default function resolvePeriodFromButton( - server: BlitzServer, + server: RegionDomain, interaction: ButtonInteraction, ) { return resolvePeriodFromURL(server, `${CYCLIC_API}/${interaction.customId}`); diff --git a/src/core/discord/resolvePeriodFromCommand.ts b/src/core/discord/resolvePeriodFromCommand.ts index 6672f41fb..c5fe1259c 100644 --- a/src/core/discord/resolvePeriodFromCommand.ts +++ b/src/core/discord/resolvePeriodFromCommand.ts @@ -1,5 +1,5 @@ import { CacheType, ChatInputCommandInteraction } from 'discord.js'; -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import getPeriodNow from '../blitzstars/getPeriodNow'; import getPeriodStart from '../blitzstars/getPeriodStart'; import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo'; @@ -31,7 +31,7 @@ export interface ResolvedPeriod { } export default function resolvePeriodFromCommand( - server: BlitzServer, + server: RegionDomain, interaction: ChatInputCommandInteraction, ) { let statsName: string; diff --git a/src/core/discord/resolvePlayerFromCommand.ts b/src/core/discord/resolvePlayerFromCommand.ts index 7c30bfa27..e9762d01a 100644 --- a/src/core/discord/resolvePlayerFromCommand.ts +++ b/src/core/discord/resolvePlayerFromCommand.ts @@ -1,5 +1,5 @@ import { CacheType, ChatInputCommandInteraction } from 'discord.js'; -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import listPlayers, { usernamePatternWithoutPosition, } from '../blitz/listPlayers'; @@ -8,7 +8,7 @@ import throwError from '../node/throwError'; export const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/; export interface ResolvedPlayer { - server: BlitzServer; + server: RegionDomain; id: number; } @@ -31,7 +31,7 @@ export default async function resolvePlayerFromCommand( if (serverAndIdPattern.test(username)) { const [server, accountId] = username.split('/'); return { - server: server as BlitzServer, + server: server as RegionDomain, id: Number(accountId), } satisfies ResolvedPlayer; } else { diff --git a/src/core/express/resolvePeriodFromRequest.ts b/src/core/express/resolvePeriodFromRequest.ts index b0e964ad8..7ff160402 100644 --- a/src/core/express/resolvePeriodFromRequest.ts +++ b/src/core/express/resolvePeriodFromRequest.ts @@ -1,10 +1,10 @@ import { Request } from 'express'; import { CYCLIC_API } from '../../constants/cyclic'; -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import resolvePeriodFromURL from './resolvePeriodFromURL'; export default function resolvePeriodFromRequest( - server: BlitzServer, + server: RegionDomain, request: Request, ) { return resolvePeriodFromURL(server, `${CYCLIC_API}${request.path}`); diff --git a/src/core/express/resolvePeriodFromURL.ts b/src/core/express/resolvePeriodFromURL.ts index d478126c6..464e1c904 100644 --- a/src/core/express/resolvePeriodFromURL.ts +++ b/src/core/express/resolvePeriodFromURL.ts @@ -1,4 +1,4 @@ -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import getPeriodNow from '../blitzstars/getPeriodNow'; import getPeriodStart from '../blitzstars/getPeriodStart'; import getTimeDaysAgo from '../blitzstars/getTimeDaysAgo'; @@ -10,7 +10,7 @@ import { } from '../discord/resolvePeriodFromCommand'; export default function resolvePeriodFromURL( - server: BlitzServer, + server: RegionDomain, urlString: string, ) { let statsName: string; diff --git a/src/core/express/resolvePlayerFromURL.ts b/src/core/express/resolvePlayerFromURL.ts index 983fa11b8..112e2abb5 100644 --- a/src/core/express/resolvePlayerFromURL.ts +++ b/src/core/express/resolvePlayerFromURL.ts @@ -1,4 +1,4 @@ -import { BlitzServer } from '../../constants/servers'; +import { RegionDomain } from '../../constants/regions'; import { ResolvedPlayer } from '../discord/resolvePlayerFromCommand'; export default function resolvePlayerFromURL(urlString: string) { @@ -6,6 +6,6 @@ export default function resolvePlayerFromURL(urlString: string) { return { id: parseInt(url.searchParams.get('id')!), - server: url.searchParams.get('server') as BlitzServer, + server: url.searchParams.get('server') as RegionDomain, } satisfies ResolvedPlayer; } diff --git a/src/renderers/evolution.tsx b/src/renderers/evolution.tsx index 19d4f85e6..5300a2619 100644 --- a/src/renderers/evolution.tsx +++ b/src/renderers/evolution.tsx @@ -3,7 +3,7 @@ import NoData, { NoDataType } from '../components/NoData'; import PoweredBy, { PoweredByType } from '../components/PoweredBy'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { BLITZ_SERVERS } from '../constants/servers'; +import { REGION_DOMAIN_NAMES } from '../constants/regions'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import resolveTankName from '../core/blitz/resolveTankName'; import { tankopedia } from '../core/blitz/tankopedia'; @@ -89,7 +89,7 @@ export default async function evolution( nameDiscriminator={nameDiscriminator} image={image} description={`${evolutionName} • ${new Date().toDateString()} • ${ - BLITZ_SERVERS[server] + REGION_DOMAIN_NAMES[server] }`} /> diff --git a/src/renderers/fullStats.tsx b/src/renderers/fullStats.tsx index 1a2b20d2d..16957b2bd 100644 --- a/src/renderers/fullStats.tsx +++ b/src/renderers/fullStats.tsx @@ -4,7 +4,7 @@ import PoweredBy, { PoweredByType } from '../components/PoweredBy'; import TierWeights, { TierWeightsRecord } from '../components/TierWeights'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { BLITZ_SERVERS } from '../constants/servers'; +import { REGION_DOMAIN_NAMES } from '../constants/regions'; import calculateWN8 from '../core/blitz/calculateWN8'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import resolveTankName from '../core/blitz/resolveTankName'; @@ -143,7 +143,7 @@ export default async function fullStats( nameDiscriminator={nameDiscriminator} image={image} description={`${statsName} • ${new Date().toDateString()} • ${ - BLITZ_SERVERS[server] + REGION_DOMAIN_NAMES[server] }`} /> diff --git a/src/renderers/stats.tsx b/src/renderers/stats.tsx index 9fe5e2629..ed8755d0d 100644 --- a/src/renderers/stats.tsx +++ b/src/renderers/stats.tsx @@ -4,7 +4,7 @@ import PoweredBy, { PoweredByType } from '../components/PoweredBy'; import { TierWeightsRecord } from '../components/TierWeights'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { BLITZ_SERVERS } from '../constants/servers'; +import { REGION_DOMAIN_NAMES } from '../constants/regions'; import calculateWN8 from '../core/blitz/calculateWN8'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; import resolveTankName from '../core/blitz/resolveTankName'; @@ -164,7 +164,7 @@ export default async function stats( nameDiscriminator={nameDiscriminator} image={image} description={`${statsName} • ${new Date().toDateString()} • ${ - BLITZ_SERVERS[server] + REGION_DOMAIN_NAMES[server] }`} /> diff --git a/src/renderers/today.tsx b/src/renderers/today.tsx index 24028604f..9928062c3 100644 --- a/src/renderers/today.tsx +++ b/src/renderers/today.tsx @@ -3,7 +3,7 @@ import NoData, { NoDataType } from '../components/NoData'; import PoweredBy, { PoweredByType } from '../components/PoweredBy'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; -import { BLITZ_SERVERS } from '../constants/servers'; +import { REGION_DOMAIN_NAMES } from '../constants/regions'; import calculateWN8 from '../core/blitz/calculateWN8'; import getTankStats from '../core/blitz/getTankStats'; import getWargamingResponse from '../core/blitz/getWargamingResponse'; @@ -162,7 +162,7 @@ export default async function today( : undefined } description={`Today's breakdown • ${new Date().toDateString()} • ${ - BLITZ_SERVERS[server] + REGION_DOMAIN_NAMES[server] }`} /> diff --git a/yarn.lock b/yarn.lock index e74fb6d32..9199e75d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -445,6 +445,103 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== +"@octokit/auth-token@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-4.0.0.tgz#40d203ea827b9f17f42a29c6afb93b7745ef80c7" + integrity sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA== + +"@octokit/core@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-5.0.0.tgz#0fc2b6eb88437e5c1d69f756a5dcee7472d2b2dd" + integrity sha512-YbAtMWIrbZ9FCXbLwT9wWB8TyLjq9mxpKdgB3dUNxQcIVTf9hJ70gRPwAcqGZdY6WdJPZ0I7jLaaNDCiloGN2A== + dependencies: + "@octokit/auth-token" "^4.0.0" + "@octokit/graphql" "^7.0.0" + "@octokit/request" "^8.0.2" + "@octokit/request-error" "^5.0.0" + "@octokit/types" "^11.0.0" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-9.0.0.tgz#c5ce19c74b999b85af9a8a189275c80faa3e90fd" + integrity sha512-szrQhiqJ88gghWY2Htt8MqUDO6++E/EIXqJ2ZEp5ma3uGS46o7LZAzSLt49myB7rT+Hfw5Y6gO3LmOxGzHijAQ== + dependencies: + "@octokit/types" "^11.0.0" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^7.0.0": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-7.0.1.tgz#f2291620e17cdaa8115f8d0cdfc0644789ec2db2" + integrity sha512-T5S3oZ1JOE58gom6MIcrgwZXzTaxRnxBso58xhozxHpOqSTgDS6YNeEUvZ/kRvXgPrRz/KHnZhtb7jUMRi9E6w== + dependencies: + "@octokit/request" "^8.0.1" + "@octokit/types" "^11.0.0" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^18.0.0": + version "18.0.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-18.0.0.tgz#f43d765b3c7533fd6fb88f3f25df079c24fccf69" + integrity sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw== + +"@octokit/plugin-paginate-rest@^8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-8.0.0.tgz#417b5367da2ba3c2d255a59b87c1cc608228ec38" + integrity sha512-2xZ+baZWUg+qudVXnnvXz7qfrTmDeYPCzangBVq/1gXxii/OiS//4shJp9dnCCvj1x+JAm9ji1Egwm1BA47lPQ== + dependencies: + "@octokit/types" "^11.0.0" + +"@octokit/plugin-request-log@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz#260fa6970aa97bbcbd91f99f3cd812e2b285c9f1" + integrity sha512-2uJI1COtYCq8Z4yNSnM231TgH50bRkheQ9+aH8TnZanB6QilOnx8RMD2qsnamSOXtDj0ilxvevf5fGsBhBBzKA== + +"@octokit/plugin-rest-endpoint-methods@^9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-9.0.0.tgz#e15d54540893202da107305ded2bfd21ce6f769d" + integrity sha512-KquMF/VB1IkKNiVnzJKspY5mFgGyLd7HzdJfVEGTJFzqu9BRFNWt+nwTCMuUiWc72gLQhRWYubTwOkQj+w/1PA== + dependencies: + "@octokit/types" "^11.0.0" + +"@octokit/request-error@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-5.0.0.tgz#060c5770833f9d563ad9a49fec6650c41584bc40" + integrity sha512-1ue0DH0Lif5iEqT52+Rf/hf0RmGO9NWFjrzmrkArpG9trFfDM/efx00BJHdLGuro4BR/gECxCU2Twf5OKrRFsQ== + dependencies: + "@octokit/types" "^11.0.0" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^8.0.1", "@octokit/request@^8.0.2": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-8.1.0.tgz#3b0fd15faeda1545024af09c31ac2689f4d65e7d" + integrity sha512-0gg/NwewU0iXctYBale0VVcCPqOtoW5lsog8cNBJgzV/CyTHa2gicUBOlNnzOk6pJkuwXI34qkq+uRm40PmD4A== + dependencies: + "@octokit/endpoint" "^9.0.0" + "@octokit/request-error" "^5.0.0" + "@octokit/types" "^11.1.0" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/rest@^20.0.1": + version "20.0.1" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-20.0.1.tgz#b8ee194b7cf89772d1e3fea3209f741c76a5efd3" + integrity sha512-wROV21RwHQIMNb2Dgd4+pY+dVy1Dwmp85pBrgr6YRRDYRBu9Gb+D73f4Bl2EukZSj5hInq2Tui9o7gAQpc2k2Q== + dependencies: + "@octokit/core" "^5.0.0" + "@octokit/plugin-paginate-rest" "^8.0.0" + "@octokit/plugin-request-log" "^4.0.0" + "@octokit/plugin-rest-endpoint-methods" "^9.0.0" + +"@octokit/types@^11.0.0", "@octokit/types@^11.1.0": + version "11.1.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-11.1.0.tgz#9e5db741d582b05718a4d91bac8cc987def235ea" + integrity sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ== + dependencies: + "@octokit/openapi-types" "^18.0.0" + "@radix-ui/colors@^0.1.8": version "0.1.9" resolved "https://registry.yarnpkg.com/@radix-ui/colors/-/colors-0.1.9.tgz#aad732ecc4ce1018adcb3aedd3ce3c573c2256cc" @@ -871,6 +968,11 @@ base64-js@0.0.8: resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw== +before-after-hook@^2.2.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" + integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== + bepaint@^0.1.0-beta.3: version "0.1.0-beta.3" resolved "https://registry.yarnpkg.com/bepaint/-/bepaint-0.1.0-beta.3.tgz#fe8f0d20e35da075a6ed69f42d61156d985da584" @@ -992,6 +1094,11 @@ depd@2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== +deprecation@^2.0.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + destroy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" @@ -1274,6 +1381,11 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + "js-tokens@^3.0.0 || ^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -1395,6 +1507,13 @@ on-finished@2.4.1: dependencies: ee-first "1.1.1" +once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + pako@^0.2.5: version "0.2.9" resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" @@ -1686,6 +1805,11 @@ unicode-trie@^2.0.0: pako "^0.2.5" tiny-inflate "^1.0.0" +universal-user-agent@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" + integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -1767,6 +1891,11 @@ vue@^3.3.4: "@vue/server-renderer" "3.3.4" "@vue/shared" "3.3.4" +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + ws@^8.13.0: version "8.13.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0"