From 8cd1a70333886529d5f7f0421825b9a9202b44dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 29 Oct 2023 11:54:23 -0500 Subject: [PATCH 01/88] feat: isolated tankopediaInfo --- package.json | 2 +- src/commands/ownedTanks.tsx | 2 +- src/core/blitz/tankopediaInfo.ts | 15 +++++++++++++++ src/core/blitzkrieg/filtersToDescription.ts | 7 ++----- src/core/blitzstars/tankopedia.ts | 16 ---------------- src/core/discord/addFilterOptions.ts | 2 +- 6 files changed, 20 insertions(+), 24 deletions(-) create mode 100644 src/core/blitz/tankopediaInfo.ts diff --git a/package.json b/package.json index 86789c79f..03c75bf40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blitzkrieg", - "version": "1.14.4", + "version": "1.15.0", "description": "🎉 Tools for everything World of Tanks Blitz", "main": "src/server.ts", "type": "module", diff --git a/src/commands/ownedTanks.tsx b/src/commands/ownedTanks.tsx index 8f731590e..242429804 100644 --- a/src/commands/ownedTanks.tsx +++ b/src/commands/ownedTanks.tsx @@ -8,13 +8,13 @@ import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import getTankStats from '../core/blitz/getTankStats'; import getTreeType from '../core/blitz/getTreeType'; import resolveTankName from '../core/blitz/resolveTankName'; +import { tankopediaInfo } from '../core/blitz/tankopediaInfo'; import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL'; import { TIER_ROMAN_NUMERALS, TankopediaEntry, Tier, tankopedia, - tankopediaInfo, } from '../core/blitzstars/tankopedia'; import addTierChoices from '../core/discord/addTierChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices'; diff --git a/src/core/blitz/tankopediaInfo.ts b/src/core/blitz/tankopediaInfo.ts new file mode 100644 index 000000000..cc9855fea --- /dev/null +++ b/src/core/blitz/tankopediaInfo.ts @@ -0,0 +1,15 @@ +import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID'; +import fetchBlitz from './fetchBlitz'; + +export interface TankopediaInfo { + achievement_sections: Record; + tanks_updated_at: number; + languages: Record; + vehicle_types: Record; + vehicle_nations: Record; + game_version: string; +} + +export const tankopediaInfo = fetchBlitz( + `https://api.wotblitz.com/wotb/encyclopedia/info/?application_id=${WARGAMING_APPLICATION_ID}`, +); diff --git a/src/core/blitzkrieg/filtersToDescription.ts b/src/core/blitzkrieg/filtersToDescription.ts index 85d79c20d..8393ac294 100644 --- a/src/core/blitzkrieg/filtersToDescription.ts +++ b/src/core/blitzkrieg/filtersToDescription.ts @@ -1,10 +1,7 @@ import { TREE_TYPE_NAMES } from '../../components/Tanks'; import resolveTankName from '../blitz/resolveTankName'; -import { - TIER_ROMAN_NUMERALS, - Tier, - tankopediaInfo, -} from '../blitzstars/tankopedia'; +import { tankopediaInfo } from '../blitz/tankopediaInfo'; +import { TIER_ROMAN_NUMERALS, Tier } from '../blitzstars/tankopedia'; import { StatFilters } from '../statistics/filterStats'; export async function filtersToDescription({ diff --git a/src/core/blitzstars/tankopedia.ts b/src/core/blitzstars/tankopedia.ts index a21182b0e..9c00d6d2b 100644 --- a/src/core/blitzstars/tankopedia.ts +++ b/src/core/blitzstars/tankopedia.ts @@ -1,6 +1,4 @@ import { deburr } from 'lodash'; -import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID'; -import fetchBlitz from '../blitz/fetchBlitz'; import { context } from '../blitzkrieg/context'; export interface TankopediaEntry { @@ -56,20 +54,6 @@ export const TANK_NAMES_DIACRITICS = TANK_NAMES.then((tankNames) => ), ); -export interface TankopediaInfo { - achievement_sections: Record; - tanks_updated_at: number; - languages: Record; - vehicle_types: Record; - vehicle_nations: Record; - game_version: string; -} - -// this is blocking because info is needed for command creation -export const tankopediaInfo = fetchBlitz( - `https://api.wotblitz.com/wotb/encyclopedia/info/?application_id=${WARGAMING_APPLICATION_ID}`, -); - export const TANK_ICONS: Record = { 'AT-SPG': 'https://i.imgur.com/BIHSEH0.png', lightTank: 'https://i.imgur.com/CSNha5V.png', diff --git a/src/core/discord/addFilterOptions.ts b/src/core/discord/addFilterOptions.ts index 9a79642a4..9468e0484 100644 --- a/src/core/discord/addFilterOptions.ts +++ b/src/core/discord/addFilterOptions.ts @@ -4,7 +4,7 @@ import { SlashCommandSubcommandGroupBuilder, } from 'discord.js'; import { TREE_TYPE_NAMES, TreeTypeString } from '../../components/Tanks'; -import { tankopediaInfo } from '../blitzstars/tankopedia'; +import { tankopediaInfo } from '../blitz/tankopediaInfo'; import addPeriodSubCommands from './addPeriodSubCommands'; import addTankChoices from './addTankChoices'; import addTierChoices from './addTierChoices'; From 93ffe5c50781b6f63d04eb1f43f2c7083d84e10c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 29 Oct 2023 13:40:32 -0500 Subject: [PATCH 02/88] feat: options for ratings tool --- src/app/api/ratings-info/[region]/route.ts | 10 +++ src/app/tools/ratings/page.tsx | 76 ++++++++++++++++++++++ src/constants/tools.ts | 1 - src/core/blitz/getRatingsInfo.ts | 9 ++- src/stores/ratings.ts | 32 +++++++++ 5 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 src/app/api/ratings-info/[region]/route.ts create mode 100644 src/app/tools/ratings/page.tsx create mode 100644 src/stores/ratings.ts diff --git a/src/app/api/ratings-info/[region]/route.ts b/src/app/api/ratings-info/[region]/route.ts new file mode 100644 index 000000000..dcb0bb87f --- /dev/null +++ b/src/app/api/ratings-info/[region]/route.ts @@ -0,0 +1,10 @@ +import { NextResponse } from 'next/server'; +import { Region } from '../../../../constants/regions'; +import getRatingsInfo from '../../../../core/blitz/getRatingsInfo'; + +export async function GET( + request: Request, + { params }: { params: { region: Region } }, +) { + return NextResponse.json(await getRatingsInfo(params.region)); +} diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx new file mode 100644 index 000000000..eda68e495 --- /dev/null +++ b/src/app/tools/ratings/page.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { Flex, Select } from '@radix-ui/themes'; +import { useEffect, useState } from 'react'; +import { RatingsInfo } from '../../../commands/ratings'; +import PageWrapper from '../../../components/PageWrapper'; +import { Region } from '../../../constants/regions'; +import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; +import { RatingsType, useRatings } from '../../../stores/ratings'; + +export default function Page() { + const ratings = useRatings(); + const [awaitedRatingsInfo, setRatingsInfo] = useState( + null, + ); + + useEffect(() => { + getRatingsInfo(ratings.region).then(setRatingsInfo); + }, [ratings.region]); + + return ( + + + + useRatings.setState({ region: value as Region }) + } + > + + + + North America + Europe + Asia + + + + useRatings.setState({ type: value as RatingsType }) + } + > + + + + League + Neighbors + + + + {ratings.type === 'league' ? ( + + useRatings.setState({ league: parseInt(value) }) + } + > + + + + {awaitedRatingsInfo?.detail === undefined && + awaitedRatingsInfo?.leagues.map((league) => ( + + {league.title} + + ))} + + + ) : ( + '1' + )} + + + ); +} diff --git a/src/constants/tools.ts b/src/constants/tools.ts index c9c0b0241..137b53975 100644 --- a/src/constants/tools.ts +++ b/src/constants/tools.ts @@ -15,7 +15,6 @@ export const TOOLS: Tool[] = [ id: 'ratings', title: 'Ratings', description: 'Current and old season leader boards', - disabled: true, }, { id: 'tankopedia', diff --git a/src/core/blitz/getRatingsInfo.ts b/src/core/blitz/getRatingsInfo.ts index 56588cdac..5527ab044 100644 --- a/src/core/blitz/getRatingsInfo.ts +++ b/src/core/blitz/getRatingsInfo.ts @@ -1,12 +1,15 @@ import { RatingsInfo } from '../../commands/ratings'; import { Region } from '../../constants/regions'; +import { context } from '../blitzkrieg/context'; import regionToRegionSubdomain from './regionToRegionSubdomain'; export default async function getRatingsInfo(region: Region) { const response = await fetch( - `https://${regionToRegionSubdomain( - region, - )}.wotblitz.com/en/api/rating-leaderboards/season/`, + context === 'bot' + ? `https://${regionToRegionSubdomain( + region, + )}.wotblitz.com/en/api/rating-leaderboards/season/` + : `/api/ratings-info/${region}`, ); return (await response.json()) as Promise; diff --git a/src/stores/ratings.ts b/src/stores/ratings.ts new file mode 100644 index 000000000..6a9a1fc21 --- /dev/null +++ b/src/stores/ratings.ts @@ -0,0 +1,32 @@ +import { create } from 'zustand'; +import { devtools, persist } from 'zustand/middleware'; +import { Region } from '../constants/regions'; + +export type RatingsType = 'neighbors' | 'league'; + +interface Ratings { + type: RatingsType; + /** + * When null, the current season is used unless there ain't one in which case + * we do whatever lol + */ + season: number | null; + region: Region; + league: number; +} + +export const useRatings = create()( + devtools( + persist( + (set) => ({ + type: 'league', + season: null, + league: 0, + region: 'com', + }), + { + name: 'ratings', + }, + ), + ), +); From c83cec8ed743fbc829de2e89dc4e3f126bcb8a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 30 Oct 2023 21:56:01 -0500 Subject: [PATCH 03/88] feat: simpler descs --- src/constants/tools.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/constants/tools.ts b/src/constants/tools.ts index 137b53975..efcaab6a6 100644 --- a/src/constants/tools.ts +++ b/src/constants/tools.ts @@ -9,12 +9,12 @@ export const TOOLS: Tool[] = [ { id: 'session', title: 'Session tracker', - description: 'Track your performance in a session', + description: 'Live session performance tracker', }, { id: 'ratings', title: 'Ratings', - description: 'Current and old season leader boards', + description: 'Current and archived ratings leaderboards', }, { id: 'tankopedia', From 81c2ca80bc05646a77a71a6202c1b4608dd8b1cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 30 Oct 2023 21:57:22 -0500 Subject: [PATCH 04/88] feat: better ratings toolbar --- src/app/tools/ratings/page.tsx | 155 ++++++++++++++---- src/constants/regions.ts | 4 +- .../blitz/getLatestArchivedSeasonNumber.ts | 8 + src/stores/ratings.ts | 8 - 4 files changed, 131 insertions(+), 44 deletions(-) create mode 100644 src/core/blitz/getLatestArchivedSeasonNumber.ts diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index eda68e495..b762a0a41 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -1,26 +1,49 @@ 'use client'; -import { Flex, Select } from '@radix-ui/themes'; +import { MagnifyingGlassIcon } from '@radix-ui/react-icons'; +import { Button, Flex, Popover, Select, TextField } from '@radix-ui/themes'; +import { range } from 'lodash'; import { useEffect, useState } from 'react'; import { RatingsInfo } from '../../../commands/ratings'; import PageWrapper from '../../../components/PageWrapper'; -import { Region } from '../../../constants/regions'; +import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; +import { + FIRST_ARCHIVED_RATINGS_SEASON, + getLatestArchivedSeasonNumber, +} from '../../../core/blitz/getLatestArchivedSeasonNumber'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; -import { RatingsType, useRatings } from '../../../stores/ratings'; +import { useRatings } from '../../../stores/ratings'; export default function Page() { const ratings = useRatings(); - const [awaitedRatingsInfo, setRatingsInfo] = useState( - null, - ); + const [ratingsInfo, setRatingsInfo] = useState(null); + const [jumpToLeague, setJumpToLeague] = useState(0); + const [latestArchivedSeasonNumber, setLatestArchivedSeasonNumber] = useState< + number | null + >(null); + // null being the latest season + const [season, setSeason] = useState(null); useEffect(() => { - getRatingsInfo(ratings.region).then(setRatingsInfo); + (async () => { + const [newRatingsInfo, newLatestArchivedSeasonNumber] = await Promise.all( + [getRatingsInfo(ratings.region), getLatestArchivedSeasonNumber()], + ); + + setRatingsInfo(newRatingsInfo); + setLatestArchivedSeasonNumber(newLatestArchivedSeasonNumber); + + setSeason( + newRatingsInfo.detail == undefined + ? null + : newLatestArchivedSeasonNumber, + ); + })(); }, [ratings.region]); return ( - + @@ -30,46 +53,108 @@ export default function Page() { - North America - Europe - Asia + {REGIONS.map((region) => ( + + {REGION_NAMES[region]} + + ))} + - useRatings.setState({ type: value as RatingsType }) + setSeason(value === 'null' ? null : parseInt(value)) } > - League - Neighbors + {ratingsInfo?.detail === undefined && ( + Current season + )} + + + Archives + + {latestArchivedSeasonNumber && + range( + FIRST_ARCHIVED_RATINGS_SEASON, + latestArchivedSeasonNumber + 1, + ) + .map((season) => ( + + Season {season} + + )) + .reverse()} + - {ratings.type === 'league' ? ( - - useRatings.setState({ league: parseInt(value) }) - } - > - + + + + - - {awaitedRatingsInfo?.detail === undefined && - awaitedRatingsInfo?.leagues.map((league) => ( - - {league.title} - - ))} - - - ) : ( - '1' - )} + + + setJumpToLeague(parseInt(value))} + > + + + + {ratingsInfo?.detail === undefined && + ratingsInfo?.leagues.map(({ index, title }) => ( + + {title} + + ))} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); diff --git a/src/constants/regions.ts b/src/constants/regions.ts index ee4b04781..d6e088235 100644 --- a/src/constants/regions.ts +++ b/src/constants/regions.ts @@ -1,4 +1,6 @@ -export type Region = 'com' | 'eu' | 'asia'; +export const REGIONS = ['com', 'eu', 'asia'] as const; + +export type Region = (typeof REGIONS)[number]; export type RegionSubdomain = 'na' | 'eu' | 'asia'; export const REGION_NAMES: Record = { diff --git a/src/core/blitz/getLatestArchivedSeasonNumber.ts b/src/core/blitz/getLatestArchivedSeasonNumber.ts new file mode 100644 index 000000000..40cf7deac --- /dev/null +++ b/src/core/blitz/getLatestArchivedSeasonNumber.ts @@ -0,0 +1,8 @@ +/** + * The first every season that Blitzkrieg tracked :) + */ +export const FIRST_ARCHIVED_RATINGS_SEASON = 49; + +export async function getLatestArchivedSeasonNumber() { + return 51; +} diff --git a/src/stores/ratings.ts b/src/stores/ratings.ts index 6a9a1fc21..4fb2f3f35 100644 --- a/src/stores/ratings.ts +++ b/src/stores/ratings.ts @@ -2,26 +2,18 @@ import { create } from 'zustand'; import { devtools, persist } from 'zustand/middleware'; import { Region } from '../constants/regions'; -export type RatingsType = 'neighbors' | 'league'; - interface Ratings { - type: RatingsType; /** * When null, the current season is used unless there ain't one in which case * we do whatever lol */ - season: number | null; region: Region; - league: number; } export const useRatings = create()( devtools( persist( (set) => ({ - type: 'league', - season: null, - league: 0, region: 'com', }), { From 6b6ee94d9b45b61f6df9487798e38e5c97504b54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:41:04 -0500 Subject: [PATCH 05/88] feat: use corsproxy.io --- src/app/api/ratings-info/[region]/route.ts | 10 ---------- src/app/api/tank-averages/route.ts | 6 ------ src/app/api/tankopedia/route.ts | 6 ------ src/app/tools/ratings/page.tsx | 2 +- src/core/blitz/getRatingsInfo.ts | 13 ++++++++----- src/core/blitzkrieg/withCORSProxy.ts | 7 +++++++ src/core/blitzstars/tankAverages.ts | 5 ++--- src/core/blitzstars/tankopedia.ts | 5 ++--- 8 files changed, 20 insertions(+), 34 deletions(-) delete mode 100644 src/app/api/ratings-info/[region]/route.ts delete mode 100644 src/app/api/tank-averages/route.ts delete mode 100644 src/app/api/tankopedia/route.ts create mode 100644 src/core/blitzkrieg/withCORSProxy.ts diff --git a/src/app/api/ratings-info/[region]/route.ts b/src/app/api/ratings-info/[region]/route.ts deleted file mode 100644 index dcb0bb87f..000000000 --- a/src/app/api/ratings-info/[region]/route.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NextResponse } from 'next/server'; -import { Region } from '../../../../constants/regions'; -import getRatingsInfo from '../../../../core/blitz/getRatingsInfo'; - -export async function GET( - request: Request, - { params }: { params: { region: Region } }, -) { - return NextResponse.json(await getRatingsInfo(params.region)); -} diff --git a/src/app/api/tank-averages/route.ts b/src/app/api/tank-averages/route.ts deleted file mode 100644 index 7f5ab96bf..000000000 --- a/src/app/api/tank-averages/route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { NextResponse } from 'next/server'; -import { tankAverages } from '../../../core/blitzstars/tankAverages'; - -export async function GET() { - return NextResponse.json(await tankAverages); -} diff --git a/src/app/api/tankopedia/route.ts b/src/app/api/tankopedia/route.ts deleted file mode 100644 index 1ee08729c..000000000 --- a/src/app/api/tankopedia/route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { NextResponse } from 'next/server'; -import { tankopedia } from '../../../core/blitzstars/tankopedia'; - -export async function GET() { - return NextResponse.json(await tankopedia); -} diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index b762a0a41..349db2d4a 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -28,7 +28,7 @@ export default function Page() { (async () => { const [newRatingsInfo, newLatestArchivedSeasonNumber] = await Promise.all( [getRatingsInfo(ratings.region), getLatestArchivedSeasonNumber()], - ); + ); setRatingsInfo(newRatingsInfo); setLatestArchivedSeasonNumber(newLatestArchivedSeasonNumber); diff --git a/src/core/blitz/getRatingsInfo.ts b/src/core/blitz/getRatingsInfo.ts index 5527ab044..8ff7d4087 100644 --- a/src/core/blitz/getRatingsInfo.ts +++ b/src/core/blitz/getRatingsInfo.ts @@ -1,15 +1,18 @@ import { RatingsInfo } from '../../commands/ratings'; import { Region } from '../../constants/regions'; import { context } from '../blitzkrieg/context'; +import { withCORSProxy } from '../blitzkrieg/withCORSProxy'; import regionToRegionSubdomain from './regionToRegionSubdomain'; export default async function getRatingsInfo(region: Region) { const response = await fetch( - context === 'bot' - ? `https://${regionToRegionSubdomain( - region, - )}.wotblitz.com/en/api/rating-leaderboards/season/` - : `/api/ratings-info/${region}`, + withCORSProxy( + context === 'bot' + ? `https://${regionToRegionSubdomain( + region, + )}.wotblitz.com/en/api/rating-leaderboards/season/` + : `/api/ratings/info/${region}`, + ), ); return (await response.json()) as Promise; diff --git a/src/core/blitzkrieg/withCORSProxy.ts b/src/core/blitzkrieg/withCORSProxy.ts new file mode 100644 index 000000000..d000ce271 --- /dev/null +++ b/src/core/blitzkrieg/withCORSProxy.ts @@ -0,0 +1,7 @@ +import { context } from './context'; + +export function withCORSProxy(url: string) { + return context === 'website' + ? `https://corsproxy.wotblitz.com/?${encodeURIComponent(url)}` + : url; +} diff --git a/src/core/blitzstars/tankAverages.ts b/src/core/blitzstars/tankAverages.ts index a210431c6..336bb5bcf 100644 --- a/src/core/blitzstars/tankAverages.ts +++ b/src/core/blitzstars/tankAverages.ts @@ -1,5 +1,6 @@ import { AllStats } from '../blitz/getAccountInfo'; import { context } from '../blitzkrieg/context'; +import { withCORSProxy } from '../blitzkrieg/withCORSProxy'; import { tankopedia } from './tankopedia'; export interface SpecialStats { @@ -85,9 +86,7 @@ tankopedia; export type TankAverages = Record; export const tankAverages = fetch( - context === 'bot' - ? 'https://www.blitzstars.com/api/tankaverages.json' - : '/api/tank-averages', + withCORSProxy('https://www.blitzstars.com/api/tankaverages.json'), ) .then((response) => response.json()) .then((json) => { diff --git a/src/core/blitzstars/tankopedia.ts b/src/core/blitzstars/tankopedia.ts index 9c00d6d2b..f918ff7d4 100644 --- a/src/core/blitzstars/tankopedia.ts +++ b/src/core/blitzstars/tankopedia.ts @@ -1,5 +1,6 @@ import { deburr } from 'lodash'; import { context } from '../blitzkrieg/context'; +import { withCORSProxy } from '../blitzkrieg/withCORSProxy'; export interface TankopediaEntry { name: string; @@ -18,9 +19,7 @@ export interface Tankopedia { } export const tankopedia = fetch( - context === 'bot' - ? 'https://www.blitzstars.com/bs-tankopedia.json' - : '/api/tankopedia', + withCORSProxy('https://www.blitzstars.com/bs-tankopedia.json'), ) .then(async (response) => response.json()) .then((wrapperTankopedia) => From 44f03a1fceb9a5b274909888024a09fbcfbd254c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:43:28 -0500 Subject: [PATCH 06/88] fix: cors url --- src/core/blitz/getRatingsInfo.ts | 9 +++------ src/core/blitzkrieg/withCORSProxy.ts | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/core/blitz/getRatingsInfo.ts b/src/core/blitz/getRatingsInfo.ts index 8ff7d4087..7980f3cbf 100644 --- a/src/core/blitz/getRatingsInfo.ts +++ b/src/core/blitz/getRatingsInfo.ts @@ -1,17 +1,14 @@ import { RatingsInfo } from '../../commands/ratings'; import { Region } from '../../constants/regions'; -import { context } from '../blitzkrieg/context'; import { withCORSProxy } from '../blitzkrieg/withCORSProxy'; import regionToRegionSubdomain from './regionToRegionSubdomain'; export default async function getRatingsInfo(region: Region) { const response = await fetch( withCORSProxy( - context === 'bot' - ? `https://${regionToRegionSubdomain( - region, - )}.wotblitz.com/en/api/rating-leaderboards/season/` - : `/api/ratings/info/${region}`, + `https://${regionToRegionSubdomain( + region, + )}.wotblitz.com/en/api/rating-leaderboards/season/`, ), ); diff --git a/src/core/blitzkrieg/withCORSProxy.ts b/src/core/blitzkrieg/withCORSProxy.ts index d000ce271..cce2d45ad 100644 --- a/src/core/blitzkrieg/withCORSProxy.ts +++ b/src/core/blitzkrieg/withCORSProxy.ts @@ -2,6 +2,6 @@ import { context } from './context'; export function withCORSProxy(url: string) { return context === 'website' - ? `https://corsproxy.wotblitz.com/?${encodeURIComponent(url)}` + ? `https://corsproxy.io/?${encodeURIComponent(url)}` : url; } From d1ebb7943282d2ad2b553858d946f78e91e08f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:56:06 -0500 Subject: [PATCH 07/88] feat: simpler non-user error messaging --- .../handlers/chatInputCommand.ts | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/events/interactionCreate/handlers/chatInputCommand.ts b/src/events/interactionCreate/handlers/chatInputCommand.ts index dbe76ff67..12fabf78f 100644 --- a/src/events/interactionCreate/handlers/chatInputCommand.ts +++ b/src/events/interactionCreate/handlers/chatInputCommand.ts @@ -47,25 +47,39 @@ export default async function handleChatInputCommand( await interaction.editReply(reply); } catch (error) { - const actionRow = new ActionRowBuilder().addComponents( - new ButtonBuilder() - .setLabel('Get Help on Discord Server') - .setURL('https://discord.gg/nDt7AjGJQH') - .setStyle(ButtonStyle.Link), - ); + const components = [ + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setLabel('Get Help on Discord Server') + .setURL('https://discord.gg/nDt7AjGJQH') + .setStyle(ButtonStyle.Link), + ), + ]; if (!(error instanceof UserError)) { console.error(interaction.commandName, error); - } - await interaction.editReply({ - embeds: [ - embedNegative( - (error as Error).message, - `${(error as Error).cause ?? 'No further information is available.'}`, - ), - ], - components: [actionRow], - }); + interaction.editReply({ + embeds: [ + embedNegative( + (error as Error).message, + `${ + (error as Error).cause ?? 'No further information is available.' + }`, + ), + ], + components, + }); + } else { + interaction.editReply({ + embeds: [ + embedNegative( + 'Blitzkrieg ran into an error!', + "We're so sorry about this. Feel free to [join the official Discord server](https://discord.gg/nDt7AjGJQH) to get help.", + ), + ], + components, + }); + } } } From f02db2a2d8de047f6532f276935fd8e5b8a4227c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 31 Oct 2023 12:35:32 -0500 Subject: [PATCH 08/88] feat: data driven latest archived season number --- .../archives/[region]/[season]/route.ts | 12 +++ .../latest-archived-season-number/route.ts | 6 ++ src/app/tools/ratings/page.tsx | 10 +- src/commands/ratings.tsx | 102 ++++++------------ .../blitz/getLatestArchivedSeasonNumber.ts | 8 -- src/core/blitzkrieg/context.ts | 4 +- .../getArchivedLatestSeasonNumber.ts | 25 +++++ .../getArchivedLatestSeasonNumberAPI.ts | 18 ++++ src/core/blitzkrieg/getArchivedRatingsInfo.ts | 12 +-- .../getArchivedRatingsLeaderboard.ts | 38 +++++++ ... getArchivedRatingsMidnightLeaderboard.ts} | 2 +- src/core/blitzstars/tankAverages.ts | 2 +- src/core/blitzstars/tankopedia.ts | 2 +- 13 files changed, 145 insertions(+), 96 deletions(-) create mode 100644 src/app/api/ratings/archives/[region]/[season]/route.ts create mode 100644 src/app/api/ratings/latest-archived-season-number/route.ts delete mode 100644 src/core/blitz/getLatestArchivedSeasonNumber.ts create mode 100644 src/core/blitzkrieg/getArchivedLatestSeasonNumber.ts create mode 100644 src/core/blitzkrieg/getArchivedLatestSeasonNumberAPI.ts create mode 100644 src/core/blitzkrieg/getArchivedRatingsLeaderboard.ts rename src/core/blitzkrieg/{getMidnightLeaderboard.ts => getArchivedRatingsMidnightLeaderboard.ts} (94%) diff --git a/src/app/api/ratings/archives/[region]/[season]/route.ts b/src/app/api/ratings/archives/[region]/[season]/route.ts new file mode 100644 index 000000000..01dd9cb31 --- /dev/null +++ b/src/app/api/ratings/archives/[region]/[season]/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server'; +import { Region } from '../../../../../../constants/regions'; +import { getArchivedRatingsLeaderboard } from '../../../../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; + +export async function GET( + request: Request, + { params }: { params: { region: Region; season: number } }, +) { + return NextResponse.json( + await getArchivedRatingsLeaderboard(params.region, params.season), + ); +} diff --git a/src/app/api/ratings/latest-archived-season-number/route.ts b/src/app/api/ratings/latest-archived-season-number/route.ts new file mode 100644 index 000000000..241f9777c --- /dev/null +++ b/src/app/api/ratings/latest-archived-season-number/route.ts @@ -0,0 +1,6 @@ +import { NextResponse } from 'next/server'; +import { getArchivedLatestSeasonNumber } from '../../../../core/blitzkrieg/getArchivedLatestSeasonNumber'; + +export async function GET() { + return NextResponse.json(await getArchivedLatestSeasonNumber()); +} diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 349db2d4a..7b222fcdb 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -7,11 +7,11 @@ import { useEffect, useState } from 'react'; import { RatingsInfo } from '../../../commands/ratings'; import PageWrapper from '../../../components/PageWrapper'; import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; +import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; import { FIRST_ARCHIVED_RATINGS_SEASON, - getLatestArchivedSeasonNumber, -} from '../../../core/blitz/getLatestArchivedSeasonNumber'; -import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; + getArchivedLatestSeasonNumberAPI, +} from '../../../core/blitzkrieg/getArchivedLatestSeasonNumberAPI'; import { useRatings } from '../../../stores/ratings'; export default function Page() { @@ -27,8 +27,8 @@ export default function Page() { useEffect(() => { (async () => { const [newRatingsInfo, newLatestArchivedSeasonNumber] = await Promise.all( - [getRatingsInfo(ratings.region), getLatestArchivedSeasonNumber()], - ); + [getRatingsInfo(ratings.region), getArchivedLatestSeasonNumberAPI()], + ); setRatingsInfo(newRatingsInfo); setLatestArchivedSeasonNumber(newLatestArchivedSeasonNumber); diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index 18c4f4504..ac3bb9d25 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -4,6 +4,7 @@ import { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder, } from 'discord.js'; +import { range } from 'lodash'; import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard'; import * as Leaderboard from '../components/Leaderboard'; import TitleBar from '../components/TitleBar'; @@ -13,11 +14,11 @@ import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../core/blitz/getRatingsInfo'; import regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain'; +import { getArchivedLatestSeasonNumber } from '../core/blitzkrieg/getArchivedLatestSeasonNumber'; +import { FIRST_ARCHIVED_RATINGS_SEASON } from '../core/blitzkrieg/getArchivedLatestSeasonNumberAPI'; import getArchivedRatingsInfo from '../core/blitzkrieg/getArchivedRatingsInfo'; -import getMidnightLeaderboard, { - DATABASE_REPO, -} from '../core/blitzkrieg/getMidnightLeaderboard'; -import { octokit } from '../core/blitzkrieg/octokit'; +import { getArchivedRatingsLeaderboard } from '../core/blitzkrieg/getArchivedRatingsLeaderboard'; +import getArchivedRatingsMidnightLeaderboard from '../core/blitzkrieg/getArchivedRatingsMidnightLeaderboard'; import { UserError } from '../core/blitzkrieg/userError'; import addRegionChoices from '../core/discord/addRegionChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices'; @@ -178,23 +179,17 @@ const noOngoingSeason = embedNegative( export const ratingsCommand = new Promise( async (resolve) => { - const seasonChoices = await octokit.repos - .getContent({ - ...DATABASE_REPO, - path: 'com/ratings', - }) - .then(({ data }) => { - if (!Array.isArray(data)) { - throw new Error('Archived ratings data is malformed'); - } - return data.map( - (folder) => - ({ - name: `Season ${folder.name}`, - value: folder.name, - }) satisfies APIApplicationCommandOptionChoice, - ); - }); + const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber(); + const seasonChoices = range( + FIRST_ARCHIVED_RATINGS_SEASON, + latestArchivedSeasonNumber + 1, + ).map( + (number) => + ({ + name: `Season ${number}`, + value: `${number}`, + }) satisfies APIApplicationCommandOptionChoice, + ); const command = new SlashCommandBuilder() .setName('ratings') @@ -262,7 +257,7 @@ export const ratingsCommand = new Promise( const region = interaction.options.getString('region') as Region; regionRatingsInfo = await (subcommandGroup === 'current' ? getRatingsInfo(region) - : getArchivedRatingsInfo(season!, region)); + : getArchivedRatingsInfo(region, season!)); if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; @@ -285,19 +280,8 @@ export const ratingsCommand = new Promise( }) satisfies SimplifiedPlayer, ), ) - : await octokit.repos - .getContent({ - ...DATABASE_REPO, - path: `${region}/ratings/${season}/latest.json`, - }) - .then(async ({ data }) => { - if (Array.isArray(data) || data.type !== 'file') { - throw new Error('Archived ratings data is malformed'); - } - - const players = (await fetch(data.download_url!).then( - (response) => response.json(), - )) as BlitzkriegRatingsLeaderboard; + : await getArchivedRatingsLeaderboard(region, season!).then( + async (players) => { const firstPlayerIndex = leagueIndex === 0 ? 0 @@ -328,15 +312,16 @@ export const ratingsCommand = new Promise( `Unknown Player ${player.id}`, }) satisfies SimplifiedPlayer, ); - }); + }, + ); const leagueInfo = regionRatingsInfo.leagues[leagueIndex]; midnightLeaderboard = subcommandGroup === 'current' - ? await getMidnightLeaderboard( + ? await getArchivedRatingsMidnightLeaderboard( region, regionRatingsInfo.current_season, ) - : await getMidnightLeaderboard(region, season!); + : await getArchivedRatingsMidnightLeaderboard(region, season!); players = result; playersBefore = result ? result[0].position - 1 : 0; @@ -354,21 +339,7 @@ export const ratingsCommand = new Promise( regionRatingsInfo = subcommandGroup === 'current' ? await getRatingsInfo(region) - : await octokit.repos - .getContent({ - ...DATABASE_REPO, - path: `${region}/ratings/${season}/info.json`, - }) - .then(async ({ data }) => { - if (Array.isArray(data) || data.type !== 'file') { - throw new Error('Archived ratings info is malformed'); - } - - const response = fetch(data.download_url!); - return (await response.then((response) => - response.json(), - )) as RatingsInfo; - }); + : await getArchivedRatingsInfo(region, season!); if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; @@ -397,22 +368,8 @@ export const ratingsCommand = new Promise( }) satisfies SimplifiedPlayer, ), ) - : await octokit.repos - .getContent({ - ...DATABASE_REPO, - path: `${region}/ratings/${season}/latest.json`, - }) - .then(async ({ data }) => { - if (Array.isArray(data) || data.type !== 'file') { - throw new Error( - 'Archived ratings latest data is malformed', - ); - } - - const response = fetch(data.download_url!); - const players = (await response.then((response) => - response.json(), - )) as BlitzkriegRatingsLeaderboard; + : await getArchivedRatingsLeaderboard(region, season!).then( + async (players) => { const playerIndex = players.findIndex( (player) => player.id === id, ); @@ -445,14 +402,15 @@ export const ratingsCommand = new Promise( clan: clanData[player.id]!.clan?.tag, nickname: clanData[player.id]!.account_name, })); - }); + }, + ); midnightLeaderboard = subcommandGroup === 'current' - ? await getMidnightLeaderboard( + ? await getArchivedRatingsMidnightLeaderboard( region, regionRatingsInfo.current_season, ) - : await getMidnightLeaderboard(region, season!); + : await getArchivedRatingsMidnightLeaderboard(region, season!); if (clan && neighbors) titleNameDiscriminator = `[${clan.tag}]`; diff --git a/src/core/blitz/getLatestArchivedSeasonNumber.ts b/src/core/blitz/getLatestArchivedSeasonNumber.ts deleted file mode 100644 index 40cf7deac..000000000 --- a/src/core/blitz/getLatestArchivedSeasonNumber.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * The first every season that Blitzkrieg tracked :) - */ -export const FIRST_ARCHIVED_RATINGS_SEASON = 49; - -export async function getLatestArchivedSeasonNumber() { - return 51; -} diff --git a/src/core/blitzkrieg/context.ts b/src/core/blitzkrieg/context.ts index 07ba4b209..7d57bd9d7 100644 --- a/src/core/blitzkrieg/context.ts +++ b/src/core/blitzkrieg/context.ts @@ -1,4 +1,4 @@ -type Context = 'website' | 'bot'; +type Context = 'website' | 'server'; export const context: Context = - typeof window === 'undefined' ? 'bot' : 'website'; + typeof window === 'undefined' ? 'server' : 'website'; diff --git a/src/core/blitzkrieg/getArchivedLatestSeasonNumber.ts b/src/core/blitzkrieg/getArchivedLatestSeasonNumber.ts new file mode 100644 index 000000000..e3ba0cf51 --- /dev/null +++ b/src/core/blitzkrieg/getArchivedLatestSeasonNumber.ts @@ -0,0 +1,25 @@ +import { DATABASE_REPO } from './getArchivedRatingsMidnightLeaderboard'; +import { octokit } from './octokit'; + +let cachedLatestArchivedSeasonNumber: number | null = null; + +export async function getArchivedLatestSeasonNumber() { + if (cachedLatestArchivedSeasonNumber) { + return cachedLatestArchivedSeasonNumber; + } + + const { data } = await octokit.repos.getContent({ + ...DATABASE_REPO, + path: 'com/ratings', + }); + + if (!Array.isArray(data)) { + throw new Error('Archived ratings data is malformed'); + } + + cachedLatestArchivedSeasonNumber = Math.max( + ...data.map(({ name }) => parseInt(name)), + ); + + return cachedLatestArchivedSeasonNumber; +} diff --git a/src/core/blitzkrieg/getArchivedLatestSeasonNumberAPI.ts b/src/core/blitzkrieg/getArchivedLatestSeasonNumberAPI.ts new file mode 100644 index 000000000..45671cf13 --- /dev/null +++ b/src/core/blitzkrieg/getArchivedLatestSeasonNumberAPI.ts @@ -0,0 +1,18 @@ +let cachedLatestArchivedSeasonNumber: number | null = null; + +/** + * The first every season that Blitzkrieg tracked :) + */ +export const FIRST_ARCHIVED_RATINGS_SEASON = 49; + +export async function getArchivedLatestSeasonNumberAPI() { + if (cachedLatestArchivedSeasonNumber) { + return cachedLatestArchivedSeasonNumber; + } + + const response = await fetch(`/api/ratings/latest-archived-season-number`); + const number = parseInt(await response.text()); + cachedLatestArchivedSeasonNumber = number; + + return cachedLatestArchivedSeasonNumber; +} diff --git a/src/core/blitzkrieg/getArchivedRatingsInfo.ts b/src/core/blitzkrieg/getArchivedRatingsInfo.ts index 3a1e3157a..76f85908b 100644 --- a/src/core/blitzkrieg/getArchivedRatingsInfo.ts +++ b/src/core/blitzkrieg/getArchivedRatingsInfo.ts @@ -1,9 +1,9 @@ import { RatingsInfo } from '../../commands/ratings'; import { Region } from '../../constants/regions'; -import { DATABASE_REPO } from './getMidnightLeaderboard'; +import { DATABASE_REPO } from './getArchivedRatingsMidnightLeaderboard'; import { octokit } from './octokit'; -const ARCHIVED_RATINGS_CACHE: Record< +const INFO_CACHE: Record< Region, Record< number, @@ -18,11 +18,11 @@ const ARCHIVED_RATINGS_CACHE: Record< }; export default async function getArchivedRatingsInfo( - season: number, region: Region, + season: number, ) { - if (ARCHIVED_RATINGS_CACHE[region][season]) { - return ARCHIVED_RATINGS_CACHE[region][season]; + if (INFO_CACHE[region][season]) { + return INFO_CACHE[region][season]; } const { data } = await octokit.repos.getContent({ @@ -38,7 +38,7 @@ export default async function getArchivedRatingsInfo( detail: undefined; }; - ARCHIVED_RATINGS_CACHE[region][season] = jsonContent; + INFO_CACHE[region][season] = jsonContent; return jsonContent; } diff --git a/src/core/blitzkrieg/getArchivedRatingsLeaderboard.ts b/src/core/blitzkrieg/getArchivedRatingsLeaderboard.ts new file mode 100644 index 000000000..c253ea094 --- /dev/null +++ b/src/core/blitzkrieg/getArchivedRatingsLeaderboard.ts @@ -0,0 +1,38 @@ +import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard'; +import { Region } from '../../constants/regions'; +import { DATABASE_REPO } from './getArchivedRatingsMidnightLeaderboard'; +import { octokit } from './octokit'; + +const LEADERBOARD_CACHE: Record< + Region, + Record +> = { + asia: {}, + eu: {}, + com: {}, +}; + +export async function getArchivedRatingsLeaderboard( + region: Region, + season: number, +) { + if (LEADERBOARD_CACHE[region][season]) { + return LEADERBOARD_CACHE[region][season]; + } + + const { data } = await octokit.repos.getContent({ + ...DATABASE_REPO, + path: `${region}/ratings/${season}/latest.json`, + }); + + if (Array.isArray(data) || data.type !== 'file') { + throw new Error('Archived ratings data is malformed'); + } + + const response = await fetch(data.download_url!); + const json = (await response.json()) as BlitzkriegRatingsLeaderboard; + + LEADERBOARD_CACHE[region][season] = json; + + return json; +} diff --git a/src/core/blitzkrieg/getMidnightLeaderboard.ts b/src/core/blitzkrieg/getArchivedRatingsMidnightLeaderboard.ts similarity index 94% rename from src/core/blitzkrieg/getMidnightLeaderboard.ts rename to src/core/blitzkrieg/getArchivedRatingsMidnightLeaderboard.ts index 17794025d..392169365 100644 --- a/src/core/blitzkrieg/getMidnightLeaderboard.ts +++ b/src/core/blitzkrieg/getArchivedRatingsMidnightLeaderboard.ts @@ -13,7 +13,7 @@ const MIDNIGHT_LEADERBOARD_CACHE: Record< asia: {}, }; -export default async function getMidnightLeaderboard( +export default async function getArchivedRatingsMidnightLeaderboard( region: Region, season: number, ) { diff --git a/src/core/blitzstars/tankAverages.ts b/src/core/blitzstars/tankAverages.ts index 336bb5bcf..a2d1bf8ae 100644 --- a/src/core/blitzstars/tankAverages.ts +++ b/src/core/blitzstars/tankAverages.ts @@ -90,7 +90,7 @@ export const tankAverages = fetch( ) .then((response) => response.json()) .then((json) => { - if (context === 'bot') { + if (context === 'server') { const partialTankAverages: TankAverages = {}; (json as IndividualTankAverage[]).forEach((individualTankAverage) => { diff --git a/src/core/blitzstars/tankopedia.ts b/src/core/blitzstars/tankopedia.ts index f918ff7d4..3d12d61cb 100644 --- a/src/core/blitzstars/tankopedia.ts +++ b/src/core/blitzstars/tankopedia.ts @@ -23,7 +23,7 @@ export const tankopedia = fetch( ) .then(async (response) => response.json()) .then((wrapperTankopedia) => - context === 'bot' + context === 'server' ? (wrapperTankopedia as { data: Tankopedia }).data : (wrapperTankopedia as Tankopedia), ); From 3d9872a671dbc6fd00ea500a703ef8157b0459fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 31 Oct 2023 12:41:27 -0500 Subject: [PATCH 09/88] feat: patient ratings leaderboard building --- scripts/buildRatingsLeaderboard.ts | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/scripts/buildRatingsLeaderboard.ts b/scripts/buildRatingsLeaderboard.ts index 72e850404..07c477fc9 100644 --- a/scripts/buildRatingsLeaderboard.ts +++ b/scripts/buildRatingsLeaderboard.ts @@ -13,11 +13,26 @@ import commitMultipleFiles from './commitMultipleFiles.js'; * Central European Time (UTC+1): use 0 23 * * *. * Central Asia Standard Time (UTC+7): use 0 17 * * *. * - * na/ratings/49/info.json: season info like rewards, start & end time, etc. - * na/ratings/49/latest.json: the absolute latest leaderboard recorded at UTC+0 midnight - * na/ratings/49/midnight.json: last midnight's leaderboard relative to the region + * [region]/ratings/[season]/info.json: season info like rewards, start & end time, etc. + * [region]/ratings/[season]/latest.json: the absolute latest leaderboard recorded at UTC+0 midnight + * [region]/ratings/[season]/midnight.json: last midnight's leaderboard relative to the region */ +async function patientFetch(url: string) { + let patientResponse: Response; + + while (patientResponse! === undefined) { + try { + patientResponse = await fetch(url); + } catch (error) { + console.warn(`Failed to fetch ${url}; retrying in 1 minute...`); + await new Promise((resolve) => setTimeout(resolve, 1 * 60 * 1000)); + } + } + + return patientResponse; +} + export type BlitzkriegRatingsLeaderboard = { id: number; score: number }[]; const publish = argv.includes('--publish'); @@ -27,7 +42,7 @@ const server = argv.find((arg) => arg.startsWith('--region='))?.split('=')[1]; if (!server) throw new Error('Region parameter not specified'); const NEIGHBORS = 1000; -const PLAYERS = await fetch( +const PLAYERS = await patientFetch( `https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`, ) .then((response) => response.json() as Promise) @@ -42,7 +57,7 @@ const leaderboard: BlitzkriegRatingsLeaderboard = []; let registered = 0; async function branchFromPlayer(id: number) { - const { neighbors } = await fetch( + const { neighbors } = await patientFetch( `https://${server}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}`, ).then((response) => response.json() as Promise); @@ -73,7 +88,7 @@ async function branchFromPlayer(id: number) { } async function branchFromLeague(id: number) { - const { result } = await fetch( + const { result } = await patientFetch( `https://${server}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`, ).then((response) => response.json() as Promise<{ result: RatingsPlayer[] }>); @@ -111,7 +126,7 @@ const leaderboardJSON = JSON.stringify(leaderboard); if (publish) { const octokit = new Octokit({ auth: env.GH_TOKEN }); - const info = await fetch( + const info = await patientFetch( `https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`, ).then( (response) => From 3245284eccff0d16cb8b2f8e71e66691f62934ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 31 Oct 2023 12:58:24 -0500 Subject: [PATCH 10/88] feat: website apis --- .../archives/[region]/[season]/info/route.ts | 12 ++++++ .../[region]/[season]/{ => latest}/route.ts | 4 +- src/app/tools/ratings/page.tsx | 24 +++++------ .../blitzkrieg/getArchivedRatingsInfoAPI.ts | 40 +++++++++++++++++++ 4 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 src/app/api/ratings/archives/[region]/[season]/info/route.ts rename src/app/api/ratings/archives/[region]/[season]/{ => latest}/route.ts (73%) create mode 100644 src/core/blitzkrieg/getArchivedRatingsInfoAPI.ts diff --git a/src/app/api/ratings/archives/[region]/[season]/info/route.ts b/src/app/api/ratings/archives/[region]/[season]/info/route.ts new file mode 100644 index 000000000..912ffe7a3 --- /dev/null +++ b/src/app/api/ratings/archives/[region]/[season]/info/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server'; +import { Region } from '../../../../../../../constants/regions'; +import getArchivedRatingsInfo from '../../../../../../../core/blitzkrieg/getArchivedRatingsInfo'; + +export async function GET( + request: Request, + { params }: { params: { region: Region; season: number } }, +) { + return NextResponse.json( + await getArchivedRatingsInfo(params.region, params.season), + ); +} diff --git a/src/app/api/ratings/archives/[region]/[season]/route.ts b/src/app/api/ratings/archives/[region]/[season]/latest/route.ts similarity index 73% rename from src/app/api/ratings/archives/[region]/[season]/route.ts rename to src/app/api/ratings/archives/[region]/[season]/latest/route.ts index 01dd9cb31..1dede8e2a 100644 --- a/src/app/api/ratings/archives/[region]/[season]/route.ts +++ b/src/app/api/ratings/archives/[region]/[season]/latest/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from 'next/server'; -import { Region } from '../../../../../../constants/regions'; -import { getArchivedRatingsLeaderboard } from '../../../../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; +import { Region } from '../../../../../../../constants/regions'; +import { getArchivedRatingsLeaderboard } from '../../../../../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; export async function GET( request: Request, diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 7b222fcdb..628d7b8b8 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -12,6 +12,7 @@ import { FIRST_ARCHIVED_RATINGS_SEASON, getArchivedLatestSeasonNumberAPI, } from '../../../core/blitzkrieg/getArchivedLatestSeasonNumberAPI'; +import getArchivedRatingsInfoAPI from '../../../core/blitzkrieg/getArchivedRatingsInfoAPI'; import { useRatings } from '../../../stores/ratings'; export default function Page() { @@ -24,22 +25,19 @@ export default function Page() { // null being the latest season const [season, setSeason] = useState(null); + useEffect(() => { + getArchivedLatestSeasonNumberAPI().then(setLatestArchivedSeasonNumber); + }, []); + useEffect(() => { (async () => { - const [newRatingsInfo, newLatestArchivedSeasonNumber] = await Promise.all( - [getRatingsInfo(ratings.region), getArchivedLatestSeasonNumberAPI()], - ); - - setRatingsInfo(newRatingsInfo); - setLatestArchivedSeasonNumber(newLatestArchivedSeasonNumber); - - setSeason( - newRatingsInfo.detail == undefined - ? null - : newLatestArchivedSeasonNumber, - ); + if (season === null) { + setRatingsInfo(await getRatingsInfo(ratings.region)); + } else { + setRatingsInfo(await getArchivedRatingsInfoAPI(ratings.region, season)); + } })(); - }, [ratings.region]); + }, [season]); return ( diff --git a/src/core/blitzkrieg/getArchivedRatingsInfoAPI.ts b/src/core/blitzkrieg/getArchivedRatingsInfoAPI.ts new file mode 100644 index 000000000..89cc631c5 --- /dev/null +++ b/src/core/blitzkrieg/getArchivedRatingsInfoAPI.ts @@ -0,0 +1,40 @@ +import { RatingsInfo } from '../../commands/ratings'; +import { Region } from '../../constants/regions'; + +const INFO_CACHE: Record< + Region, + Record< + number, + RatingsInfo & { + detail: undefined; + } + > +> = { + com: {}, + eu: {}, + asia: {}, +}; + +export default async function getArchivedRatingsInfoAPI( + region: Region, + season: number, +) { + if (INFO_CACHE[region][season]) { + return INFO_CACHE[region][season]; + } + + const response = await fetch( + `/api/ratings/archives/${region}/${season}/info`, + ); + const json = (await response.json()) as RatingsInfo; + + if (json.detail) { + throw new Error( + `No ratings info found for season ${season}, region ${region}`, + ); + } + + INFO_CACHE[region][season] = json; + + return json; +} From dd673f44ae1ca8bc8c9b3d13204443cbda50ed0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:15:28 -0500 Subject: [PATCH 11/88] feat: use swr --- package.json | 1 + src/app/tools/ratings/page.tsx | 40 +++++++------------ src/commands/ratings.tsx | 2 +- .../getArchivedLatestSeasonNumberAPI.ts | 18 --------- src/core/blitzkrieg/numberFetcher.ts | 6 +++ yarn.lock | 12 +++++- 6 files changed, 33 insertions(+), 46 deletions(-) delete mode 100644 src/core/blitzkrieg/getArchivedLatestSeasonNumberAPI.ts create mode 100644 src/core/blitzkrieg/numberFetcher.ts diff --git a/package.json b/package.json index 03c75bf40..21bf0b3aa 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "react-dom": "^18.2.0", "react-fuzzysort": "^1.0.1", "satori": "^0.10.8", + "swr": "^2.2.4", "vite-node": "^0.34.5", "vitepress": "^1.0.0-rc.20", "vitepress-plugin-auto-sidebar": "^1.1.0", diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 628d7b8b8..64dc09733 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -3,41 +3,31 @@ import { MagnifyingGlassIcon } from '@radix-ui/react-icons'; import { Button, Flex, Popover, Select, TextField } from '@radix-ui/themes'; import { range } from 'lodash'; -import { useEffect, useState } from 'react'; -import { RatingsInfo } from '../../../commands/ratings'; +import { useState } from 'react'; +import useSWR from 'swr'; import PageWrapper from '../../../components/PageWrapper'; import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; -import { - FIRST_ARCHIVED_RATINGS_SEASON, - getArchivedLatestSeasonNumberAPI, -} from '../../../core/blitzkrieg/getArchivedLatestSeasonNumberAPI'; import getArchivedRatingsInfoAPI from '../../../core/blitzkrieg/getArchivedRatingsInfoAPI'; +import { numberFetcher } from '../../../core/blitzkrieg/numberFetcher'; import { useRatings } from '../../../stores/ratings'; +export const FIRST_ARCHIVED_RATINGS_SEASON = 49; + export default function Page() { const ratings = useRatings(); - const [ratingsInfo, setRatingsInfo] = useState(null); - const [jumpToLeague, setJumpToLeague] = useState(0); - const [latestArchivedSeasonNumber, setLatestArchivedSeasonNumber] = useState< - number | null - >(null); // null being the latest season const [season, setSeason] = useState(null); - - useEffect(() => { - getArchivedLatestSeasonNumberAPI().then(setLatestArchivedSeasonNumber); - }, []); - - useEffect(() => { - (async () => { - if (season === null) { - setRatingsInfo(await getRatingsInfo(ratings.region)); - } else { - setRatingsInfo(await getArchivedRatingsInfoAPI(ratings.region, season)); - } - })(); - }, [season]); + const { data: ratingsInfo } = useSWR(`ratings-info-${season}`, () => + season === null + ? getRatingsInfo(ratings.region) + : getArchivedRatingsInfoAPI(ratings.region, season), + ); + const [jumpToLeague, setJumpToLeague] = useState(0); + const { data: latestArchivedSeasonNumber } = useSWR( + '/api/ratings/latest-archived-season-number', + numberFetcher, + ); return ( diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index ac3bb9d25..227e0f11f 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -6,6 +6,7 @@ import { } from 'discord.js'; import { range } from 'lodash'; import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard'; +import { FIRST_ARCHIVED_RATINGS_SEASON } from '../app/tools/ratings/page'; import * as Leaderboard from '../components/Leaderboard'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; @@ -15,7 +16,6 @@ import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../core/blitz/getRatingsInfo'; import regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain'; import { getArchivedLatestSeasonNumber } from '../core/blitzkrieg/getArchivedLatestSeasonNumber'; -import { FIRST_ARCHIVED_RATINGS_SEASON } from '../core/blitzkrieg/getArchivedLatestSeasonNumberAPI'; import getArchivedRatingsInfo from '../core/blitzkrieg/getArchivedRatingsInfo'; import { getArchivedRatingsLeaderboard } from '../core/blitzkrieg/getArchivedRatingsLeaderboard'; import getArchivedRatingsMidnightLeaderboard from '../core/blitzkrieg/getArchivedRatingsMidnightLeaderboard'; diff --git a/src/core/blitzkrieg/getArchivedLatestSeasonNumberAPI.ts b/src/core/blitzkrieg/getArchivedLatestSeasonNumberAPI.ts deleted file mode 100644 index 45671cf13..000000000 --- a/src/core/blitzkrieg/getArchivedLatestSeasonNumberAPI.ts +++ /dev/null @@ -1,18 +0,0 @@ -let cachedLatestArchivedSeasonNumber: number | null = null; - -/** - * The first every season that Blitzkrieg tracked :) - */ -export const FIRST_ARCHIVED_RATINGS_SEASON = 49; - -export async function getArchivedLatestSeasonNumberAPI() { - if (cachedLatestArchivedSeasonNumber) { - return cachedLatestArchivedSeasonNumber; - } - - const response = await fetch(`/api/ratings/latest-archived-season-number`); - const number = parseInt(await response.text()); - cachedLatestArchivedSeasonNumber = number; - - return cachedLatestArchivedSeasonNumber; -} diff --git a/src/core/blitzkrieg/numberFetcher.ts b/src/core/blitzkrieg/numberFetcher.ts new file mode 100644 index 000000000..0292a3040 --- /dev/null +++ b/src/core/blitzkrieg/numberFetcher.ts @@ -0,0 +1,6 @@ +import { Fetcher } from 'swr'; + +export const numberFetcher: Fetcher = (url) => + fetch(url) + .then((res) => res.text()) + .then(parseInt); diff --git a/yarn.lock b/yarn.lock index b8670b494..dd37d74ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2462,7 +2462,7 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -client-only@0.0.1: +client-only@0.0.1, client-only@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== @@ -4033,6 +4033,14 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +swr@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.4.tgz#03ec4c56019902fbdc904d78544bd7a9a6fa3f07" + integrity sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ== + dependencies: + client-only "^0.0.1" + use-sync-external-store "^1.2.0" + tabbable@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" @@ -4162,7 +4170,7 @@ use-sidecar@^1.1.2: detect-node-es "^1.1.0" tslib "^2.0.0" -use-sync-external-store@1.2.0: +use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== From 4550246d63485b55a0c588e1ca6d7e969feec993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:30:47 -0500 Subject: [PATCH 12/88] feat: players rendering --- src/app/tools/ratings/page.tsx | 44 ++++++++++++++++--- .../getArchivedRatingsLeaderboardAPI.ts | 29 ++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 src/core/blitzkrieg/getArchivedRatingsLeaderboardAPI.ts diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 64dc09733..db1395747 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -1,14 +1,23 @@ 'use client'; import { MagnifyingGlassIcon } from '@radix-ui/react-icons'; -import { Button, Flex, Popover, Select, TextField } from '@radix-ui/themes'; +import { + Button, + Flex, + Popover, + Select, + Text, + TextField, +} from '@radix-ui/themes'; import { range } from 'lodash'; import { useState } from 'react'; import useSWR from 'swr'; +import * as Leaderboard from '../../../components/Leaderboard'; import PageWrapper from '../../../components/PageWrapper'; import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; import getArchivedRatingsInfoAPI from '../../../core/blitzkrieg/getArchivedRatingsInfoAPI'; +import { getArchivedRatingsLeaderboardAPI } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboardAPI'; import { numberFetcher } from '../../../core/blitzkrieg/numberFetcher'; import { useRatings } from '../../../stores/ratings'; @@ -18,16 +27,26 @@ export default function Page() { const ratings = useRatings(); // null being the latest season const [season, setSeason] = useState(null); - const { data: ratingsInfo } = useSWR(`ratings-info-${season}`, () => - season === null - ? getRatingsInfo(ratings.region) - : getArchivedRatingsInfoAPI(ratings.region, season), + const { data: ratingsInfo } = useSWR( + `ratings-info-${ratings.region}-${season}`, + () => + season === null + ? getRatingsInfo(ratings.region) + : getArchivedRatingsInfoAPI(ratings.region, season), ); const [jumpToLeague, setJumpToLeague] = useState(0); const { data: latestArchivedSeasonNumber } = useSWR( '/api/ratings/latest-archived-season-number', numberFetcher, ); + const players = useSWR(`ratings-players-${ratings.region}-${season}`, () => { + if (season === null) { + // i'll deal with this later + return null; + } else { + return getArchivedRatingsLeaderboardAPI(ratings.region, season); + } + }); return ( @@ -144,6 +163,21 @@ export default function Page() { + + {players.data && ( + + {players.data.slice(0, 1000).map((player, index) => ( + + ))} + + )} + + {players.isLoading && Loading...} ); } diff --git a/src/core/blitzkrieg/getArchivedRatingsLeaderboardAPI.ts b/src/core/blitzkrieg/getArchivedRatingsLeaderboardAPI.ts new file mode 100644 index 000000000..410661277 --- /dev/null +++ b/src/core/blitzkrieg/getArchivedRatingsLeaderboardAPI.ts @@ -0,0 +1,29 @@ +import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard'; +import { Region } from '../../constants/regions'; + +const LEADERBOARD_CACHE: Record< + Region, + Record +> = { + asia: {}, + eu: {}, + com: {}, +}; + +export async function getArchivedRatingsLeaderboardAPI( + region: Region, + season: number, +) { + if (LEADERBOARD_CACHE[region][season]) { + return LEADERBOARD_CACHE[region][season]; + } + + const response = await fetch( + `/api/ratings/archives/${region}/${season}/latest`, + ); + const json = (await response.json()) as BlitzkriegRatingsLeaderboard; + + LEADERBOARD_CACHE[region][season] = json; + + return json; +} From 04ce20857e9e9e9167253f3f87e6b0d62ce10bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:32:08 -0500 Subject: [PATCH 13/88] feat: no cap --- src/app/tools/ratings/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index db1395747..b8e74a80e 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -166,7 +166,7 @@ export default function Page() { {players.data && ( - {players.data.slice(0, 1000).map((player, index) => ( + {players.data.map((player, index) => ( Date: Tue, 31 Oct 2023 15:20:37 -0500 Subject: [PATCH 14/88] feat: username display --- src/app/tools/ratings/page.css.ts | 9 + src/app/tools/ratings/page.tsx | 282 ++++++++++++++++++++---------- src/core/blitz/fetchBlitz.ts | 2 +- src/stores/ratings.ts | 24 --- 4 files changed, 204 insertions(+), 113 deletions(-) create mode 100644 src/app/tools/ratings/page.css.ts delete mode 100644 src/stores/ratings.ts diff --git a/src/app/tools/ratings/page.css.ts b/src/app/tools/ratings/page.css.ts new file mode 100644 index 000000000..057061637 --- /dev/null +++ b/src/app/tools/ratings/page.css.ts @@ -0,0 +1,9 @@ +import { style } from '@vanilla-extract/css'; + +export const noArrows = style({ + '::-webkit-progress-inner-value': { + appearance: 'none', + display: 'none', + margin: 0, + }, +}); diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index b8e74a80e..78cda33a6 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -9,53 +9,104 @@ import { Text, TextField, } from '@radix-ui/themes'; +import { produce } from 'immer'; import { range } from 'lodash'; -import { useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import useSWR from 'swr'; +import { create } from 'zustand'; import * as Leaderboard from '../../../components/Leaderboard'; import PageWrapper from '../../../components/PageWrapper'; import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; +import { getAccountInfo } from '../../../core/blitz/getAccountInfo'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; import getArchivedRatingsInfoAPI from '../../../core/blitzkrieg/getArchivedRatingsInfoAPI'; import { getArchivedRatingsLeaderboardAPI } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboardAPI'; import { numberFetcher } from '../../../core/blitzkrieg/numberFetcher'; -import { useRatings } from '../../../stores/ratings'; +import { noArrows } from './page.css'; export const FIRST_ARCHIVED_RATINGS_SEASON = 49; +const ROWS_PER_PAGE = 64; + +const useUsernameCache = create>>(() => ({ + asia: {}, + com: {}, + eu: {}, +})); +const clanCache: Record> = { + com: {}, + eu: {}, + asia: {}, +}; export default function Page() { - const ratings = useRatings(); + const usernameCache = useUsernameCache(); + const [region, setRegion] = useState('com'); // null being the latest season const [season, setSeason] = useState(null); const { data: ratingsInfo } = useSWR( - `ratings-info-${ratings.region}-${season}`, + `ratings-info-${region}-${season}`, () => season === null - ? getRatingsInfo(ratings.region) - : getArchivedRatingsInfoAPI(ratings.region, season), + ? getRatingsInfo(region) + : getArchivedRatingsInfoAPI(region, season), ); const [jumpToLeague, setJumpToLeague] = useState(0); const { data: latestArchivedSeasonNumber } = useSWR( '/api/ratings/latest-archived-season-number', numberFetcher, ); - const players = useSWR(`ratings-players-${ratings.region}-${season}`, () => { + const players = useSWR(`ratings-players-${region}-${season}`, () => { if (season === null) { // i'll deal with this later return null; } else { - return getArchivedRatingsLeaderboardAPI(ratings.region, season); + return getArchivedRatingsLeaderboardAPI(region, season); } }); + const [page, setPage] = useState(0); + const pageInput = useRef(null); + const playerSlice = players.data?.slice( + page * ROWS_PER_PAGE, + page * ROWS_PER_PAGE + ROWS_PER_PAGE, + ); + + cachePage(page - 1); + cachePage(page); + cachePage(page + 1); + + function cachePage(page: number) { + const ids = players.data + ?.slice(page * ROWS_PER_PAGE, page * ROWS_PER_PAGE + ROWS_PER_PAGE) + .map(({ id }) => id) + .filter((id) => !(id in usernameCache[region])); + + if (ids && ids.length > 0) { + getAccountInfo(region, ids).then((data) => { + data.map((player) => { + useUsernameCache.setState( + produce((draft) => { + draft[region][player.account_id] = player.nickname; + }), + ); + }); + }); + } + } + + useEffect(() => { + if (pageInput.current) pageInput.current.value = `${page + 1}`; + }, [page]); + + useEffect(() => { + setPage(0); + }, [season, region]); return ( - + - useRatings.setState({ region: value as Region }) - } + defaultValue={region} + onValueChange={(value) => setRegion(region)} > @@ -98,84 +149,139 @@ export default function Page() { - - - - - - - - - setJumpToLeague(parseInt(value))} - > - - - - {ratingsInfo?.detail === undefined && - ratingsInfo?.leagues.map(({ index, title }) => ( - - {title} - - ))} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {players.data && ( - - {players.data.map((player, index) => ( - + + + + + + + + + setJumpToLeague(parseInt(value))} + > + + + + {ratingsInfo?.detail === undefined && + ratingsInfo?.leagues.map(({ index, title }) => ( + + {title} + + ))} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Page + { + setPage( + Math.max( + 0, + Math.min( + Math.floor((players.data?.length ?? 0) / ROWS_PER_PAGE), + event.target.valueAsNumber - 1, + ), + ), + ); + }} + onKeyDown={(event) => { + if (event.key === 'Enter') { + (event.target as HTMLInputElement).blur(); + } + }} /> - ))} - - )} + + out of {Math.ceil((players.data?.length ?? 0) / ROWS_PER_PAGE)} + + + + + + + + + {playerSlice?.map((player, index) => ( + + ))} + {players.isLoading && Loading...} diff --git a/src/core/blitz/fetchBlitz.ts b/src/core/blitz/fetchBlitz.ts index 64f1ae26d..5ac7a256d 100644 --- a/src/core/blitz/fetchBlitz.ts +++ b/src/core/blitz/fetchBlitz.ts @@ -32,7 +32,7 @@ async function manageQueue() { if (data.status === 'ok') { resolve(data.data); } else { - throw new Error(`Wargaming response error status:"${data.status}"`, { + throw new Error(`Wargaming response error status: "${data.status}"`, { cause: `Message: "${data.error.message}"\nURL: "${url}"`, }); } diff --git a/src/stores/ratings.ts b/src/stores/ratings.ts deleted file mode 100644 index 4fb2f3f35..000000000 --- a/src/stores/ratings.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { create } from 'zustand'; -import { devtools, persist } from 'zustand/middleware'; -import { Region } from '../constants/regions'; - -interface Ratings { - /** - * When null, the current season is used unless there ain't one in which case - * we do whatever lol - */ - region: Region; -} - -export const useRatings = create()( - devtools( - persist( - (set) => ({ - region: 'com', - }), - { - name: 'ratings', - }, - ), - ), -); From 8fbd1acf4a4f6055724f2b8a301a0b74899db20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 31 Oct 2023 15:24:00 -0500 Subject: [PATCH 15/88] fix: set region --- src/app/tools/ratings/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 78cda33a6..e4c18818c 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -106,7 +106,7 @@ export default function Page() { setRegion(region)} + onValueChange={(value) => setRegion(value as Region)} > From 87e23673fbfd7979b979d189cf06f3fa0673ead1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 31 Oct 2023 15:31:03 -0500 Subject: [PATCH 16/88] feat: clan cache --- src/app/tools/ratings/page.tsx | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index e4c18818c..a46a78fa2 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -18,6 +18,7 @@ import * as Leaderboard from '../../../components/Leaderboard'; import PageWrapper from '../../../components/PageWrapper'; import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; import { getAccountInfo } from '../../../core/blitz/getAccountInfo'; +import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; import getArchivedRatingsInfoAPI from '../../../core/blitzkrieg/getArchivedRatingsInfoAPI'; import { getArchivedRatingsLeaderboardAPI } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboardAPI'; @@ -32,14 +33,17 @@ const useUsernameCache = create>>(() => ({ com: {}, eu: {}, })); -const clanCache: Record> = { - com: {}, - eu: {}, - asia: {}, -}; +const useClanCache = create>>( + () => ({ + asia: {}, + com: {}, + eu: {}, + }), +); export default function Page() { const usernameCache = useUsernameCache(); + const clanCache = useClanCache(); const [region, setRegion] = useState('com'); // null being the latest season const [season, setSeason] = useState(null); @@ -90,6 +94,19 @@ export default function Page() { ); }); }); + + getClanAccountInfo(region, ids, ['clan']).then((data) => { + data.map((player) => { + useClanCache.setState( + produce((draft) => { + if (player) { + draft[region][player.account_id] = + player.clan?.tag ?? undefined; + } + }), + ); + }); + }); } } @@ -151,7 +168,7 @@ export default function Page() { - + @@ -278,6 +295,7 @@ export default function Page() { } position={page * ROWS_PER_PAGE + index + 1} score={player.score} + clan={clanCache[region][player.id]} key={player.id} /> ))} From dfba8bd15f7e690e6890655a0cbcbadff1daeded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 31 Oct 2023 15:32:35 -0500 Subject: [PATCH 17/88] feat: better blitz fetch error management --- src/core/blitz/fetchBlitz.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/core/blitz/fetchBlitz.ts b/src/core/blitz/fetchBlitz.ts index 5ac7a256d..566f1715b 100644 --- a/src/core/blitz/fetchBlitz.ts +++ b/src/core/blitz/fetchBlitz.ts @@ -17,7 +17,7 @@ async function manageQueue() { if (queue.length > 0 && inProgress < MAX_CALLS_PER_SECOND) { inProgress++; - setTimeout(() => { + const timeout = setTimeout(() => { inProgress--; manageQueue(); }, 1000); @@ -32,6 +32,10 @@ async function manageQueue() { if (data.status === 'ok') { resolve(data.data); } else { + clearTimeout(timeout); + inProgress--; + manageQueue(); + throw new Error(`Wargaming response error status: "${data.status}"`, { cause: `Message: "${data.error.message}"\nURL: "${url}"`, }); From d991be4934b069eb51b68d13da5f7f99fb10f044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 1 Nov 2023 11:21:30 -0500 Subject: [PATCH 18/88] fix: user error now results in user friendly message --- .../handlers/chatInputCommand.ts | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/events/interactionCreate/handlers/chatInputCommand.ts b/src/events/interactionCreate/handlers/chatInputCommand.ts index 12fabf78f..fbad9dc6b 100644 --- a/src/events/interactionCreate/handlers/chatInputCommand.ts +++ b/src/events/interactionCreate/handlers/chatInputCommand.ts @@ -20,20 +20,20 @@ export default async function handleChatInputCommand( await interaction.deferReply(); + if (registry.inPreview && interaction.guildId !== discord.tres_guild_id) { + interaction.editReply({ + embeds: [ + embedWarning( + `\`/${registry.command.name}\` is in Public Preview`, + '[Join the official Discord server](https://discord.gg/nDt7AjGJQH) to gain early access to this command before it is released to the public.', + ), + ], + }); + + return; + } + try { - if (registry.inPreview && interaction.guildId !== discord.tres_guild_id) { - interaction.editReply({ - embeds: [ - embedWarning( - `\`/${registry.command.name}\` is in Public Preview`, - '[Join the official Discord server](https://discord.gg/nDt7AjGJQH) to gain early access to this command before it is released to the public.', - ), - ], - }); - - return; - } - const returnable = await registry.handler(interaction); if (registry.handlesInteraction) return; @@ -56,21 +56,20 @@ export default async function handleChatInputCommand( ), ]; - if (!(error instanceof UserError)) { - console.error(interaction.commandName, error); - + if (error instanceof UserError) { interaction.editReply({ embeds: [ embedNegative( - (error as Error).message, - `${ - (error as Error).cause ?? 'No further information is available.' - }`, + error.message, + (error.cause as string | undefined) ?? + 'No further information is available.', ), ], components, }); } else { + console.error(interaction.commandName, error); + interaction.editReply({ embeds: [ embedNegative( From 882e61ff444de264a0c2d24cef952e3cd06aaab5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 1 Nov 2023 11:27:41 -0500 Subject: [PATCH 19/88] feat: isolated first archived season number --- src/app/tools/ratings/page.tsx | 2 +- src/commands/ratings.tsx | 2 +- src/constants/ratings.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/constants/ratings.ts diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index a46a78fa2..0c1c4c01f 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -16,6 +16,7 @@ import useSWR from 'swr'; import { create } from 'zustand'; import * as Leaderboard from '../../../components/Leaderboard'; import PageWrapper from '../../../components/PageWrapper'; +import { FIRST_ARCHIVED_RATINGS_SEASON } from '../../../constants/ratings'; import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; import { getAccountInfo } from '../../../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo'; @@ -25,7 +26,6 @@ import { getArchivedRatingsLeaderboardAPI } from '../../../core/blitzkrieg/getAr import { numberFetcher } from '../../../core/blitzkrieg/numberFetcher'; import { noArrows } from './page.css'; -export const FIRST_ARCHIVED_RATINGS_SEASON = 49; const ROWS_PER_PAGE = 64; const useUsernameCache = create>>(() => ({ diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index 227e0f11f..3961de1a4 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -6,10 +6,10 @@ import { } from 'discord.js'; import { range } from 'lodash'; import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard'; -import { FIRST_ARCHIVED_RATINGS_SEASON } from '../app/tools/ratings/page'; import * as Leaderboard from '../components/Leaderboard'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; +import { FIRST_ARCHIVED_RATINGS_SEASON } from '../constants/ratings'; import { REGION_NAMES_SHORT, Region } from '../constants/regions'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; diff --git a/src/constants/ratings.ts b/src/constants/ratings.ts new file mode 100644 index 000000000..ec9ead703 --- /dev/null +++ b/src/constants/ratings.ts @@ -0,0 +1 @@ +export const FIRST_ARCHIVED_RATINGS_SEASON = 49; From 3ea77f5899ad84461e2d3399d15240a7e0ea78a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 1 Nov 2023 11:45:19 -0500 Subject: [PATCH 20/88] feat: autocomplete size limits --- src/core/blitz/searchClansAcrossRegions.ts | 36 +++++++++++--------- src/core/blitz/searchPlayersAcrossRegions.ts | 4 +-- src/core/discord/autocompleteClan.ts | 33 ++++++++++++------ src/core/discord/autocompleteTanks.ts | 28 +++++++++++---- src/core/discord/autocompleteUsername.ts | 32 ++++++++++++----- 5 files changed, 88 insertions(+), 45 deletions(-) diff --git a/src/core/blitz/searchClansAcrossRegions.ts b/src/core/blitz/searchClansAcrossRegions.ts index 98b4cfabb..45539244b 100644 --- a/src/core/blitz/searchClansAcrossRegions.ts +++ b/src/core/blitz/searchClansAcrossRegions.ts @@ -9,43 +9,45 @@ export type ClanListWithRegion = (Clan & { export default async function searchClansAcrossRegions( search: string, - limit = 9, + limit = 25, ) { const trimmed = search.trim(); if (trimmed.length < 3 && trimmed.length > 100) return []; - const normalizedLimit = Math.round(limit / 3); + const normalizedLimit = Math.floor(limit / 3); + + // TODO: remove splice once wargaming fixes the functionality of the limit felid return ( await Promise.all([ fetchBlitz( `https://api.wotblitz.com/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`, - ).then( - (value) => - value && - value.map((account) => ({ + ).then((value) => + value + .map((account) => ({ ...account, region: 'com' as Region, - })), + })) + .splice(0, normalizedLimit), ), fetchBlitz( `https://api.wotblitz.eu/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`, - ).then( - (value) => - value && - value.map((account) => ({ + ).then((value) => + value + .map((account) => ({ ...account, region: 'eu' as Region, - })), + })) + .splice(0, normalizedLimit), ), fetchBlitz( `https://api.wotblitz.asia/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${search}&limit=${normalizedLimit}`, - ).then( - (value) => - value && - value.map((account) => ({ + ).then((value) => + value + .map((account) => ({ ...account, region: 'asia' as Region, - })), + })) + .splice(0, normalizedLimit), ), ]) ) diff --git a/src/core/blitz/searchPlayersAcrossRegions.ts b/src/core/blitz/searchPlayersAcrossRegions.ts index e8a12a17a..cfc61bb89 100644 --- a/src/core/blitz/searchPlayersAcrossRegions.ts +++ b/src/core/blitz/searchPlayersAcrossRegions.ts @@ -16,10 +16,10 @@ export const usernamePatternWithoutPosition = /[a-zA-Z0-9_]{3,24}/; export default async function searchPlayersAcrossRegions( search: string, - limit = 9, + limit = 25, ) { const trimmedSearch = search.trim(); - const normalizedLimit = Math.round(limit / 3); + const normalizedLimit = Math.floor(limit / 3); const encodedSearch = encodeURIComponent(trimmedSearch); if (usernamePattern.test(trimmedSearch)) { diff --git a/src/core/discord/autocompleteClan.ts b/src/core/discord/autocompleteClan.ts index 83b4bdc0e..bbbd9a2de 100644 --- a/src/core/discord/autocompleteClan.ts +++ b/src/core/discord/autocompleteClan.ts @@ -3,26 +3,39 @@ import { AutocompleteInteraction, CacheType, } from 'discord.js'; -import { REGION_NAMES } from '../../constants/regions'; +import { REGION_NAMES_SHORT } from '../../constants/regions'; import searchClansAcrossRegions from '../blitz/searchClansAcrossRegions'; +export const DISCORD_CHOICES_MAX_NAME_SIZE = 25; +export const OVERFLOW_SUFFIX = '…'; + export default async function autocompleteClan( interaction: AutocompleteInteraction, ) { const focusedOption = interaction.options.getFocused(true); if (focusedOption.name !== 'clan') return; if (focusedOption.value.length < 2) return interaction.respond([]); - const players = await searchClansAcrossRegions(focusedOption.value); + const clans = await searchClansAcrossRegions(focusedOption.value); await interaction.respond( - players - ? players.map( - (player) => - ({ - name: `${player.name} (${REGION_NAMES[player.region]})`, - value: `${player.region}/${player.clan_id}`, - }) satisfies ApplicationCommandOptionChoiceData, - ) + clans + ? clans.map((clan) => { + let name = `${clan.name} (${REGION_NAMES_SHORT[clan.region]})`; + + if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { + const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; + + name = `${clan.name.slice( + 0, + clan.name.length - overSize - OVERFLOW_SUFFIX.length, + )}${OVERFLOW_SUFFIX} (${REGION_NAMES_SHORT[clan.region]})`; + } + + return { + name, + value: `${clan.region}/${clan.clan_id}`, + } satisfies ApplicationCommandOptionChoiceData; + }) : [], ); } diff --git a/src/core/discord/autocompleteTanks.ts b/src/core/discord/autocompleteTanks.ts index adfc8477b..de74a907f 100644 --- a/src/core/discord/autocompleteTanks.ts +++ b/src/core/discord/autocompleteTanks.ts @@ -5,6 +5,10 @@ import { } from 'discord.js'; import { go } from 'fuzzysort'; import { TANK_NAMES_DIACRITICS } from '../blitzstars/tankopedia'; +import { + DISCORD_CHOICES_MAX_NAME_SIZE, + OVERFLOW_SUFFIX, +} from './autocompleteClan'; export default async function autocompleteTanks( interaction: AutocompleteInteraction, @@ -18,13 +22,23 @@ export default async function autocompleteTanks( go(focusedOption.value, await TANK_NAMES_DIACRITICS, { keys: ['combined'], limit: 10, - }).map( - async (item) => - ({ - name: item.obj.original, - value: `${item.obj.id}`, - }) satisfies ApplicationCommandOptionChoiceData, - ), + }).map(async (item) => { + let name = item.obj.original; + + if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { + const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; + + name = `${item.obj.original.slice( + 0, + item.obj.original.length - overSize - OVERFLOW_SUFFIX.length, + )}${OVERFLOW_SUFFIX}`; + } + + return { + name, + value: `${item.obj.id}`, + } satisfies ApplicationCommandOptionChoiceData; + }), ) : [], ); diff --git a/src/core/discord/autocompleteUsername.ts b/src/core/discord/autocompleteUsername.ts index 5160a2024..333b51624 100644 --- a/src/core/discord/autocompleteUsername.ts +++ b/src/core/discord/autocompleteUsername.ts @@ -5,6 +5,10 @@ import { } from 'discord.js'; import { REGION_NAMES_SHORT } from '../../constants/regions'; import searchPlayersAcrossRegions from '../blitz/searchPlayersAcrossRegions'; +import { + DISCORD_CHOICES_MAX_NAME_SIZE, + OVERFLOW_SUFFIX, +} from './autocompleteClan'; export default async function autocompleteUsername( interaction: AutocompleteInteraction, @@ -16,15 +20,25 @@ export default async function autocompleteUsername( try { await interaction.respond( players - ? players.map( - (player) => - ({ - name: `${player.nickname} (${ - REGION_NAMES_SHORT[player.region] - })`, - value: `${player.region}/${player.account_id}`, - }) satisfies ApplicationCommandOptionChoiceData, - ) + ? players.map((player) => { + let name = `${player.nickname} (${ + REGION_NAMES_SHORT[player.region] + })`; + + if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { + const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; + + name = `${player.nickname.slice( + 0, + player.nickname.length - overSize - OVERFLOW_SUFFIX.length, + )}${OVERFLOW_SUFFIX} (${REGION_NAMES_SHORT[player.region]})`; + } + + return { + name, + value: `${player.region}/${player.account_id}`, + } satisfies ApplicationCommandOptionChoiceData; + }) : [], ); } catch (error) {} From 322f95e42e74b470a3c05c7733ef1a70c520942d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:14:13 -0500 Subject: [PATCH 21/88] feat: rows per page --- src/app/tools/ratings/page.tsx | 48 ++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 0c1c4c01f..74b461107 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -1,6 +1,10 @@ 'use client'; -import { MagnifyingGlassIcon } from '@radix-ui/react-icons'; +import { + CaretLeftIcon, + CaretRightIcon, + MagnifyingGlassIcon, +} from '@radix-ui/react-icons'; import { Button, Flex, @@ -26,7 +30,7 @@ import { getArchivedRatingsLeaderboardAPI } from '../../../core/blitzkrieg/getAr import { numberFetcher } from '../../../core/blitzkrieg/numberFetcher'; import { noArrows } from './page.css'; -const ROWS_PER_PAGE = 64; +const ROWS_OPTIONS = [5, 10, 25, 50, 100]; const useUsernameCache = create>>(() => ({ asia: {}, @@ -42,6 +46,7 @@ const useClanCache = create>>( ); export default function Page() { + const [rowsPerPage, setRowsPerPage] = useState(ROWS_OPTIONS[2]); const usernameCache = useUsernameCache(); const clanCache = useClanCache(); const [region, setRegion] = useState('com'); @@ -70,8 +75,8 @@ export default function Page() { const [page, setPage] = useState(0); const pageInput = useRef(null); const playerSlice = players.data?.slice( - page * ROWS_PER_PAGE, - page * ROWS_PER_PAGE + ROWS_PER_PAGE, + page * rowsPerPage, + page * rowsPerPage + rowsPerPage, ); cachePage(page - 1); @@ -80,7 +85,7 @@ export default function Page() { function cachePage(page: number) { const ids = players.data - ?.slice(page * ROWS_PER_PAGE, page * ROWS_PER_PAGE + ROWS_PER_PAGE) + ?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) .map(({ id }) => id) .filter((id) => !(id in usernameCache[region])); @@ -234,14 +239,29 @@ export default function Page() { + + setRowsPerPage(parseInt(value))} + defaultValue={`${rowsPerPage}`} + > + + + + {ROWS_OPTIONS.map((size) => ( + + {size} per page + + ))} + + + - Page - out of {Math.ceil((players.data?.length ?? 0) / ROWS_PER_PAGE)} + out of {Math.ceil((players.data?.length ?? 0) / rowsPerPage)} - @@ -291,9 +310,10 @@ export default function Page() { {playerSlice?.map((player, index) => ( Date: Mon, 6 Nov 2023 23:02:26 -0600 Subject: [PATCH 22/88] feat: tankopedia extractor --- .gitignore | 3 +- .vscode/settings.json | 1 + package.json | 6 + scripts/buildTankopedia.ts | 86 ++++++++++ src/constants/nations.ts | 11 ++ yarn.lock | 331 ++++++++++++++++++++++++++++++++++++- 6 files changed, 434 insertions(+), 4 deletions(-) create mode 100644 scripts/buildTankopedia.ts create mode 100644 src/constants/nations.ts diff --git a/.gitignore b/.gitignore index bcf647cdf..11dac0cb3 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ test.* /docs/.vitepress/cache servers.csv .next/ -tsconfig.tsbuildinfo \ No newline at end of file +tsconfig.tsbuildinfo +temp/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index f9d65679c..5aa864800 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -36,6 +36,7 @@ "subnation", "tankaverages", "tankopedia", + "tankopedias", "tankstats", "tresabhi", "Unicum", diff --git a/package.json b/package.json index 21bf0b3aa..ce0f448b9 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "build:website": "next build", "build:docs": "vitepress build docs", "build:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard", + "build:tankopedia": "vite-node --script scripts/buildTankopedia", "dev:bot": "yarn build:bot && yarn start:bot", "dev:docs": "vitepress dev docs", "dev:website": "next dev", @@ -33,12 +34,14 @@ "@resvg/resvg-js": "^2.4.1", "@vanilla-extract/css": "^1.13.0", "bepaint": "^0.1.0-beta.3", + "decompress": "^4.2.1", "discord.js": "^14.13.0", "dotenv": "^16.3.1", "esbuild": "^0.19.3", "express": "^4.18.2", "fuzzysort": "^2.0.4", "immer": "^10.0.2", + "js-yaml": "^4.1.0", "lodash": "^4.17.21", "markdown-escape": "^2.0.0", "next": "^13.5.2", @@ -50,12 +53,15 @@ "vite-node": "^0.34.5", "vitepress": "^1.0.0-rc.20", "vitepress-plugin-auto-sidebar": "^1.1.0", + "xml-js": "^1.6.11", "zustand": "^4.4.1" }, "devDependencies": { "@total-typescript/ts-reset": "^0.5.1", + "@types/decompress": "^4.2.6", "@types/escape-html": "^1.0.2", "@types/express": "^4.17.18", + "@types/js-yaml": "^4.0.8", "@types/lodash": "^4.14.199", "@types/markdown-escape": "^1.1.0", "@types/node": "^20.6.5", diff --git a/scripts/buildTankopedia.ts b/scripts/buildTankopedia.ts new file mode 100644 index 000000000..a028e33b0 --- /dev/null +++ b/scripts/buildTankopedia.ts @@ -0,0 +1,86 @@ +import decompress from 'decompress'; +import { readFile, readdir, rm, writeFile } from 'fs/promises'; +import { load } from 'js-yaml'; +import { ElementCompact, xml2js } from 'xml-js'; +import { NATION_IDS } from '../src/constants/nations'; + +const REWRITE = false; +const VEHICLE_DEFS = 'assets/Data/XML/item_defs/vehicles'; +const APK_FILE = 'temp/blitz.apk'; +const STRINGS = 'assets/Data/Strings'; +const LANGUAGE = 'en'; +const DIRECTORIES_OF_INTEREST = [VEHICLE_DEFS, STRINGS]; + +if (REWRITE) { + await rm('temp/blitz', { recursive: true, force: true }); + decompress(APK_FILE, 'temp/blitz', { + filter: (file) => + DIRECTORIES_OF_INTEREST.some((dir) => file.path.startsWith(dir)), + }); +} + +interface BlitzkriegTankopedia { + id: number; + nation: string; + name: string; + name_short?: string; + is_premium: boolean; + is_collector: boolean; + tier: number; + type: string; +} + +const strings = await readFile( + `temp/blitz/${STRINGS}/${LANGUAGE}.yaml`, + 'utf-8', +).then((data) => load(data) as Record); +const tankopedia: Record = {}; +const nations = await readdir(`temp/blitz/${VEHICLE_DEFS}`); + +await Promise.all( + nations + .filter((nation) => nation !== 'common') + .map(async (nation) => { + const listXML = await readFile( + `temp/blitz/${VEHICLE_DEFS}/${nation}/list.xml`, + 'utf-8', + ); + const list = xml2js(listXML, { compact: true }) as ElementCompact; + + await Promise.all( + Object.entries(list.root).map( + async ([vehicle, vehicleData]) => { + // const vehicleXML = await readFile( + // `temp/blitz/${VEHICLE_DEFS}/${nation}/${vehicle}.xml`, + // 'utf-8', + // ).then((xml) => xml2js(xml, { compact: true }) as ElementCompact); + const nationVehicleId = parseInt(list.root[vehicle].id._text); + const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1; + // TODO: use userString property instead + const name = strings[`#${nation}_vehicles:${vehicle}`]; + const name_short = strings[`#${nation}_vehicles:${vehicle}_short`]; + const is_premium = 'gold' in vehicleData.price; + const is_collector = vehicleData.sellPrice + ? 'gold' in vehicleData.sellPrice + : false; + const tier = parseInt(vehicleData.level._text); + const tags = vehicleData.tags._text.split(' '); + const type = tags[0]; + + tankopedia[id] = { + id, + name, + name_short, + nation, + is_premium, + is_collector, + tier, + type, + }; + }, + ), + ); + }), +); + +writeFile('dist/tankopedia.json', JSON.stringify(tankopedia, null, 2)); diff --git a/src/constants/nations.ts b/src/constants/nations.ts new file mode 100644 index 000000000..f7adc2840 --- /dev/null +++ b/src/constants/nations.ts @@ -0,0 +1,11 @@ +export const NATION_IDS: Record = { + ussr: 0, + germany: 1, + usa: 2, + china: 3, + france: 4, + uk: 5, + japan: 6, + other: 7, + european: 8, +}; diff --git a/yarn.lock b/yarn.lock index dd37d74ac..d77daf632 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1882,6 +1882,13 @@ dependencies: "@types/node" "*" +"@types/decompress@^4.2.6": + version "4.2.6" + resolved "https://registry.yarnpkg.com/@types/decompress/-/decompress-4.2.6.tgz#2ae3fb360c4d27593f51293e54990b9e709477ae" + integrity sha512-ULmsMkKxU7aIGbnxP4Rz28hLdRZ4q0cdy6kcb8dx+UgDGOn+id5fibvoeTnjuolhrRM7f7TOtGADeKEk60SSsQ== + dependencies: + "@types/node" "*" + "@types/escape-html@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/escape-html/-/escape-html-1.0.2.tgz#072b7b13784fb3cee9c2450c22f36405983f5e3c" @@ -1912,6 +1919,11 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.2.tgz#a86e00bbde8950364f8e7846687259ffcd96e8c2" integrity sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg== +"@types/js-yaml@^4.0.8": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.8.tgz#7574e422d70d4a1b41f517d1d9abc61be2299a97" + integrity sha512-m6jnPk1VhlYRiLFm3f8X9Uep761f+CK8mHyS65LutH2OhmBF0BeMEjHgg05usH8PLZMWWc/BUR9RPmkvpWnyRA== + "@types/linkify-it@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.3.tgz#15a0712296c5041733c79efe233ba17ae5a7587b" @@ -2333,6 +2345,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== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + 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" @@ -2348,6 +2365,14 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== +bl@^1.0.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7" + integrity sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww== + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + body-parser@1.20.1: version "1.20.1" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" @@ -2390,11 +2415,42 @@ browserslist@^4.21.9: node-releases "^2.0.13" update-browserslist-db "^1.0.13" +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg== + +buffer-alloc@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow== + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + integrity sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer@^5.2.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + busboy@1.6.0, busboy@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" @@ -2496,6 +2552,11 @@ color-name@^1.1.4, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +commander@^2.8.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commander@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" @@ -2540,6 +2601,11 @@ cookie@0.5.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + cross-spawn@^7.0.0: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2602,6 +2668,59 @@ debug@^4.1.0, debug@^4.3.1, debug@^4.3.4: dependencies: ms "2.1.2" +decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" + integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ== + dependencies: + file-type "^5.2.0" + is-stream "^1.1.0" + tar-stream "^1.5.2" + +decompress-tarbz2@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b" + integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A== + dependencies: + decompress-tar "^4.1.0" + file-type "^6.1.0" + is-stream "^1.1.0" + seek-bzip "^1.0.5" + unbzip2-stream "^1.0.9" + +decompress-targz@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee" + integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w== + dependencies: + decompress-tar "^4.1.1" + file-type "^5.2.0" + is-stream "^1.1.0" + +decompress-unzip@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69" + integrity sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw== + dependencies: + file-type "^3.8.0" + get-stream "^2.2.0" + pify "^2.3.0" + yauzl "^2.4.2" + +decompress@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.1.tgz#007f55cc6a62c055afa37c07eb6a4ee1b773f118" + integrity sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ== + dependencies: + decompress-tar "^4.0.0" + decompress-tarbz2 "^4.0.0" + decompress-targz "^4.0.0" + decompress-unzip "^4.0.1" + graceful-fs "^4.1.10" + make-dir "^1.0.0" + pify "^2.3.0" + strip-dirs "^2.0.0" + deep-freeze@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" @@ -2730,6 +2849,13 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== +end-of-stream@^1.0.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + esbuild@0.17.6: version "0.17.6" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.6.tgz#bbccd4433629deb6e0a83860b3b61da120ba4e01" @@ -2907,11 +3033,33 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + fflate@^0.7.3: version "0.7.4" resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50" integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw== +file-type@^3.8.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" + integrity sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA== + +file-type@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" + integrity sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ== + +file-type@^6.1.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" + integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg== + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -2975,6 +3123,11 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" @@ -3010,6 +3163,14 @@ get-nonce@^1.0.0: resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== +get-stream@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" + integrity sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA== + dependencies: + object-assign "^4.0.1" + pinkie-promise "^2.0.0" + glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -3049,7 +3210,7 @@ globby@^13.1.3: merge2 "^1.4.1" slash "^4.0.0" -graceful-fs@^4.1.2: +graceful-fs@^4.1.10, graceful-fs@^4.1.2: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -3114,6 +3275,11 @@ identity-function@^1.0.0: resolved "https://registry.yarnpkg.com/identity-function/-/identity-function-1.0.0.tgz#bea1159f0985239be3ca348edf40ce2f0dd2c21d" integrity sha512-kNrgUK0qI+9qLTBidsH85HjDLpZfrrS0ElquKKe/fJFdB3D7VeKdXXEvOPDUHSHOzdZKCAAaQIWWyp0l2yq6pw== +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.1.8, ignore@^5.2.4: version "5.2.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" @@ -3129,7 +3295,7 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -inherits@2.0.4: +inherits@2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3168,6 +3334,11 @@ is-iterable@^1.1.0: resolved "https://registry.yarnpkg.com/is-iterable/-/is-iterable-1.1.1.tgz#71f9aa6f113e1d968ebe1d41cff4c8fb23a817bc" integrity sha512-EdOZCr0NsGE00Pot+x1ZFx9MJK3C6wy91geZpXwvwexDLJvA4nzYyZf7r+EIwSeVsOLDdBz7ATg9NqKTzuNYuQ== +is-natural-number@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" + integrity sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ== + is-number@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" @@ -3183,6 +3354,16 @@ is-plain-object@^5.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -3349,6 +3530,13 @@ magic-string@^0.30.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" +make-dir@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" + integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== + dependencies: + pify "^3.0.0" + map-obj@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9" @@ -3507,6 +3695,11 @@ npm-normalize-package-bin@^3.0.0: resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz#25447e32a9a7de1f51362c61a559233b89947832" integrity sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ== +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + object-inspect@^1.9.0: version "1.12.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" @@ -3618,6 +3811,11 @@ pathe@^1.1.0, pathe@^1.1.1: resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a" integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q== +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -3628,6 +3826,28 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== + pkg-types@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868" @@ -3684,6 +3904,11 @@ prisma@^5.4.1: dependencies: "@prisma/engines" "5.4.1" +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -3778,6 +4003,19 @@ read-package-json-fast@^3.0.0: json-parse-even-better-errors "^3.0.0" npm-normalize-package-bin "^3.0.0" +readable-stream@^2.3.0, readable-stream@^2.3.5: + version "2.3.8" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + regenerator-runtime@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" @@ -3812,11 +4050,16 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@5.2.1: +safe-buffer@5.2.1, safe-buffer@^5.1.1: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -3838,6 +4081,11 @@ satori@^0.10.8: postcss-value-parser "^4.2.0" yoga-wasm-web "^0.3.3" +sax@^1.2.4: + version "1.3.0" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" + integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== + scheduler@^0.23.0: version "0.23.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" @@ -3845,6 +4093,13 @@ scheduler@^0.23.0: dependencies: loose-envify "^1.1.0" +seek-bzip@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.6.tgz#35c4171f55a680916b52a07859ecf3b5857f21c4" + integrity sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ== + dependencies: + commander "^2.8.1" + semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" @@ -3987,6 +4242,13 @@ string.prototype.codepointat@^0.2.1: resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc" integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg== +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: name strip-ansi-cjs version "6.0.1" @@ -4002,6 +4264,13 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.0.1" +strip-dirs@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" + integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g== + dependencies: + is-natural-number "^4.0.1" + strip-json-comments@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-5.0.1.tgz#0d8b7d01b23848ed7dbdf4baaaa31a8250d8cfa0" @@ -4046,11 +4315,34 @@ tabbable@^6.2.0: resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== +tar-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" + integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== + dependencies: + bl "^1.0.0" + buffer-alloc "^1.2.0" + end-of-stream "^1.0.0" + fs-constants "^1.0.0" + readable-stream "^2.3.0" + to-buffer "^1.1.1" + xtend "^4.0.0" + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + tiny-inflate@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== +to-buffer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" + integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== + to-fast-properties@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" @@ -4115,6 +4407,14 @@ ufo@^1.3.0: resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.0.tgz#c92f8ac209daff607c57bbd75029e190930a0019" integrity sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw== +unbzip2-stream@^1.0.9: + version "1.4.3" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" + integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== + dependencies: + buffer "^5.2.1" + through "^2.3.8" + undici@5.22.1: version "5.22.1" resolved "https://registry.yarnpkg.com/undici/-/undici-5.22.1.tgz#877d512effef2ac8be65e695f3586922e1a57d7b" @@ -4175,6 +4475,11 @@ use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0: resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -4326,11 +4631,31 @@ ws@^8.13.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== +xml-js@^1.6.11: + version "1.6.11" + resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" + integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== + dependencies: + sax "^1.2.4" + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yauzl@^2.4.2: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 1bb83da253f3df64a6229aa5b50c08c9c0ff9c34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 10:06:15 -0600 Subject: [PATCH 23/88] feat: per-page theming --- src/app/layout.tsx | 7 +- src/app/tools/ratings/page.tsx | 144 +++++++++--------- src/app/tools/session/page.tsx | 2 +- src/commands/ratings.tsx | 6 +- src/components/Leaderboard/components/Gap.tsx | 6 +- src/components/PageWrapper.tsx | 38 +++-- 6 files changed, 106 insertions(+), 97 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2c03919e2..69c55312d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -41,12 +41,7 @@ export default function RootLayout({ children }: RootLayoutProps) { : 'url(https://i.imgur.com/PhS06NJ.png)', }} > - + {!isEmbed && } {children} diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 74b461107..6ad802b1d 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -5,14 +5,7 @@ import { CaretRightIcon, MagnifyingGlassIcon, } from '@radix-ui/react-icons'; -import { - Button, - Flex, - Popover, - Select, - Text, - TextField, -} from '@radix-ui/themes'; +import { Button, Flex, Popover, Select, TextField } from '@radix-ui/themes'; import { produce } from 'immer'; import { range } from 'lodash'; import { useEffect, useRef, useState } from 'react'; @@ -73,11 +66,15 @@ export default function Page() { } }); const [page, setPage] = useState(0); - const pageInput = useRef(null); const playerSlice = players.data?.slice( page * rowsPerPage, page * rowsPerPage + rowsPerPage, ); + const previousPage = () => setPage((page) => Math.max(page - 1, 0)); + const nextPage = () => + setPage((page) => + Math.min(page + 1, Math.floor((players.data?.length ?? 0) / rowsPerPage)), + ); cachePage(page - 1); cachePage(page); @@ -115,16 +112,51 @@ export default function Page() { } } - useEffect(() => { - if (pageInput.current) pageInput.current.value = `${page + 1}`; - }, [page]); - useEffect(() => { setPage(0); }, [season, region]); + function PageTurner() { + const pageInput = useRef(null); + + useEffect(() => { + if (pageInput.current) pageInput.current.value = `${page + 1}`; + }, [page]); + + return ( + + Page + { + setPage( + Math.max( + 0, + Math.min( + Math.floor((players.data?.length ?? 0) / rowsPerPage), + event.target.valueAsNumber - 1, + ), + ), + ); + }} + onKeyDown={(event) => { + if (event.key === 'Enter') { + (event.target as HTMLInputElement).blur(); + } + }} + /> + + out of {Math.ceil((players.data?.length ?? 0) / rowsPerPage)} + + + ); + } + return ( - + - - - Page - { - setPage( - Math.max( - 0, - Math.min( - Math.floor((players.data?.length ?? 0) / rowsPerPage), - event.target.valueAsNumber - 1, - ), - ), - ); - }} - onKeyDown={(event) => { - if (event.key === 'Enter') { - (event.target as HTMLInputElement).blur(); - } - }} - /> - - out of {Math.ceil((players.data?.length ?? 0) / rowsPerPage)} - - - - {playerSlice?.map((player, index) => ( - - ))} + {players.isLoading ? ( + + ) : ( + playerSlice?.map((player, index) => ( + + )) + )} - {players.isLoading && Loading...} + + + + + ); } diff --git a/src/app/tools/session/page.tsx b/src/app/tools/session/page.tsx index 7f9d19635..bfba6c65c 100644 --- a/src/app/tools/session/page.tsx +++ b/src/app/tools/session/page.tsx @@ -51,7 +51,7 @@ export default function Page() { }, [session]); return ( - +
diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index 3961de1a4..fc06f2322 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -474,10 +474,12 @@ export const ratingsCommand = new Promise( {items && ( {playersBefore > 0 && ( - + )} {items} - {playersAfter > 0 && } + {playersAfter > 0 && ( + + )} )} diff --git a/src/components/Leaderboard/components/Gap.tsx b/src/components/Leaderboard/components/Gap.tsx index fab9f6aba..ea99825a0 100644 --- a/src/components/Leaderboard/components/Gap.tsx +++ b/src/components/Leaderboard/components/Gap.tsx @@ -1,10 +1,10 @@ import { theme } from '../../../stitches.config'; export interface GapProps { - number: number; + message: string; } -export function Gap({ number }: GapProps) { +export function Gap({ message: number }: GapProps) { return (
- + {number.toLocaleString()} more + {number.toLocaleString()}
); diff --git a/src/components/PageWrapper.tsx b/src/components/PageWrapper.tsx index 71086349b..ecfc7a5f1 100644 --- a/src/components/PageWrapper.tsx +++ b/src/components/PageWrapper.tsx @@ -1,24 +1,32 @@ +import { Theme } from '@radix-ui/themes'; import { ComponentProps } from 'react'; +interface PageWrapperProps extends ComponentProps<'div'> { + color?: ComponentProps['accentColor']; +} + export default function PageWrapper({ style, + color, ...props -}: ComponentProps<'div'>) { +}: PageWrapperProps) { return ( -
+
+ ...style, + }} + {...props} + /> + ); } From 466f68701f49a6826e6b18657ff8c4bd7aa1dfd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 10:32:01 -0600 Subject: [PATCH 24/88] feat: archived player lookup --- src/app/tools/ratings/page.tsx | 113 ++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 23 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 6ad802b1d..d06299c75 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -5,19 +5,29 @@ import { CaretRightIcon, MagnifyingGlassIcon, } from '@radix-ui/react-icons'; -import { Button, Flex, Popover, Select, TextField } from '@radix-ui/themes'; +import { + Button, + Dialog, + Flex, + Popover, + Select, + TextField, +} from '@radix-ui/themes'; import { produce } from 'immer'; -import { range } from 'lodash'; -import { useEffect, useRef, useState } from 'react'; +import { debounce, range } from 'lodash'; +import { ChangeEvent, useEffect, useRef, useState } from 'react'; import useSWR from 'swr'; import { create } from 'zustand'; import * as Leaderboard from '../../../components/Leaderboard'; import PageWrapper from '../../../components/PageWrapper'; import { FIRST_ARCHIVED_RATINGS_SEASON } from '../../../constants/ratings'; import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; +import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID'; +import fetchBlitz from '../../../core/blitz/fetchBlitz'; import { getAccountInfo } from '../../../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; +import { AccountList } from '../../../core/blitz/searchPlayersAcrossRegions'; import getArchivedRatingsInfoAPI from '../../../core/blitzkrieg/getArchivedRatingsInfoAPI'; import { getArchivedRatingsLeaderboardAPI } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboardAPI'; import { numberFetcher } from '../../../core/blitzkrieg/numberFetcher'; @@ -66,6 +76,9 @@ export default function Page() { } }); const [page, setPage] = useState(0); + const [highlightedPlayerId, setHighlightedPlayerId] = useState( + null, + ); const playerSlice = players.data?.slice( page * rowsPerPage, page * rowsPerPage + rowsPerPage, @@ -75,6 +88,34 @@ export default function Page() { setPage((page) => Math.min(page + 1, Math.floor((players.data?.length ?? 0) / rowsPerPage)), ); + const [searchResults, setSearchResults] = useState( + undefined, + ); + const handleSearchPlayerChange = debounce( + async (event: ChangeEvent) => { + const trimmedSearch = event.target.value.trim(); + + if (trimmedSearch) { + const encodedSearch = encodeURIComponent(trimmedSearch); + const accountList = await fetchBlitz( + `https://api.wotblitz.com/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=100`, + ); + + setSearchResults( + accountList.filter( + (searchedPlayer) => + players.data?.some( + (leaderboardPlayer) => + leaderboardPlayer.id === searchedPlayer.account_id, + ), + ), + ); + } else { + setSearchResults(undefined); + } + }, + 500, + ); cachePage(page - 1); cachePage(page); @@ -156,7 +197,7 @@ export default function Page() { } return ( - + - - - - - - + + - + - - + + - + - - - - + + {searchResults?.map((player) => ( + + - + setPage(playerPage); + setHighlightedPlayerId(player.account_id); + }} + > + {player.nickname} + + + ))} + + {searchResults?.length === 0 && ( + + )} + + + + + + - - + + )) )} From ca00f18a6a3d49b87f153c788249269cab929b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 10:47:28 -0600 Subject: [PATCH 25/88] feat: jump to position --- src/app/tools/ratings/page.tsx | 248 +++++++++++++++++++-------------- 1 file changed, 147 insertions(+), 101 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index d06299c75..4aed22073 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -83,6 +83,7 @@ export default function Page() { page * rowsPerPage, page * rowsPerPage + rowsPerPage, ); + const positionInput = useRef(null); const previousPage = () => setPage((page) => Math.max(page - 1, 0)); const nextPage = () => setPage((page) => @@ -152,6 +153,19 @@ export default function Page() { }); } } + const handleJumpToPosition = () => { + const rawPosition = positionInput.current!.valueAsNumber - 1; + const position = Math.max( + 0, + Math.min(rawPosition, (players.data?.length ?? 0) - 1), + ); + setPage(Math.floor(position / rowsPerPage)); + + if (players.data) { + setHighlightedPlayerId(players.data[position].id); + } + }; + const [jumpToPositionOpen, setJumpToPositionOpen] = useState(false); useEffect(() => { setPage(0); @@ -246,98 +260,11 @@ export default function Page() { - - - - - - - - - - setJumpToLeague(parseInt(value))} - > - - - - {ratingsInfo?.detail === undefined && - ratingsInfo?.leagues.map(({ index, title }) => ( - - {title} - - ))} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {searchResults?.map((player) => ( - - - - ))} - - {searchResults?.length === 0 && ( - - )} - - - - - - - - - - - - + + + setRowsPerPage(parseInt(value))} defaultValue={`${rowsPerPage}`} @@ -352,16 +279,135 @@ export default function Page() { ))} + + - - - - - + + + + + + + + + { + if (event.key === 'Enter') { + handleJumpToPosition(); + setJumpToPositionOpen(false); + } + }} + /> + + + + + + + + + + + + + + + + + + + + + setJumpToLeague(parseInt(value))} + > + + + + {ratingsInfo?.detail === undefined && + ratingsInfo?.leagues.map(({ index, title }) => ( + + {title} + + ))} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {searchResults?.map((player) => ( + + + + ))} + + {searchResults?.length === 0 && ( + + )} + + + + + + + + + + From 8d2679c458a60a761e8ae4a2ead62bf0dd24b4d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 10:50:41 -0600 Subject: [PATCH 26/88] feat: better page turner --- src/app/tools/ratings/page.tsx | 112 +++++++++++++++------------------ 1 file changed, 52 insertions(+), 60 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 4aed22073..3546b2227 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -179,34 +179,56 @@ export default function Page() { }, [page]); return ( - - Page - { - setPage( - Math.max( - 0, - Math.min( - Math.floor((players.data?.length ?? 0) / rowsPerPage), - event.target.valueAsNumber - 1, + + + + Page + { + setPage( + Math.max( + 0, + Math.min( + Math.floor((players.data?.length ?? 0) / rowsPerPage), + event.target.valueAsNumber - 1, + ), ), - ), - ); - }} - onKeyDown={(event) => { - if (event.key === 'Enter') { - (event.target as HTMLInputElement).blur(); - } - }} - /> - - out of {Math.ceil((players.data?.length ?? 0) / rowsPerPage)} - - + ); + }} + onKeyDown={(event) => { + if (event.key === 'Enter') { + (event.target as HTMLInputElement).blur(); + } + }} + /> + + out of {Math.ceil((players.data?.length ?? 0) / rowsPerPage)} + + + setRowsPerPage(parseInt(value))} + defaultValue={`${rowsPerPage}`} + > + + + + {ROWS_OPTIONS.map((size) => ( + + {size} per page + + ))} + + + + ); } @@ -260,30 +282,6 @@ export default function Page() { - - - - setRowsPerPage(parseInt(value))} - defaultValue={`${rowsPerPage}`} - > - - - - {ROWS_OPTIONS.map((size) => ( - - {size} per page - - ))} - - - - - + + {players.isLoading ? ( @@ -430,15 +430,7 @@ export default function Page() { )} - - - - - + ); } From 57527115d4977715e66a354bbeb4cfac9061b1a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:02:11 -0600 Subject: [PATCH 27/88] feat: jump to league --- src/app/tools/ratings/page.tsx | 47 ++++++++++++++++++++++------------ src/commands/ratings.tsx | 9 +------ src/constants/leagues.ts | 7 +++++ 3 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 src/constants/leagues.ts diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 3546b2227..de8a16854 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -5,14 +5,7 @@ import { CaretRightIcon, MagnifyingGlassIcon, } from '@radix-ui/react-icons'; -import { - Button, - Dialog, - Flex, - Popover, - Select, - TextField, -} from '@radix-ui/themes'; +import { Button, Dialog, Flex, Select, TextField } from '@radix-ui/themes'; import { produce } from 'immer'; import { debounce, range } from 'lodash'; import { ChangeEvent, useEffect, useRef, useState } from 'react'; @@ -20,6 +13,7 @@ import useSWR from 'swr'; import { create } from 'zustand'; import * as Leaderboard from '../../../components/Leaderboard'; import PageWrapper from '../../../components/PageWrapper'; +import { LEAGUES } from '../../../constants/leagues'; import { FIRST_ARCHIVED_RATINGS_SEASON } from '../../../constants/ratings'; import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID'; @@ -318,12 +312,12 @@ export default function Page() { - - + + - + - + - + - + + + + - - + + diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index fc06f2322..7abe31c82 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -9,6 +9,7 @@ import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderbo import * as Leaderboard from '../components/Leaderboard'; import TitleBar from '../components/TitleBar'; import Wrapper from '../components/Wrapper'; +import { LEAGUES } from '../constants/leagues'; import { FIRST_ARCHIVED_RATINGS_SEASON } from '../constants/ratings'; import { REGION_NAMES_SHORT, Region } from '../constants/regions'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; @@ -114,14 +115,6 @@ export interface RatingsNeighbors { neighbors: RatingsPlayer[]; } -const LEAGUES = [ - { name: 'Diamond', minScore: 5000 }, - { name: 'Platinum', minScore: 4000 }, - { name: 'Gold', minScore: 3000 }, - { name: 'Silver', minScore: 2000 }, - { name: 'Bronze', minScore: 0 }, -]; - function addSubcommands( option: SlashCommandSubcommandGroupBuilder, addOptions = (option: SlashCommandSubcommandBuilder) => option, diff --git a/src/constants/leagues.ts b/src/constants/leagues.ts new file mode 100644 index 000000000..082886490 --- /dev/null +++ b/src/constants/leagues.ts @@ -0,0 +1,7 @@ +export const LEAGUES = [ + { name: 'Diamond', minScore: 5000 }, + { name: 'Platinum', minScore: 4000 }, + { name: 'Gold', minScore: 3000 }, + { name: 'Silver', minScore: 2000 }, + { name: 'Bronze', minScore: 0 }, +]; From 0b9db21c10f4be76ec3b5b5dc7a62bff5b16ef0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:13:30 -0600 Subject: [PATCH 28/88] feat: deleted user handling --- src/app/tools/ratings/page.tsx | 21 +++++++++++++------ .../Leaderboard/components/Item.tsx | 5 ++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index de8a16854..e5f9d510b 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -29,7 +29,9 @@ import { noArrows } from './page.css'; const ROWS_OPTIONS = [5, 10, 25, 50, 100]; -const useUsernameCache = create>>(() => ({ +type UsernameCache = Record>; + +const useUsernameCache = create(() => ({ asia: {}, com: {}, eu: {}, @@ -124,10 +126,14 @@ export default function Page() { if (ids && ids.length > 0) { getAccountInfo(region, ids).then((data) => { - data.map((player) => { + data.map((player, index) => { useUsernameCache.setState( - produce((draft) => { - draft[region][player.account_id] = player.nickname; + produce((draft: UsernameCache) => { + if (player) { + draft[region][ids[index]] = player.nickname; + } else { + draft[region][ids[index]] = null; + } }), ); }); @@ -432,8 +438,11 @@ export default function Page() { playerSlice?.map((player, index) => ( { position: number; deltaPosition?: number; score: number; @@ -25,9 +26,11 @@ export function Item({ nickname, clan, highlight, + ...props }: ItemProps) { return (
Date: Tue, 7 Nov 2023 11:36:08 -0600 Subject: [PATCH 29/88] feat: public blitzkrieg-db --- src/app/tools/ratings/page.tsx | 8 ++-- .../getArchivedLatestSeasonNumber.ts | 46 +++++++++++-------- src/core/blitzkrieg/getArchivedRatingsInfo.ts | 16 ++----- .../blitzkrieg/getArchivedRatingsInfoAPI.ts | 40 ---------------- .../getArchivedRatingsLeaderboard.ts | 16 ++----- .../getArchivedRatingsLeaderboardAPI.ts | 29 ------------ .../getArchivedRatingsMidnightLeaderboard.ts | 19 +++----- 7 files changed, 44 insertions(+), 130 deletions(-) delete mode 100644 src/core/blitzkrieg/getArchivedRatingsInfoAPI.ts delete mode 100644 src/core/blitzkrieg/getArchivedRatingsLeaderboardAPI.ts diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index e5f9d510b..2c9cdd452 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -22,8 +22,8 @@ import { getAccountInfo } from '../../../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; import { AccountList } from '../../../core/blitz/searchPlayersAcrossRegions'; -import getArchivedRatingsInfoAPI from '../../../core/blitzkrieg/getArchivedRatingsInfoAPI'; -import { getArchivedRatingsLeaderboardAPI } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboardAPI'; +import getArchivedRatingsInfo from '../../../core/blitzkrieg/getArchivedRatingsInfo'; +import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; import { numberFetcher } from '../../../core/blitzkrieg/numberFetcher'; import { noArrows } from './page.css'; @@ -56,7 +56,7 @@ export default function Page() { () => season === null ? getRatingsInfo(region) - : getArchivedRatingsInfoAPI(region, season), + : getArchivedRatingsInfo(region, season), ); const [jumpToLeague, setJumpToLeague] = useState(0); const { data: latestArchivedSeasonNumber } = useSWR( @@ -68,7 +68,7 @@ export default function Page() { // i'll deal with this later return null; } else { - return getArchivedRatingsLeaderboardAPI(region, season); + return getArchivedRatingsLeaderboard(region, season); } }); const [page, setPage] = useState(0); diff --git a/src/core/blitzkrieg/getArchivedLatestSeasonNumber.ts b/src/core/blitzkrieg/getArchivedLatestSeasonNumber.ts index e3ba0cf51..8d508e0e1 100644 --- a/src/core/blitzkrieg/getArchivedLatestSeasonNumber.ts +++ b/src/core/blitzkrieg/getArchivedLatestSeasonNumber.ts @@ -1,25 +1,33 @@ -import { DATABASE_REPO } from './getArchivedRatingsMidnightLeaderboard'; -import { octokit } from './octokit'; +import getRatingsInfo from '../blitz/getRatingsInfo'; let cachedLatestArchivedSeasonNumber: number | null = null; export async function getArchivedLatestSeasonNumber() { - if (cachedLatestArchivedSeasonNumber) { - return cachedLatestArchivedSeasonNumber; + const ratingsInfo = await getRatingsInfo('com'); + + if (ratingsInfo.detail) { + const comURL = ( + (await fetch( + 'https://api.github.com/repos/tresabhi/blitzkrieg-db/git/trees/main', + ).then((response) => response.json())) as { + tree: { path: string; url: string }[]; + } + ).tree.find(({ path }) => path === 'com')!.url; + const ratingsURL = ( + (await fetch(comURL).then((response) => response.json())) as { + tree: [{ url: string }]; + } + ).tree[0].url; + const latestSeason = Math.max( + ...( + (await fetch(ratingsURL).then((response) => { + response.json(); + })) as { tree: { path: string }[] } + ).tree.map(({ path }) => parseInt(path)), + ); + + return latestSeason; + } else { + return ratingsInfo.current_season - 1; } - - const { data } = await octokit.repos.getContent({ - ...DATABASE_REPO, - path: 'com/ratings', - }); - - if (!Array.isArray(data)) { - throw new Error('Archived ratings data is malformed'); - } - - cachedLatestArchivedSeasonNumber = Math.max( - ...data.map(({ name }) => parseInt(name)), - ); - - return cachedLatestArchivedSeasonNumber; } diff --git a/src/core/blitzkrieg/getArchivedRatingsInfo.ts b/src/core/blitzkrieg/getArchivedRatingsInfo.ts index 76f85908b..e08b9793a 100644 --- a/src/core/blitzkrieg/getArchivedRatingsInfo.ts +++ b/src/core/blitzkrieg/getArchivedRatingsInfo.ts @@ -1,7 +1,5 @@ import { RatingsInfo } from '../../commands/ratings'; import { Region } from '../../constants/regions'; -import { DATABASE_REPO } from './getArchivedRatingsMidnightLeaderboard'; -import { octokit } from './octokit'; const INFO_CACHE: Record< Region, @@ -25,16 +23,10 @@ export default async function getArchivedRatingsInfo( return INFO_CACHE[region][season]; } - const { data } = await octokit.repos.getContent({ - ...DATABASE_REPO, - path: `${region}/ratings/${season}/info.json`, - }); - - if (Array.isArray(data) || data.type !== 'file') - throw new Error('Archived ratings info is malformed'); - - const content = Buffer.from(data.content, 'base64').toString(); - const jsonContent = JSON.parse(content) as RatingsInfo & { + const response = await fetch( + `https://raw.githubusercontent.com/tresabhi/blitzkrieg-db/main/${region}/ratings/${season}/info.json`, + ); + const jsonContent = (await response.json()) as RatingsInfo & { detail: undefined; }; diff --git a/src/core/blitzkrieg/getArchivedRatingsInfoAPI.ts b/src/core/blitzkrieg/getArchivedRatingsInfoAPI.ts deleted file mode 100644 index 89cc631c5..000000000 --- a/src/core/blitzkrieg/getArchivedRatingsInfoAPI.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { RatingsInfo } from '../../commands/ratings'; -import { Region } from '../../constants/regions'; - -const INFO_CACHE: Record< - Region, - Record< - number, - RatingsInfo & { - detail: undefined; - } - > -> = { - com: {}, - eu: {}, - asia: {}, -}; - -export default async function getArchivedRatingsInfoAPI( - region: Region, - season: number, -) { - if (INFO_CACHE[region][season]) { - return INFO_CACHE[region][season]; - } - - const response = await fetch( - `/api/ratings/archives/${region}/${season}/info`, - ); - const json = (await response.json()) as RatingsInfo; - - if (json.detail) { - throw new Error( - `No ratings info found for season ${season}, region ${region}`, - ); - } - - INFO_CACHE[region][season] = json; - - return json; -} diff --git a/src/core/blitzkrieg/getArchivedRatingsLeaderboard.ts b/src/core/blitzkrieg/getArchivedRatingsLeaderboard.ts index c253ea094..29e59e56e 100644 --- a/src/core/blitzkrieg/getArchivedRatingsLeaderboard.ts +++ b/src/core/blitzkrieg/getArchivedRatingsLeaderboard.ts @@ -1,7 +1,5 @@ import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard'; import { Region } from '../../constants/regions'; -import { DATABASE_REPO } from './getArchivedRatingsMidnightLeaderboard'; -import { octokit } from './octokit'; const LEADERBOARD_CACHE: Record< Region, @@ -20,18 +18,10 @@ export async function getArchivedRatingsLeaderboard( return LEADERBOARD_CACHE[region][season]; } - const { data } = await octokit.repos.getContent({ - ...DATABASE_REPO, - path: `${region}/ratings/${season}/latest.json`, - }); - - if (Array.isArray(data) || data.type !== 'file') { - throw new Error('Archived ratings data is malformed'); - } - - const response = await fetch(data.download_url!); + const response = await fetch( + `https://raw.githubusercontent.com/tresabhi/blitzkrieg-db/main/${region}/ratings/${season}/latest.json`, + ); const json = (await response.json()) as BlitzkriegRatingsLeaderboard; - LEADERBOARD_CACHE[region][season] = json; return json; diff --git a/src/core/blitzkrieg/getArchivedRatingsLeaderboardAPI.ts b/src/core/blitzkrieg/getArchivedRatingsLeaderboardAPI.ts deleted file mode 100644 index 410661277..000000000 --- a/src/core/blitzkrieg/getArchivedRatingsLeaderboardAPI.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard'; -import { Region } from '../../constants/regions'; - -const LEADERBOARD_CACHE: Record< - Region, - Record -> = { - asia: {}, - eu: {}, - com: {}, -}; - -export async function getArchivedRatingsLeaderboardAPI( - region: Region, - season: number, -) { - if (LEADERBOARD_CACHE[region][season]) { - return LEADERBOARD_CACHE[region][season]; - } - - const response = await fetch( - `/api/ratings/archives/${region}/${season}/latest`, - ); - const json = (await response.json()) as BlitzkriegRatingsLeaderboard; - - LEADERBOARD_CACHE[region][season] = json; - - return json; -} diff --git a/src/core/blitzkrieg/getArchivedRatingsMidnightLeaderboard.ts b/src/core/blitzkrieg/getArchivedRatingsMidnightLeaderboard.ts index 392169365..85c7029b0 100644 --- a/src/core/blitzkrieg/getArchivedRatingsMidnightLeaderboard.ts +++ b/src/core/blitzkrieg/getArchivedRatingsMidnightLeaderboard.ts @@ -1,6 +1,5 @@ import { BlitzkriegRatingsLeaderboard } from '../../../scripts/buildRatingsLeaderboard'; import { Region } from '../../constants/regions'; -import { octokit } from './octokit'; export const DATABASE_REPO = { owner: 'tresabhi', repo: 'blitzkrieg-db' }; @@ -22,19 +21,13 @@ export default async function getArchivedRatingsMidnightLeaderboard( } try { - const { data } = await octokit.repos.getContent({ - ...DATABASE_REPO, - path: `${region}/ratings/${season}/midnight.json`, - }); + const response = await fetch( + `https://raw.githubusercontent.com/tresabhi/blitzkrieg-db/main/${region}/ratings/${season}/midnight.json`, + ); + const json = (await response.json()) as BlitzkriegRatingsLeaderboard; + MIDNIGHT_LEADERBOARD_CACHE[region][season] = json; - if (!Array.isArray(data) && data.type === 'file') { - const response = await fetch(data.download_url!); - const json = (await response.json()) as BlitzkriegRatingsLeaderboard; - - MIDNIGHT_LEADERBOARD_CACHE[region][season] = json; - - return json; - } + return json; } catch (error) { console.warn( `No midnight leaderboard found for season ${season}, region ${region}`, From 1081e836f54b39146249bd04d511560b9c803caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:40:46 -0600 Subject: [PATCH 30/88] fix: region based player search --- src/app/tools/ratings/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 2c9cdd452..96e445896 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -95,7 +95,7 @@ export default function Page() { if (trimmedSearch) { const encodedSearch = encodeURIComponent(trimmedSearch); const accountList = await fetchBlitz( - `https://api.wotblitz.com/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=100`, + `https://api.wotblitz.${region}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=100`, ); setSearchResults( From 46457a8bdce4949398a20bb7ac00e4d80da21eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:50:51 -0600 Subject: [PATCH 31/88] feat: jump to score --- src/app/tools/ratings/page.tsx | 132 +++++++++++++++++++++++---------- 1 file changed, 93 insertions(+), 39 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 96e445896..fc8059a89 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -7,7 +7,7 @@ import { } from '@radix-ui/react-icons'; import { Button, Dialog, Flex, Select, TextField } from '@radix-ui/themes'; import { produce } from 'immer'; -import { debounce, range } from 'lodash'; +import { debounce, findLastIndex, range } from 'lodash'; import { ChangeEvent, useEffect, useRef, useState } from 'react'; import useSWR from 'swr'; import { create } from 'zustand'; @@ -80,6 +80,7 @@ export default function Page() { page * rowsPerPage + rowsPerPage, ); const positionInput = useRef(null); + const scoreInput = useRef(null); const previousPage = () => setPage((page) => Math.max(page - 1, 0)); const nextPage = () => setPage((page) => @@ -154,6 +155,8 @@ export default function Page() { } } const handleJumpToPosition = () => { + if (!players.data) return; + const rawPosition = positionInput.current!.valueAsNumber - 1; const position = Math.max( 0, @@ -166,6 +169,22 @@ export default function Page() { } }; const [jumpToPositionOpen, setJumpToPositionOpen] = useState(false); + const handleJumpToScore = () => { + if (!players.data) return; + + const playerIndex = findLastIndex(players.data, (player) => { + return player.score >= scoreInput.current!.valueAsNumber; + }); + + if (playerIndex === -1) return; + + setPage(Math.floor(playerIndex / rowsPerPage)); + + if (players.data) { + setHighlightedPlayerId(players.data[playerIndex].id); + } + }; + const [jumpToScoreOpen, setJumpToScoreOpen] = useState(false); useEffect(() => { setPage(0); @@ -180,37 +199,6 @@ export default function Page() { return ( - - - Page - { - setPage( - Math.max( - 0, - Math.min( - Math.floor((players.data?.length ?? 0) / rowsPerPage), - event.target.valueAsNumber - 1, - ), - ), - ); - }} - onKeyDown={(event) => { - if (event.key === 'Enter') { - (event.target as HTMLInputElement).blur(); - } - }} - /> - - out of {Math.ceil((players.data?.length ?? 0) / rowsPerPage)} - - setRowsPerPage(parseInt(value))} defaultValue={`${rowsPerPage}`} @@ -225,9 +213,43 @@ export default function Page() { ))} - + + + + + Page + { + setPage( + Math.max( + 0, + Math.min( + Math.floor((players.data?.length ?? 0) / rowsPerPage), + event.target.valueAsNumber - 1, + ), + ), + ); + }} + onKeyDown={(event) => { + if (event.key === 'Enter') { + (event.target as HTMLInputElement).blur(); + } + }} + /> + + out of {Math.ceil((players.data?.length ?? 0) / rowsPerPage)} + + + + ); } @@ -282,13 +304,45 @@ export default function Page() { - + + + + + + + + + { + if (event.key === 'Enter') { + handleJumpToScore(); + setJumpToScoreOpen(false); + } + }} + /> + + + + + + + + + + + + + - + @@ -320,7 +374,7 @@ export default function Page() { - + @@ -373,7 +427,7 @@ export default function Page() { - + From 62f6aabf05ca644fab0d7feefe66a5407585f770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:55:27 -0600 Subject: [PATCH 32/88] feat: better jump buttons --- src/app/tools/ratings/page.tsx | 22 +++++++++++++++------- src/components/PageWrapper.tsx | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index fc8059a89..af9e466c8 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -5,7 +5,14 @@ import { CaretRightIcon, MagnifyingGlassIcon, } from '@radix-ui/react-icons'; -import { Button, Dialog, Flex, Select, TextField } from '@radix-ui/themes'; +import { + Button, + Dialog, + Flex, + Select, + Text, + TextField, +} from '@radix-ui/themes'; import { produce } from 'immer'; import { debounce, findLastIndex, range } from 'lodash'; import { ChangeEvent, useEffect, useRef, useState } from 'react'; @@ -304,10 +311,11 @@ export default function Page() { - + + Jump to: - + @@ -342,7 +350,7 @@ export default function Page() { onOpenChange={setJumpToPositionOpen} > - + @@ -374,11 +382,11 @@ export default function Page() { - + - + setJumpToLeague(parseInt(value))} @@ -427,7 +435,7 @@ export default function Page() { - + diff --git a/src/components/PageWrapper.tsx b/src/components/PageWrapper.tsx index ecfc7a5f1..34ef80c03 100644 --- a/src/components/PageWrapper.tsx +++ b/src/components/PageWrapper.tsx @@ -20,7 +20,7 @@ export default function PageWrapper({ display: 'flex', padding: 16, flexDirection: 'column', - gap: 16, + gap: 32, boxSizing: 'border-box', ...style, From 6f784e9e94da0bc739a1fd7820c17b4e0b3e33b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:24:36 -0600 Subject: [PATCH 33/88] feat: remove clan apis --- .../ratings/archives/[region]/[season]/info/route.ts | 12 ------------ .../archives/[region]/[season]/latest/route.ts | 12 ------------ .../ratings/latest-archived-season-number/route.ts | 6 ------ 3 files changed, 30 deletions(-) delete mode 100644 src/app/api/ratings/archives/[region]/[season]/info/route.ts delete mode 100644 src/app/api/ratings/archives/[region]/[season]/latest/route.ts delete mode 100644 src/app/api/ratings/latest-archived-season-number/route.ts diff --git a/src/app/api/ratings/archives/[region]/[season]/info/route.ts b/src/app/api/ratings/archives/[region]/[season]/info/route.ts deleted file mode 100644 index 912ffe7a3..000000000 --- a/src/app/api/ratings/archives/[region]/[season]/info/route.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NextResponse } from 'next/server'; -import { Region } from '../../../../../../../constants/regions'; -import getArchivedRatingsInfo from '../../../../../../../core/blitzkrieg/getArchivedRatingsInfo'; - -export async function GET( - request: Request, - { params }: { params: { region: Region; season: number } }, -) { - return NextResponse.json( - await getArchivedRatingsInfo(params.region, params.season), - ); -} diff --git a/src/app/api/ratings/archives/[region]/[season]/latest/route.ts b/src/app/api/ratings/archives/[region]/[season]/latest/route.ts deleted file mode 100644 index 1dede8e2a..000000000 --- a/src/app/api/ratings/archives/[region]/[season]/latest/route.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NextResponse } from 'next/server'; -import { Region } from '../../../../../../../constants/regions'; -import { getArchivedRatingsLeaderboard } from '../../../../../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; - -export async function GET( - request: Request, - { params }: { params: { region: Region; season: number } }, -) { - return NextResponse.json( - await getArchivedRatingsLeaderboard(params.region, params.season), - ); -} diff --git a/src/app/api/ratings/latest-archived-season-number/route.ts b/src/app/api/ratings/latest-archived-season-number/route.ts deleted file mode 100644 index 241f9777c..000000000 --- a/src/app/api/ratings/latest-archived-season-number/route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { NextResponse } from 'next/server'; -import { getArchivedLatestSeasonNumber } from '../../../../core/blitzkrieg/getArchivedLatestSeasonNumber'; - -export async function GET() { - return NextResponse.json(await getArchivedLatestSeasonNumber()); -} From 9e340d2e1694ce1f1d9160126172d9562468740c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:24:51 -0600 Subject: [PATCH 34/88] fix: deleted clan handling --- src/app/tools/ratings/page.tsx | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index af9e466c8..f5367b956 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -29,27 +29,26 @@ import { getAccountInfo } from '../../../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; import { AccountList } from '../../../core/blitz/searchPlayersAcrossRegions'; +import { getArchivedLatestSeasonNumber } from '../../../core/blitzkrieg/getArchivedLatestSeasonNumber'; import getArchivedRatingsInfo from '../../../core/blitzkrieg/getArchivedRatingsInfo'; import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; -import { numberFetcher } from '../../../core/blitzkrieg/numberFetcher'; import { noArrows } from './page.css'; const ROWS_OPTIONS = [5, 10, 25, 50, 100]; type UsernameCache = Record>; +type ClanCache = Record>; const useUsernameCache = create(() => ({ asia: {}, com: {}, eu: {}, })); -const useClanCache = create>>( - () => ({ - asia: {}, - com: {}, - eu: {}, - }), -); +const useClanCache = create(() => ({ + asia: {}, + com: {}, + eu: {}, +})); export default function Page() { const [rowsPerPage, setRowsPerPage] = useState(ROWS_OPTIONS[2]); @@ -67,8 +66,8 @@ export default function Page() { ); const [jumpToLeague, setJumpToLeague] = useState(0); const { data: latestArchivedSeasonNumber } = useSWR( - '/api/ratings/latest-archived-season-number', - numberFetcher, + getArchivedLatestSeasonNumber.name, + getArchivedLatestSeasonNumber, ); const players = useSWR(`ratings-players-${region}-${season}`, () => { if (season === null) { @@ -148,12 +147,11 @@ export default function Page() { }); getClanAccountInfo(region, ids, ['clan']).then((data) => { - data.map((player) => { + data.map((player, index) => { useClanCache.setState( - produce((draft) => { + produce((draft: ClanCache) => { if (player) { - draft[region][player.account_id] = - player.clan?.tag ?? undefined; + draft[region][ids[index]] = player.clan?.tag; } }), ); From 6600bba58c091d00ccd207cd6c6b836e3f688b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:33:41 -0600 Subject: [PATCH 35/88] feat: better layout --- src/app/tools/ratings/page.tsx | 431 +++++++++++++++++---------------- src/components/PageWrapper.tsx | 13 +- 2 files changed, 222 insertions(+), 222 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index f5367b956..9efb65575 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -204,21 +204,6 @@ export default function Page() { return ( - setRowsPerPage(parseInt(value))} - defaultValue={`${rowsPerPage}`} - > - - - - {ROWS_OPTIONS.map((size) => ( - - {size} per page - - ))} - - - - - - - - { - if (event.key === 'Enter') { - handleJumpToScore(); - setJumpToScoreOpen(false); - } - }} - /> - - - - - - - - - - - - - - - - - - - - - { - if (event.key === 'Enter') { - handleJumpToPosition(); - setJumpToPositionOpen(false); - } - }} - /> - - - - - - - - - - - - - - - - - - - - - setJumpToLeague(parseInt(value))} - > - - - - {ratingsInfo?.detail === undefined && - ratingsInfo?.leagues.map(({ index, title }) => ( - - {title} + {latestArchivedSeasonNumber && + range( + FIRST_ARCHIVED_RATINGS_SEASON, + latestArchivedSeasonNumber + 1, + ) + .map((season) => ( + + Season {season} - ))} - - + )) + .reverse()} + + + - - - - - - - - - - - - - - - - - - - - - - - + + Jump to: + + + + + + { + if (event.key === 'Enter') { + handleJumpToScore(); + setJumpToScoreOpen(false); + } + }} /> - - - {searchResults?.map((player) => ( + + + + + + + + + + + + + + + + + + + { + if (event.key === 'Enter') { + handleJumpToPosition(); + setJumpToPositionOpen(false); + } + }} + /> + + + + + + + + + + + + + + + + + + + + setJumpToLeague(parseInt(value))} + > + + + + {ratingsInfo?.detail === undefined && + ratingsInfo?.leagues.map(({ index, title }) => ( + + {title} + + ))} + + + + + + + - ))} - - {searchResults?.length === 0 && ( - - )} + + + + + + + - - - - + + + + + + + + + + + + {searchResults?.map((player) => ( + + + + ))} + + {searchResults?.length === 0 && ( + + )} + + + + + + + - - - + + + diff --git a/src/components/PageWrapper.tsx b/src/components/PageWrapper.tsx index 34ef80c03..666937af3 100644 --- a/src/components/PageWrapper.tsx +++ b/src/components/PageWrapper.tsx @@ -1,7 +1,7 @@ -import { Theme } from '@radix-ui/themes'; +import { Flex, Theme } from '@radix-ui/themes'; import { ComponentProps } from 'react'; -interface PageWrapperProps extends ComponentProps<'div'> { +interface PageWrapperProps extends ComponentProps { color?: ComponentProps['accentColor']; } @@ -12,20 +12,19 @@ export default function PageWrapper({ }: PageWrapperProps) { return ( -
); From fbde9ec64f3b12376c88cd742d12e221ac3a4eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 18:58:19 -0600 Subject: [PATCH 36/88] fix: no double fetch and caching --- scripts/buildRatingsLeaderboard.ts | 19 +- src/app/tools/ratings/page.tsx | 307 ++++++++++++++++++---------- src/core/blitz/getRatingsLeague.ts | 23 +++ src/core/blitzkrieg/patientFetch.ts | 14 ++ 4 files changed, 243 insertions(+), 120 deletions(-) create mode 100644 src/core/blitz/getRatingsLeague.ts create mode 100644 src/core/blitzkrieg/patientFetch.ts diff --git a/scripts/buildRatingsLeaderboard.ts b/scripts/buildRatingsLeaderboard.ts index 07c477fc9..4f989f0d8 100644 --- a/scripts/buildRatingsLeaderboard.ts +++ b/scripts/buildRatingsLeaderboard.ts @@ -6,6 +6,7 @@ import { RatingsNeighbors, RatingsPlayer, } from '../src/commands/ratings'; +import { patientFetch } from '../src/core/blitzkrieg/patientFetch'; import commitMultipleFiles from './commitMultipleFiles.js'; /* @@ -18,22 +19,8 @@ import commitMultipleFiles from './commitMultipleFiles.js'; * [region]/ratings/[season]/midnight.json: last midnight's leaderboard relative to the region */ -async function patientFetch(url: string) { - let patientResponse: Response; - - while (patientResponse! === undefined) { - try { - patientResponse = await fetch(url); - } catch (error) { - console.warn(`Failed to fetch ${url}; retrying in 1 minute...`); - await new Promise((resolve) => setTimeout(resolve, 1 * 60 * 1000)); - } - } - - return patientResponse; -} - -export type BlitzkriegRatingsLeaderboard = { id: number; score: number }[]; +export type BlitzkriegRatingsLeaderboardEntry = { id: number; score: number }; +export type BlitzkriegRatingsLeaderboard = BlitzkriegRatingsLeaderboardEntry[]; const publish = argv.includes('--publish'); const latest = argv.includes('--latest'); diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 9efb65575..cfee3da4d 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -14,10 +14,11 @@ import { TextField, } from '@radix-ui/themes'; import { produce } from 'immer'; -import { debounce, findLastIndex, range } from 'lodash'; +import { debounce, range } from 'lodash'; import { ChangeEvent, useEffect, useRef, useState } from 'react'; import useSWR from 'swr'; import { create } from 'zustand'; +import { BlitzkriegRatingsLeaderboardEntry } from '../../../../scripts/buildRatingsLeaderboard'; import * as Leaderboard from '../../../components/Leaderboard'; import PageWrapper from '../../../components/PageWrapper'; import { LEAGUES } from '../../../constants/leagues'; @@ -28,13 +29,15 @@ import fetchBlitz from '../../../core/blitz/fetchBlitz'; import { getAccountInfo } from '../../../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; +import { getRatingsLeague } from '../../../core/blitz/getRatingsLeague'; import { AccountList } from '../../../core/blitz/searchPlayersAcrossRegions'; import { getArchivedLatestSeasonNumber } from '../../../core/blitzkrieg/getArchivedLatestSeasonNumber'; import getArchivedRatingsInfo from '../../../core/blitzkrieg/getArchivedRatingsInfo'; import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; import { noArrows } from './page.css'; -const ROWS_OPTIONS = [5, 10, 25, 50, 100]; +const ROWS_OPTIONS_ARCHIVED = [5, 10, 15, 25, 30, 50, 100]; +const ROWS_OPTIONS_CURRENT = [5, 10, 15, 25, 30]; type UsernameCache = Record>; type ClanCache = Record>; @@ -51,7 +54,7 @@ const useClanCache = create(() => ({ })); export default function Page() { - const [rowsPerPage, setRowsPerPage] = useState(ROWS_OPTIONS[2]); + const [rowsPerPage, setRowsPerPage] = useState(25); const usernameCache = useUsernameCache(); const clanCache = useClanCache(); const [region, setRegion] = useState('com'); @@ -69,29 +72,44 @@ export default function Page() { getArchivedLatestSeasonNumber.name, getArchivedLatestSeasonNumber, ); - const players = useSWR(`ratings-players-${region}-${season}`, () => { + const players = useSWR(`ratings-players-${region}-${season}`, async () => { if (season === null) { - // i'll deal with this later - return null; + // const players = await getRatingsLeague(region, 0); + // useUsernameCache.setState( + // produce((draft: UsernameCache) => { + // players.result.forEach((player) => { + // draft[region][player.spa_id] = player.nickname; + // }); + // }), + // ); + // useClanCache.setState( + // produce((draft: ClanCache) => { + // players.result.forEach((player) => { + // draft[region][player.spa_id] = player.clan_tag; + // }); + // }), + // ); + // return players.result.map( + // (player) => + // ({ + // id: player.spa_id, + // score: player.score, + // }) satisfies BlitzkriegRatingsLeaderboardEntry, + // ); + + return {}; } else { - return getArchivedRatingsLeaderboard(region, season); + return { + ...(await getArchivedRatingsLeaderboard(region, season)), + } as Record; } }); const [page, setPage] = useState(0); const [highlightedPlayerId, setHighlightedPlayerId] = useState( null, ); - const playerSlice = players.data?.slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage, - ); const positionInput = useRef(null); const scoreInput = useRef(null); - const previousPage = () => setPage((page) => Math.max(page - 1, 0)); - const nextPage = () => - setPage((page) => - Math.min(page + 1, Math.floor((players.data?.length ?? 0) / rowsPerPage)), - ); const [searchResults, setSearchResults] = useState( undefined, ); @@ -108,10 +126,7 @@ export default function Page() { setSearchResults( accountList.filter( (searchedPlayer) => - players.data?.some( - (leaderboardPlayer) => - leaderboardPlayer.id === searchedPlayer.account_id, - ), + players.data && searchedPlayer.account_id in players.data, ), ); } else { @@ -121,52 +136,59 @@ export default function Page() { 500, ); - cachePage(page - 1); - cachePage(page); - cachePage(page + 1); + useEffect(() => { + // TODO: caches twice on initial load + cachePage(page - 1); + cachePage(page); + cachePage(page + 1); + }, [page, players.isLoading]); function cachePage(page: number) { - const ids = players.data - ?.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - .map(({ id }) => id) - .filter((id) => !(id in usernameCache[region])); + if (season === null) { + } else { + if (players.isLoading || !ratingsInfo || ratingsInfo?.detail) return; - if (ids && ids.length > 0) { - getAccountInfo(region, ids).then((data) => { - data.map((player, index) => { - useUsernameCache.setState( - produce((draft: UsernameCache) => { - if (player) { - draft[region][ids[index]] = player.nickname; - } else { - draft[region][ids[index]] = null; - } - }), - ); - }); - }); + const ids = range( + Math.max(0, page * rowsPerPage), + Math.min(ratingsInfo.count - 1, page * rowsPerPage + rowsPerPage), + ) + .map((index) => players.data![index].id) + .filter((id) => !(id in usernameCache[region])); - getClanAccountInfo(region, ids, ['clan']).then((data) => { - data.map((player, index) => { - useClanCache.setState( - produce((draft: ClanCache) => { - if (player) { - draft[region][ids[index]] = player.clan?.tag; - } - }), - ); + if (ids && ids.length > 0) { + getAccountInfo(region, ids).then((data) => { + data.map((player, index) => { + useUsernameCache.setState( + produce((draft: UsernameCache) => { + if (player) { + draft[region][ids[index]] = player.nickname; + } else { + draft[region][ids[index]] = null; + } + }), + ); + }); }); - }); + + getClanAccountInfo(region, ids, ['clan']).then((data) => { + data.map((player, index) => { + useClanCache.setState( + produce((draft: ClanCache) => { + if (player) { + draft[region][ids[index]] = player.clan?.tag; + } + }), + ); + }); + }); + } } } const handleJumpToPosition = () => { - if (!players.data) return; + if (!players.data || !ratingsInfo || ratingsInfo?.detail) return; const rawPosition = positionInput.current!.valueAsNumber - 1; - const position = Math.max( - 0, - Math.min(rawPosition, (players.data?.length ?? 0) - 1), - ); + const position = Math.max(0, Math.min(rawPosition, ratingsInfo.count - 1)); setPage(Math.floor(position / rowsPerPage)); if (players.data) { @@ -175,11 +197,16 @@ export default function Page() { }; const [jumpToPositionOpen, setJumpToPositionOpen] = useState(false); const handleJumpToScore = () => { - if (!players.data) return; + if (!players.data || !ratingsInfo || ratingsInfo?.detail) return; - const playerIndex = findLastIndex(players.data, (player) => { - return player.score >= scoreInput.current!.valueAsNumber; - }); + let playerIndex = -1; + + for (let index = ratingsInfo.count - 1; index >= 0; index--) { + if (players.data[index].score >= scoreInput.current!.valueAsNumber) { + playerIndex = index; + break; + } + } if (playerIndex === -1) return; @@ -190,10 +217,17 @@ export default function Page() { } }; const [jumpToScoreOpen, setJumpToScoreOpen] = useState(false); + const pages = Math.ceil( + (ratingsInfo && ratingsInfo.detail === undefined ? ratingsInfo?.count : 1) / + rowsPerPage, + ); useEffect(() => { setPage(0); }, [season, region]); + useEffect(() => { + if (season === null && rowsPerPage > 30) setRowsPerPage(25); + }, [season]); function PageTurner() { const pageInput = useRef(null); @@ -205,7 +239,10 @@ export default function Page() { return ( - @@ -219,10 +256,7 @@ export default function Page() { setPage( Math.max( 0, - Math.min( - Math.floor((players.data?.length ?? 0) / rowsPerPage), - event.target.valueAsNumber - 1, - ), + Math.min(pages - 1, event.target.valueAsNumber - 1), ), ); }} @@ -232,11 +266,14 @@ export default function Page() { } }} /> - - out of {Math.ceil((players.data?.length ?? 0) / rowsPerPage)} - + out of {pages} - @@ -247,7 +284,7 @@ export default function Page() { return ( - + setRegion(value as Region)} @@ -296,12 +333,15 @@ export default function Page() { setRowsPerPage(parseInt(value))} - defaultValue={`${rowsPerPage}`} + value={`${rowsPerPage}`} > - {ROWS_OPTIONS.map((size) => ( + {(season === null + ? ROWS_OPTIONS_CURRENT + : ROWS_OPTIONS_ARCHIVED + ).map((size) => ( {size} per page @@ -406,22 +446,68 @@ export default function Page() { @@ -274,6 +275,7 @@ export default function Page() { onClick={() => { setPage((page) => Math.min(page + 1, pages - 1)); }} + disabled={page === pages - 1} > @@ -450,8 +452,6 @@ export default function Page() { onClick={async () => { if (season === null) { // that's already done - if (jumpToLeague === 0) return; - const leaguePlayers = await getRatingsLeague( region, jumpToLeague, From 212aee0373dae84d0f19dbd92dcc099a05e59435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 7 Nov 2023 20:47:20 -0600 Subject: [PATCH 39/88] feat: current season traversal --- src/app/tools/ratings/page.tsx | 212 ++++++++++++++++---------- src/core/blitz/getRatingsNeighbors.ts | 24 +++ 2 files changed, 156 insertions(+), 80 deletions(-) create mode 100644 src/core/blitz/getRatingsNeighbors.ts diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 6b5ba9187..c8a020b63 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -30,14 +30,14 @@ import { getAccountInfo } from '../../../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; import { getRatingsLeague } from '../../../core/blitz/getRatingsLeague'; +import { getRatingsNeighbors } from '../../../core/blitz/getRatingsNeighbors'; import { AccountList } from '../../../core/blitz/searchPlayersAcrossRegions'; import { getArchivedLatestSeasonNumber } from '../../../core/blitzkrieg/getArchivedLatestSeasonNumber'; import getArchivedRatingsInfo from '../../../core/blitzkrieg/getArchivedRatingsInfo'; import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; import { noArrows } from './page.css'; -const ROWS_OPTIONS_ARCHIVED = [5, 10, 15, 25, 30, 50, 100]; -const ROWS_OPTIONS_CURRENT = [5, 10, 15, 25, 30]; +const ROWS_OPTIONS = [5, 10, 15, 25, 30]; type UsernameCache = Record>; type ClanCache = Record>; @@ -72,39 +72,10 @@ export default function Page() { getArchivedLatestSeasonNumber.name, getArchivedLatestSeasonNumber, ); - const players = useSWR(`ratings-players-${region}-${season}`, async () => { - if (season === null) { - const players = await getRatingsLeague(region, 0); - useUsernameCache.setState( - produce((draft: UsernameCache) => { - players.result.forEach((player) => { - draft[region][player.spa_id] = player.nickname; - }); - }), - ); - useClanCache.setState( - produce((draft: ClanCache) => { - players.result.forEach((player) => { - draft[region][player.spa_id] = player.clan_tag; - }); - }), - ); - - return { - ...players.result.map( - (player) => - ({ - id: player.spa_id, - score: player.score, - }) satisfies BlitzkriegRatingsLeaderboardEntry, - ), - }; - } else { - return { - ...(await getArchivedRatingsLeaderboard(region, season)), - } as Record; - } - }); + const [players, setPlayers] = useState>(null); const [page, setPage] = useState(0); const [highlightedPlayerId, setHighlightedPlayerId] = useState( null, @@ -126,8 +97,7 @@ export default function Page() { setSearchResults( accountList.filter( - (searchedPlayer) => - players.data && searchedPlayer.account_id in players.data, + (searchedPlayer) => players && searchedPlayer.account_id in players, ), ); } else { @@ -138,22 +108,112 @@ export default function Page() { ); useEffect(() => { - // TODO: caches twice on initial load - cachePage(page - 1); - cachePage(page); - cachePage(page + 1); - }, [page, players.isLoading]); - - function cachePage(page: number) { + // league caching if (season === null) { - } else { - if (players.isLoading || !ratingsInfo || ratingsInfo?.detail) return; + Promise.all( + range(0, 4).map(async (league: number) => { + const leaguePlayers = await getRatingsLeague(region, league); + useUsernameCache.setState( + produce((draft: UsernameCache) => { + leaguePlayers.result.forEach((player) => { + draft[region][player.spa_id] = player.nickname; + }); + }), + ); + useClanCache.setState( + produce((draft: ClanCache) => { + leaguePlayers.result.forEach((player) => { + draft[region][player.spa_id] = player.clan_tag; + }); + }), + ); + return leaguePlayers.result.reduce< + Record + >( + (accumulator, player) => ({ + ...accumulator, + + [player.number - 1]: { + id: player.spa_id, + score: player.score, + } satisfies BlitzkriegRatingsLeaderboardEntry, + }), + {}, + ); + }), + ).then((results) => { + const newPlayers = results.reduce< + Record + >( + (accumulator, result) => ({ + ...accumulator, + ...result, + }), + {}, + ); + + setPlayers(newPlayers); + }); + } else { + getArchivedRatingsLeaderboard(region, season).then((result) => + setPlayers({ ...result }), + ); + } + }, []); + useEffect(() => { + // TODO: caches twice on initial load + if (!players || !ratingsInfo || ratingsInfo?.detail) return; + + if (season === null) { + const middleIndex = Math.round(page * rowsPerPage + rowsPerPage / 2); + + if (!(middleIndex in players)) return; + + const middleId = players[middleIndex].id; + + getRatingsNeighbors(region, middleId, rowsPerPage * 3).then( + ({ neighbors }) => { + setPlayers( + produce((draft) => { + draft = draft ?? {}; + + neighbors.forEach((neighbor) => { + draft![neighbor.number - 1] = { + id: neighbor.spa_id, + score: neighbor.score, + } satisfies BlitzkriegRatingsLeaderboardEntry; + }); + }), + ); + + useUsernameCache.setState( + produce((draft: UsernameCache) => { + neighbors.forEach((neighbor) => { + if (neighbor.nickname) { + draft[region][neighbor.spa_id] = neighbor.nickname; + } + }); + }), + ); + + useClanCache.setState( + produce((draft: ClanCache) => { + neighbors.forEach((neighbor) => { + if (neighbor.clan_tag) { + draft[region][neighbor.spa_id] = neighbor.clan_tag; + } + }); + }), + ); + }, + ); + } else { const ids = range( - Math.max(0, page * rowsPerPage), - Math.min(ratingsInfo.count - 1, page * rowsPerPage + rowsPerPage), + Math.max(0, (page - 1) * rowsPerPage), + Math.min(ratingsInfo.count - 1, (page + 1) * rowsPerPage + rowsPerPage), ) - .map((index) => players.data![index].id) + .map((index) => players![index].id) .filter((id) => !(id in usernameCache[region])); if (ids && ids.length > 0) { @@ -184,26 +244,27 @@ export default function Page() { }); } } - } + }, [page, players]); + const handleJumpToPosition = () => { - if (!players.data || !ratingsInfo || ratingsInfo?.detail) return; + if (!players || !ratingsInfo || ratingsInfo?.detail) return; const rawPosition = positionInput.current!.valueAsNumber - 1; const position = Math.max(0, Math.min(rawPosition, ratingsInfo.count - 1)); setPage(Math.floor(position / rowsPerPage)); - if (players.data) { - setHighlightedPlayerId(players.data[position].id); + if (players) { + setHighlightedPlayerId(players[position].id); } }; const [jumpToPositionOpen, setJumpToPositionOpen] = useState(false); const handleJumpToScore = () => { - if (!players.data || !ratingsInfo || ratingsInfo?.detail) return; + if (!players || !ratingsInfo || ratingsInfo?.detail) return; let playerIndex = -1; for (let index = ratingsInfo.count - 1; index >= 0; index--) { - if (players.data[index].score >= scoreInput.current!.valueAsNumber) { + if (players[index].score >= scoreInput.current!.valueAsNumber) { playerIndex = index; break; } @@ -213,8 +274,8 @@ export default function Page() { setPage(Math.floor(playerIndex / rowsPerPage)); - if (players.data) { - setHighlightedPlayerId(players.data[playerIndex].id); + if (players) { + setHighlightedPlayerId(players[playerIndex].id); } }; const [jumpToScoreOpen, setJumpToScoreOpen] = useState(false); @@ -341,10 +402,7 @@ export default function Page() { - {(season === null - ? ROWS_OPTIONS_CURRENT - : ROWS_OPTIONS_ARCHIVED - ).map((size) => ( + {ROWS_OPTIONS.map((size) => ( {size} per page @@ -480,11 +538,7 @@ export default function Page() { }) satisfies BlitzkriegRatingsLeaderboardEntry, ); } else { - if ( - !ratingsInfo || - ratingsInfo.detail || - !players.data - ) + if (!ratingsInfo || ratingsInfo.detail || !players) return; const minScore = @@ -496,7 +550,7 @@ export default function Page() { index < ratingsInfo.count; index++ ) { - if (players.data[index].score < minScore) { + if (players[index].score < minScore) { firstPlayerIndex = index; break; } @@ -505,9 +559,7 @@ export default function Page() { if (firstPlayerIndex === -1) return; setPage(Math.floor(firstPlayerIndex / rowsPerPage)); - setHighlightedPlayerId( - players.data[firstPlayerIndex].id, - ); + setHighlightedPlayerId(players[firstPlayerIndex].id); } }} > @@ -551,7 +603,7 @@ export default function Page() { index < ratingsInfo.count; index++ ) { - if (players.data![index].id === player.account_id) { + if (players![index].id === player.account_id) { playerIndex = index; break; } @@ -591,7 +643,7 @@ export default function Page() { - {players.isLoading ? ( + {players === null ? ( ) : ( range( @@ -603,22 +655,22 @@ export default function Page() { ).map((index) => ( )) )} diff --git a/src/core/blitz/getRatingsNeighbors.ts b/src/core/blitz/getRatingsNeighbors.ts new file mode 100644 index 000000000..407627e54 --- /dev/null +++ b/src/core/blitz/getRatingsNeighbors.ts @@ -0,0 +1,24 @@ +import { RatingsNeighbors } from '../../commands/ratings'; +import { Region } from '../../constants/regions'; +import { patientFetch } from '../blitzkrieg/patientFetch'; +import { withCORSProxy } from '../blitzkrieg/withCORSProxy'; +import regionToRegionSubdomain from './regionToRegionSubdomain'; + +export async function getRatingsNeighbors( + region: Region, + id: number, + count = 30, + usePatientFetcher = false, +) { + const fetcher = usePatientFetcher ? patientFetch : fetch; + const response = await fetcher( + withCORSProxy( + `https://${regionToRegionSubdomain( + region, + )}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${count}`, + ), + ); + const json = (await response.json()) as RatingsNeighbors; + + return json; +} From d08949c5978dfc027673858ca1100d009f2ffd10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 8 Nov 2023 11:16:51 -0600 Subject: [PATCH 40/88] feat: jump to league for current season --- src/app/tools/ratings/page.tsx | 187 +++++++++--------- .../Leaderboard/components/Item.tsx | 24 +-- src/core/blitzkrieg/withCORSProxy.ts | 2 + 3 files changed, 105 insertions(+), 108 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index c8a020b63..172e4bf66 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -58,6 +58,7 @@ export default function Page() { const usernameCache = useUsernameCache(); const clanCache = useClanCache(); const [region, setRegion] = useState('com'); + const leaguePositionCache = useRef>({}); // null being the latest season const [season, setSeason] = useState(null); const { data: ratingsInfo } = useSWR( @@ -110,39 +111,36 @@ export default function Page() { useEffect(() => { // league caching if (season === null) { - Promise.all( - range(0, 4).map(async (league: number) => { - const leaguePlayers = await getRatingsLeague(region, league); - useUsernameCache.setState( - produce((draft: UsernameCache) => { - leaguePlayers.result.forEach((player) => { - draft[region][player.spa_id] = player.nickname; - }); - }), - ); - useClanCache.setState( - produce((draft: ClanCache) => { - leaguePlayers.result.forEach((player) => { - draft[region][player.spa_id] = player.clan_tag; - }); - }), - ); + (async () => { + const newUsernameCache: Record = {}; + const newClanCache: Record = {}; + const results = await Promise.all( + range(0, 5).map(async (league: number) => { + const leaguePlayers = await getRatingsLeague(region, league); - return leaguePlayers.result.reduce< - Record - >( - (accumulator, player) => ({ - ...accumulator, + leaguePositionCache.current[league] = + leaguePlayers.result[0].number - 1; - [player.number - 1]: { - id: player.spa_id, - score: player.score, - } satisfies BlitzkriegRatingsLeaderboardEntry, - }), - {}, - ); - }), - ).then((results) => { + leaguePlayers.result.forEach((player) => { + newUsernameCache[player.spa_id] = player.nickname; + newClanCache[player.spa_id] = player.clan_tag; + }); + + return leaguePlayers.result.reduce< + Record + >( + (accumulator, player) => ({ + ...accumulator, + + [player.number - 1]: { + id: player.spa_id, + score: player.score, + } satisfies BlitzkriegRatingsLeaderboardEntry, + }), + {}, + ); + }), + ); const newPlayers = results.reduce< Record >( @@ -154,13 +152,59 @@ export default function Page() { ); setPlayers(newPlayers); - }); + useUsernameCache.setState((old) => ({ + ...old, + [region]: { ...old[region], ...newUsernameCache }, + })); + useClanCache.setState((old) => ({ + ...old, + [region]: { ...old[region], ...newClanCache }, + })); + })(); } else { getArchivedRatingsLeaderboard(region, season).then((result) => setPlayers({ ...result }), ); } }, []); + + async function cacheNeighbors(id: number, size: number) { + const { neighbors } = await getRatingsNeighbors(region, id, size); + + setPlayers( + produce((draft) => { + draft = draft ?? {}; + + neighbors.forEach((neighbor) => { + draft![neighbor.number - 1] = { + id: neighbor.spa_id, + score: neighbor.score, + } satisfies BlitzkriegRatingsLeaderboardEntry; + }); + }), + ); + + useUsernameCache.setState( + produce((draft: UsernameCache) => { + neighbors.forEach((neighbor) => { + if (neighbor.nickname) { + draft[region][neighbor.spa_id] = neighbor.nickname; + } + }); + }), + ); + + useClanCache.setState( + produce((draft: ClanCache) => { + neighbors.forEach((neighbor) => { + if (neighbor.clan_tag) { + draft[region][neighbor.spa_id] = neighbor.clan_tag; + } + }); + }), + ); + } + useEffect(() => { // TODO: caches twice on initial load if (!players || !ratingsInfo || ratingsInfo?.detail) return; @@ -172,42 +216,7 @@ export default function Page() { const middleId = players[middleIndex].id; - getRatingsNeighbors(region, middleId, rowsPerPage * 3).then( - ({ neighbors }) => { - setPlayers( - produce((draft) => { - draft = draft ?? {}; - - neighbors.forEach((neighbor) => { - draft![neighbor.number - 1] = { - id: neighbor.spa_id, - score: neighbor.score, - } satisfies BlitzkriegRatingsLeaderboardEntry; - }); - }), - ); - - useUsernameCache.setState( - produce((draft: UsernameCache) => { - neighbors.forEach((neighbor) => { - if (neighbor.nickname) { - draft[region][neighbor.spa_id] = neighbor.nickname; - } - }); - }), - ); - - useClanCache.setState( - produce((draft: ClanCache) => { - neighbors.forEach((neighbor) => { - if (neighbor.clan_tag) { - draft[region][neighbor.spa_id] = neighbor.clan_tag; - } - }); - }), - ); - }, - ); + cacheNeighbors(middleId, rowsPerPage * 3); } else { const ids = range( Math.max(0, (page - 1) * rowsPerPage), @@ -244,7 +253,7 @@ export default function Page() { }); } } - }, [page, players]); + }, [page]); const handleJumpToPosition = () => { if (!players || !ratingsInfo || ratingsInfo?.detail) return; @@ -509,34 +518,18 @@ export default function Page() {
); diff --git a/src/core/blitzkrieg/withCORSProxy.ts b/src/core/blitzkrieg/withCORSProxy.ts index cce2d45ad..74b58b145 100644 --- a/src/core/blitzkrieg/withCORSProxy.ts +++ b/src/core/blitzkrieg/withCORSProxy.ts @@ -4,4 +4,6 @@ export function withCORSProxy(url: string) { return context === 'website' ? `https://corsproxy.io/?${encodeURIComponent(url)}` : url; + + // `https://api.allorigins.win/raw?url=${encodeURIComponent(url)}`; } From 81aec6bad20a5629789a27b888745ec2afed062b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:17:08 -0600 Subject: [PATCH 41/88] feat: search players for current season --- src/app/tools/ratings/page.tsx | 254 ++++++++++++++----- src/core/blitz/searchPlayersAcrossRegions.ts | 6 +- 2 files changed, 197 insertions(+), 63 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 172e4bf66..8257262d0 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -31,10 +31,15 @@ import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; import { getRatingsLeague } from '../../../core/blitz/getRatingsLeague'; import { getRatingsNeighbors } from '../../../core/blitz/getRatingsNeighbors'; -import { AccountList } from '../../../core/blitz/searchPlayersAcrossRegions'; +import regionToRegionSubdomain from '../../../core/blitz/regionToRegionSubdomain'; +import { + AccountList, + AccountListItem, +} from '../../../core/blitz/searchPlayersAcrossRegions'; import { getArchivedLatestSeasonNumber } from '../../../core/blitzkrieg/getArchivedLatestSeasonNumber'; import getArchivedRatingsInfo from '../../../core/blitzkrieg/getArchivedRatingsInfo'; import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; +import { withCORSProxy } from '../../../core/blitzkrieg/withCORSProxy'; import { noArrows } from './page.css'; const ROWS_OPTIONS = [5, 10, 15, 25, 30]; @@ -83,30 +88,9 @@ export default function Page() { ); const positionInput = useRef(null); const scoreInput = useRef(null); - const [searchResults, setSearchResults] = useState( - undefined, - ); - const handleSearchPlayerChange = debounce( - async (event: ChangeEvent) => { - const trimmedSearch = event.target.value.trim(); - - if (trimmedSearch) { - const encodedSearch = encodeURIComponent(trimmedSearch); - const accountList = await fetchBlitz( - `https://api.wotblitz.${region}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=100`, - ); - - setSearchResults( - accountList.filter( - (searchedPlayer) => players && searchedPlayer.account_id in players, - ), - ); - } else { - setSearchResults(undefined); - } - }, - 500, - ); + const [searchResults, setSearchResults] = useState< + (AccountListItem & { number: number })[] | undefined + >(undefined); useEffect(() => { // league caching @@ -168,6 +152,12 @@ export default function Page() { } }, []); + useEffect(() => { + if (players !== null && 0 in players) { + cacheNeighbors(players[0].id, rowsPerPage * 3); + } + }, [players !== null && 0 in players]); + async function cacheNeighbors(id: number, size: number) { const { neighbors } = await getRatingsNeighbors(region, id, size); @@ -292,6 +282,7 @@ export default function Page() { (ratingsInfo && ratingsInfo.detail === undefined ? ratingsInfo?.count : 1) / rowsPerPage, ); + const [searchLoading, setSearchLoading] = useState(false); useEffect(() => { setPage(0); @@ -431,7 +422,6 @@ export default function Page() { { @@ -465,7 +455,6 @@ export default function Page() { { @@ -576,48 +565,193 @@ export default function Page() { ) => { + const trimmedSearch = event.target.value.trim(); + const encodedSearch = encodeURIComponent(trimmedSearch); + + if (trimmedSearch) { + if (season === null) { + setSearchLoading(true); + + const response = await fetch( + withCORSProxy( + `https://${regionToRegionSubdomain( + region, + )}.wotblitz.com/en/api/rating-leaderboards/search/?prefix=${encodedSearch}`, + ), + ); + const accountList = + (await response.json()) as Record< + number, + { + spa_id: number; + nickname: string; + clan_tag: string; + } & ( + | { + skip: true; + } + | { + skip: false; + mmr: number; + season_number: number; + calibrationBattlesLeft: number; + number: number; + updated_at: string; + neighbors: []; + } + ) + >; + const values = Object.values(accountList).filter( + (searchedPlayer) => !searchedPlayer.skip, + ); + const valuesWithTypes = + values as ((typeof values)[number] & { + skip: false; + })[]; + + setPlayers( + produce((draft: typeof players) => { + if (draft) { + values.map((player) => { + const playerWithTypes = + player as typeof player & { skip: false }; + draft[playerWithTypes.number - 1] = { + id: playerWithTypes.spa_id, + score: 0, + }; + }); + } + }), + ); + useUsernameCache.setState( + produce((draft: typeof usernameCache) => { + valuesWithTypes.forEach((searchedPlayer) => { + if (searchedPlayer.nickname) { + draft[region][searchedPlayer.spa_id] = + searchedPlayer.nickname; + } + }); + }), + ); + useClanCache.setState( + produce((draft: typeof clanCache) => { + valuesWithTypes.forEach((searchedPlayer) => { + if (searchedPlayer.clan_tag) { + draft[region][searchedPlayer.spa_id] = + searchedPlayer.clan_tag; + } + }); + }), + ); + setSearchResults( + valuesWithTypes.map((searchedPlayer) => ({ + account_id: searchedPlayer.spa_id, + nickname: searchedPlayer.nickname, + number: searchedPlayer.number, + })), + ); + setSearchLoading(false); + } else { + setSearchLoading(true); + const accountList = await fetchBlitz( + `https://api.wotblitz.${region}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=100`, + ); + const playerKeys = Object.keys(players!); + + setSearchResults( + accountList + .filter( + (searchedPlayer) => + players && + searchedPlayer.account_id in players, + ) + .map((searchedPlayer) => ({ + ...searchedPlayer, + number: parseInt( + playerKeys.find( + (index) => + players![parseInt(index)].id === + searchedPlayer.account_id, + )!, + ), + })), + ); + setSearchLoading(false); + } + } else { + setSearchResults(undefined); + } + }, + 500, + )} placeholder="Search for player..." /> - {searchResults?.map((player) => ( - - + + ))} - const playerPage = Math.floor( - playerIndex! / rowsPerPage, - ); - - setPage(playerPage); - setHighlightedPlayerId(player.account_id); - }} - > - {player.nickname} - - - ))} - - {searchResults?.length === 0 && ( + {!searchLoading && searchResults?.length === 0 && ( + )} + + {!searchLoading && searchResults === undefined && ( + + )} + + {searchLoading && ( + )} diff --git a/src/core/blitz/searchPlayersAcrossRegions.ts b/src/core/blitz/searchPlayersAcrossRegions.ts index cfc61bb89..98dc76754 100644 --- a/src/core/blitz/searchPlayersAcrossRegions.ts +++ b/src/core/blitz/searchPlayersAcrossRegions.ts @@ -2,12 +2,12 @@ import { Region } from '../../constants/regions'; import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID'; import fetchBlitz from './fetchBlitz'; -interface Account { +export interface AccountListItem { nickname: string; account_id: number; } -export type AccountList = Account[]; -export type AccountListWithServer = (Account & { +export type AccountList = AccountListItem[]; +export type AccountListWithServer = (AccountListItem & { region: Region; })[]; From 551ef79f0391eb221b428c4f30d7a463592b2d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 8 Nov 2023 17:50:00 -0600 Subject: [PATCH 42/88] feat: jump to position for current season --- src/app/tools/ratings/page.tsx | 132 +++++++++++++++++++++++++++++++-- 1 file changed, 127 insertions(+), 5 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 8257262d0..9abab8dc5 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -1,5 +1,6 @@ 'use client'; +import { blackA, orangeDark } from '@radix-ui/colors'; import { CaretLeftIcon, CaretRightIcon, @@ -19,6 +20,7 @@ import { ChangeEvent, useEffect, useRef, useState } from 'react'; import useSWR from 'swr'; import { create } from 'zustand'; import { BlitzkriegRatingsLeaderboardEntry } from '../../../../scripts/buildRatingsLeaderboard'; +import { RatingsPlayer } from '../../../commands/ratings'; import * as Leaderboard from '../../../components/Leaderboard'; import PageWrapper from '../../../components/PageWrapper'; import { LEAGUES } from '../../../constants/leagues'; @@ -43,6 +45,7 @@ import { withCORSProxy } from '../../../core/blitzkrieg/withCORSProxy'; import { noArrows } from './page.css'; const ROWS_OPTIONS = [5, 10, 15, 25, 30]; +const SEEDING_SIZE = 2 ** 7; type UsernameCache = Record>; type ClanCache = Record>; @@ -91,6 +94,9 @@ export default function Page() { const [searchResults, setSearchResults] = useState< (AccountListItem & { number: number })[] | undefined >(undefined); + const [loadingProgress, setLoadingProgress] = useState< + [number, number] | null + >(null); useEffect(() => { // league caching @@ -193,6 +199,8 @@ export default function Page() { }); }), ); + + return neighbors; } useEffect(() => { @@ -245,15 +253,85 @@ export default function Page() { } }, [page]); - const handleJumpToPosition = () => { + const handleJumpToPosition = async () => { if (!players || !ratingsInfo || ratingsInfo?.detail) return; const rawPosition = positionInput.current!.valueAsNumber - 1; - const position = Math.max(0, Math.min(rawPosition, ratingsInfo.count - 1)); - setPage(Math.floor(position / rowsPerPage)); + const targetPosition = Math.max( + 0, + Math.min(rawPosition, ratingsInfo.count - 1), + ); - if (players) { - setHighlightedPlayerId(players[position].id); + if (season === null) { + const playerEntries = Object.entries(players); + let closest: BlitzkriegRatingsLeaderboardEntry | null = null; + let closestPosition: number | null = null; + let closestPositionDistance = Infinity; + + playerEntries.forEach(([positionAsString, player]) => { + const position = parseInt(positionAsString); + const distance = Math.abs(position - targetPosition); + + if (distance < closestPositionDistance) { + closestPositionDistance = distance; + closestPosition = position; + closest = player; + } + }); + + if (closest === null || closestPosition === null) return; + + if (closestPositionDistance === 0) { + setHighlightedPlayerId( + (closest as BlitzkriegRatingsLeaderboardEntry).id, + ); + setPage(Math.floor(targetPosition / rowsPerPage)); + + return; + } + + // 1 going down and -1 going up + const loadingDirection = Math.sign(targetPosition - closestPosition); + let seedingPlayer = (closest as BlitzkriegRatingsLeaderboardEntry).id; + let targetPositionAcquired = false; + let neighbors: RatingsPlayer[]; + + setLoadingProgress([0, closestPositionDistance]); + + while (!targetPositionAcquired) { + console.log('starting'); + neighbors = await cacheNeighbors(seedingPlayer, SEEDING_SIZE); + console.log('done'); + seedingPlayer = neighbors.at(loadingDirection === -1 ? 0 : -1)!.spa_id; + + if (loadingDirection === 1) { + targetPositionAcquired = + targetPosition <= neighbors.at(-1)!.number - 1; + setLoadingProgress([ + Math.abs(neighbors.at(-1)!.number - 1 - closestPosition), + closestPositionDistance, + ]); + } else { + targetPositionAcquired = targetPosition >= neighbors[0].number - 1; + setLoadingProgress([ + Math.abs(neighbors[0].number - 1 - closestPosition), + closestPositionDistance, + ]); + } + } + + setLoadingProgress(null); + setPage(Math.floor(targetPosition / rowsPerPage)); + setHighlightedPlayerId( + neighbors!.find((neighbor) => neighbor.number - 1 === targetPosition)! + .spa_id, + ); + } else { + setPage(Math.floor(targetPosition / rowsPerPage)); + + if (players) { + setHighlightedPlayerId(players[targetPosition].id); + } } }; const [jumpToPositionOpen, setJumpToPositionOpen] = useState(false); @@ -804,6 +882,50 @@ export default function Page() { + + {loadingProgress !== null && ( + + + Loading {loadingProgress[0]} of {loadingProgress[1]} players... + + +
+
+
+ + )} ); } From 18a5fb80d0856993069c343d99e22a6f3ed1bc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 8 Nov 2023 19:28:22 -0600 Subject: [PATCH 43/88] feat: more code polish --- .../tools/ratings/components/PageTurner.tsx | 62 +++ src/app/tools/ratings/page.css.ts | 9 - src/app/tools/ratings/page.tsx | 421 ++++++++---------- src/core/blitzkrieg/withCORSProxy.ts | 2 - 4 files changed, 257 insertions(+), 237 deletions(-) create mode 100644 src/app/tools/ratings/components/PageTurner.tsx delete mode 100644 src/app/tools/ratings/page.css.ts diff --git a/src/app/tools/ratings/components/PageTurner.tsx b/src/app/tools/ratings/components/PageTurner.tsx new file mode 100644 index 000000000..f8bdf0fd5 --- /dev/null +++ b/src/app/tools/ratings/components/PageTurner.tsx @@ -0,0 +1,62 @@ +import { CaretLeftIcon, CaretRightIcon } from '@radix-ui/react-icons'; +import { Button, Flex, TextField } from '@radix-ui/themes'; +import { useEffect, useRef } from 'react'; + +interface PageTurnerProps { + page: number; + pages: number; + setPage: (page: number | ((page: number) => number)) => void; +} + +export function PageTurner({ page, setPage, pages }: PageTurnerProps) { + const pageInput = useRef(null); + + useEffect(() => { + if (pageInput.current) pageInput.current.value = `${page + 1}`; + }, [page]); + + return ( + + + + + Page + { + setPage( + Math.max( + 0, + Math.min(pages - 1, event.target.valueAsNumber - 1), + ), + ); + }} + onKeyDown={(event) => { + if (event.key === 'Enter') { + (event.target as HTMLInputElement).blur(); + } + }} + /> + out of {pages} + + + + + ); +} diff --git a/src/app/tools/ratings/page.css.ts b/src/app/tools/ratings/page.css.ts deleted file mode 100644 index 057061637..000000000 --- a/src/app/tools/ratings/page.css.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { style } from '@vanilla-extract/css'; - -export const noArrows = style({ - '::-webkit-progress-inner-value': { - appearance: 'none', - display: 'none', - margin: 0, - }, -}); diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 9abab8dc5..79c13574b 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -1,11 +1,7 @@ 'use client'; import { blackA, orangeDark } from '@radix-ui/colors'; -import { - CaretLeftIcon, - CaretRightIcon, - MagnifyingGlassIcon, -} from '@radix-ui/react-icons'; +import { MagnifyingGlassIcon } from '@radix-ui/react-icons'; import { Button, Dialog, @@ -42,13 +38,17 @@ import { getArchivedLatestSeasonNumber } from '../../../core/blitzkrieg/getArchi import getArchivedRatingsInfo from '../../../core/blitzkrieg/getArchivedRatingsInfo'; import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; import { withCORSProxy } from '../../../core/blitzkrieg/withCORSProxy'; -import { noArrows } from './page.css'; +import { PageTurner } from './components/PageTurner'; const ROWS_OPTIONS = [5, 10, 15, 25, 30]; const SEEDING_SIZE = 2 ** 7; type UsernameCache = Record>; type ClanCache = Record>; +type PlayersCache = Record< + Region, + Record> +>; const useUsernameCache = create(() => ({ asia: {}, @@ -60,57 +60,71 @@ const useClanCache = create(() => ({ com: {}, eu: {}, })); +const usePlayersCache = create(() => ({ + asia: { 0: {} }, + com: { 0: {} }, + eu: { 0: {} }, +})); export default function Page() { - const [rowsPerPage, setRowsPerPage] = useState(25); - const usernameCache = useUsernameCache(); - const clanCache = useClanCache(); const [region, setRegion] = useState('com'); - const leaguePositionCache = useRef>({}); - // null being the latest season - const [season, setSeason] = useState(null); - const { data: ratingsInfo } = useSWR( - `ratings-info-${region}-${season}`, - () => - season === null - ? getRatingsInfo(region) - : getArchivedRatingsInfo(region, season), - ); - const [jumpToLeague, setJumpToLeague] = useState(0); - const { data: latestArchivedSeasonNumber } = useSWR( - getArchivedLatestSeasonNumber.name, - getArchivedLatestSeasonNumber, - ); - const [players, setPlayers] = useState>(null); + const [season, setSeason] = useState(0); const [page, setPage] = useState(0); - const [highlightedPlayerId, setHighlightedPlayerId] = useState( - null, - ); - const positionInput = useRef(null); - const scoreInput = useRef(null); + const [rowsPerPage, setRowsPerPage] = useState(25); + const [jumpToLeague, setJumpToLeague] = useState(0); + const [searchLoading, setSearchLoading] = useState(false); + const [jumpToScoreOpen, setJumpToScoreOpen] = useState(false); + const [jumpToPositionOpen, setJumpToPositionOpen] = useState(false); const [searchResults, setSearchResults] = useState< (AccountListItem & { number: number })[] | undefined >(undefined); const [loadingProgress, setLoadingProgress] = useState< [number, number] | null >(null); + const [highlightedPlayerId, setHighlightedPlayerId] = useState( + null, + ); + const { data: ratingsInfo } = useSWR( + `ratings-info-${region}-${season}`, + () => + season === 0 + ? getRatingsInfo(region) + : getArchivedRatingsInfo(region, season), + ); + const { data: latestArchivedSeasonNumber } = useSWR( + getArchivedLatestSeasonNumber.name, + getArchivedLatestSeasonNumber, + ); + + const usernames = useUsernameCache(); + const clans = useClanCache(); + const players = usePlayersCache(); + + const positionInput = useRef(null); + const scoreInput = useRef(null); + const leaguePositionCache = useRef>({}); + + const pages = Math.ceil( + (ratingsInfo && ratingsInfo.detail === undefined ? ratingsInfo?.count : 1) / + rowsPerPage, + ); + + // page reset on season or region change useEffect(() => { - // league caching - if (season === null) { + setPage(0); + }, [season, region]); + // initial leagues caching for current season + useEffect(() => { + if (season === 0) { (async () => { const newUsernameCache: Record = {}; const newClanCache: Record = {}; const results = await Promise.all( range(0, 5).map(async (league: number) => { const leaguePlayers = await getRatingsLeague(region, league); - leaguePositionCache.current[league] = leaguePlayers.result[0].number - 1; - leaguePlayers.result.forEach((player) => { newUsernameCache[player.spa_id] = player.nickname; newClanCache[player.spa_id] = player.clan_tag; @@ -141,87 +155,61 @@ export default function Page() { {}, ); - setPlayers(newPlayers); - useUsernameCache.setState((old) => ({ - ...old, - [region]: { ...old[region], ...newUsernameCache }, - })); - useClanCache.setState((old) => ({ - ...old, - [region]: { ...old[region], ...newClanCache }, - })); + usePlayersCache.setState( + produce((draft: PlayersCache) => { + draft[region][season] = { + ...draft[region][season], + ...newPlayers, + }; + }), + ); + useUsernameCache.setState( + produce((draft: UsernameCache) => { + draft[region] = { ...draft[region], ...newUsernameCache }; + }), + ); + useClanCache.setState( + produce((draft: ClanCache) => { + draft[region] = { ...draft[region], ...newClanCache }; + }), + ); })(); } else { - getArchivedRatingsLeaderboard(region, season).then((result) => - setPlayers({ ...result }), - ); + getArchivedRatingsLeaderboard(region, season).then((result) => { + usePlayersCache.setState( + produce((draft: PlayersCache) => { + draft[region][season] = { + ...draft[region][season], + ...result, + }; + }), + ); + }); } }, []); - + // 2nd page caching for current season useEffect(() => { - if (players !== null && 0 in players) { - cacheNeighbors(players[0].id, rowsPerPage * 3); + if (players !== null && 0 in players[region][season]) { + cacheNeighbors(players[region][season][0].id, rowsPerPage * 3); } - }, [players !== null && 0 in players]); - - async function cacheNeighbors(id: number, size: number) { - const { neighbors } = await getRatingsNeighbors(region, id, size); - - setPlayers( - produce((draft) => { - draft = draft ?? {}; - - neighbors.forEach((neighbor) => { - draft![neighbor.number - 1] = { - id: neighbor.spa_id, - score: neighbor.score, - } satisfies BlitzkriegRatingsLeaderboardEntry; - }); - }), - ); - - useUsernameCache.setState( - produce((draft: UsernameCache) => { - neighbors.forEach((neighbor) => { - if (neighbor.nickname) { - draft[region][neighbor.spa_id] = neighbor.nickname; - } - }); - }), - ); - - useClanCache.setState( - produce((draft: ClanCache) => { - neighbors.forEach((neighbor) => { - if (neighbor.clan_tag) { - draft[region][neighbor.spa_id] = neighbor.clan_tag; - } - }); - }), - ); - - return neighbors; - } - + }, [0 in players[region][season]]); + // neighboring page caching for all seasons useEffect(() => { - // TODO: caches twice on initial load - if (!players || !ratingsInfo || ratingsInfo?.detail) return; + if (!ratingsInfo || ratingsInfo?.detail) return; - if (season === null) { + if (season === 0) { const middleIndex = Math.round(page * rowsPerPage + rowsPerPage / 2); - - if (!(middleIndex in players)) return; - - const middleId = players[middleIndex].id; - + if (!(middleIndex in players[region][season])) return; + const middleId = players[region][season][middleIndex].id; cacheNeighbors(middleId, rowsPerPage * 3); } else { const ids = range( Math.max(0, (page - 1) * rowsPerPage), Math.min(ratingsInfo.count - 1, (page + 1) * rowsPerPage + rowsPerPage), ) - .map((index) => players![index].id) - .filter((id) => !(id in usernameCache[region])); + .map((index) => players[region][season][index].id) + .filter(Boolean) + .filter((id) => !(id in usernames[region])); if (ids && ids.length > 0) { getAccountInfo(region, ids).then((data) => { @@ -253,8 +241,44 @@ export default function Page() { } }, [page]); - const handleJumpToPosition = async () => { - if (!players || !ratingsInfo || ratingsInfo?.detail) return; + async function cacheNeighbors(id: number, size: number) { + const { neighbors } = await getRatingsNeighbors(region, id, size); + + usePlayersCache.setState( + produce((draft: PlayersCache) => { + neighbors.forEach((neighbor) => { + draft[region][season][neighbor.number - 1] = { + id: neighbor.spa_id, + score: neighbor.score, + } satisfies BlitzkriegRatingsLeaderboardEntry; + }); + }), + ); + + useUsernameCache.setState( + produce((draft: UsernameCache) => { + neighbors.forEach((neighbor) => { + if (neighbor.nickname) { + draft[region][neighbor.spa_id] = neighbor.nickname; + } + }); + }), + ); + + useClanCache.setState( + produce((draft: ClanCache) => { + neighbors.forEach((neighbor) => { + if (neighbor.clan_tag) { + draft[region][neighbor.spa_id] = neighbor.clan_tag; + } + }); + }), + ); + + return neighbors; + } + async function handleJumpToPosition() { + if (!ratingsInfo || ratingsInfo?.detail) return; const rawPosition = positionInput.current!.valueAsNumber - 1; const targetPosition = Math.max( @@ -262,8 +286,8 @@ export default function Page() { Math.min(rawPosition, ratingsInfo.count - 1), ); - if (season === null) { - const playerEntries = Object.entries(players); + if (season === 0) { + const playerEntries = Object.entries(players[region][season]); let closest: BlitzkriegRatingsLeaderboardEntry | null = null; let closestPosition: number | null = null; let closestPositionDistance = Infinity; @@ -299,9 +323,7 @@ export default function Page() { setLoadingProgress([0, closestPositionDistance]); while (!targetPositionAcquired) { - console.log('starting'); neighbors = await cacheNeighbors(seedingPlayer, SEEDING_SIZE); - console.log('done'); seedingPlayer = neighbors.at(loadingDirection === -1 ? 0 : -1)!.spa_id; if (loadingDirection === 1) { @@ -330,18 +352,20 @@ export default function Page() { setPage(Math.floor(targetPosition / rowsPerPage)); if (players) { - setHighlightedPlayerId(players[targetPosition].id); + setHighlightedPlayerId(players[region][season][targetPosition].id); } } - }; - const [jumpToPositionOpen, setJumpToPositionOpen] = useState(false); - const handleJumpToScore = () => { - if (!players || !ratingsInfo || ratingsInfo?.detail) return; + } + function handleJumpToScore() { + if (!ratingsInfo || ratingsInfo?.detail) return; let playerIndex = -1; for (let index = ratingsInfo.count - 1; index >= 0; index--) { - if (players[index].score >= scoreInput.current!.valueAsNumber) { + if ( + players[region][season][index].score >= + scoreInput.current!.valueAsNumber + ) { playerIndex = index; break; } @@ -352,75 +376,8 @@ export default function Page() { setPage(Math.floor(playerIndex / rowsPerPage)); if (players) { - setHighlightedPlayerId(players[playerIndex].id); + setHighlightedPlayerId(players[region][season][playerIndex].id); } - }; - const [jumpToScoreOpen, setJumpToScoreOpen] = useState(false); - const pages = Math.ceil( - (ratingsInfo && ratingsInfo.detail === undefined ? ratingsInfo?.count : 1) / - rowsPerPage, - ); - const [searchLoading, setSearchLoading] = useState(false); - - useEffect(() => { - setPage(0); - }, [season, region]); - useEffect(() => { - if (season === null && rowsPerPage > 30) setRowsPerPage(25); - }, [season]); - - function PageTurner() { - const pageInput = useRef(null); - - useEffect(() => { - if (pageInput.current) pageInput.current.value = `${page + 1}`; - }, [page]); - - return ( - - - - - Page - { - setPage( - Math.max( - 0, - Math.min(pages - 1, event.target.valueAsNumber - 1), - ), - ); - }} - onKeyDown={(event) => { - if (event.key === 'Enter') { - (event.target as HTMLInputElement).blur(); - } - }} - /> - out of {pages} - - - - - ); } return ( @@ -444,15 +401,13 @@ export default function Page() { - setSeason(value === 'null' ? null : parseInt(value)) - } + onValueChange={(value) => setSeason(parseInt(value))} > {ratingsInfo?.detail === undefined && ( - Current season + Current season )} @@ -584,16 +539,18 @@ export default function Page() {
+ + + Please be patient. Wargaming is slow. + + Players are loaded in batches of {SEEDING_SIZE}. + +
)}
From 4b6de934a5b7ad140dca846148c193d8b01a1204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 11 Nov 2023 20:51:48 -0600 Subject: [PATCH 45/88] feat: current season already cached checking --- next.config.js | 1 + src/app/tools/ratings/page.tsx | 173 ++++++++++++++++----------------- 2 files changed, 83 insertions(+), 91 deletions(-) diff --git a/next.config.js b/next.config.js index 5e8d200ea..4f8efe754 100644 --- a/next.config.js +++ b/next.config.js @@ -7,6 +7,7 @@ const nextConfig = { ...withVanillaExtract(), distDir: 'dist/website', + reactStrictMode: false, }; export default nextConfig; diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 1a38fa2d6..647394252 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -24,7 +24,6 @@ import { FIRST_ARCHIVED_RATINGS_SEASON } from '../../../constants/ratings'; import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID'; import fetchBlitz from '../../../core/blitz/fetchBlitz'; -import { getAccountInfo } from '../../../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; import { getRatingsLeague } from '../../../core/blitz/getRatingsLeague'; @@ -43,27 +42,25 @@ import { PageTurner } from './components/PageTurner'; const ROWS_OPTIONS = [5, 10, 15, 25, 30]; const SEEDING_SIZE = 2 ** 7; -type UsernameCache = Record>; -type ClanCache = Record>; +interface NameCacheEntry { + nickname: string; + clan?: string; +} +type NameCache = Record>; type PlayersCache = Record< Region, Record> >; -const useUsernameCache = create(() => ({ - asia: {}, - com: {}, - eu: {}, -})); -const useClanCache = create(() => ({ +const useNameCache = create(() => ({ asia: {}, com: {}, eu: {}, })); const usePlayersCache = create(() => ({ - asia: { 0: {} }, - com: { 0: {} }, - eu: { 0: {} }, + asia: {}, + com: {}, + eu: {}, })); export default function Page() { @@ -97,8 +94,7 @@ export default function Page() { getArchivedLatestSeasonNumber, ); - const usernames = useUsernameCache(); - const clans = useClanCache(); + const names = useNameCache(); const players = usePlayersCache(); const positionInput = useRef(null); @@ -116,18 +112,21 @@ export default function Page() { }, [season, region]); // initial leagues caching for current season useEffect(() => { + if (season in players[region]) return; + if (season === 0) { (async () => { - const newUsernameCache: Record = {}; - const newClanCache: Record = {}; + const newNameCache: Record = {}; const results = await Promise.all( range(0, 5).map(async (league: number) => { const leaguePlayers = await getRatingsLeague(region, league); leaguePositionCache.current[league] = leaguePlayers.result[0].number - 1; leaguePlayers.result.forEach((player) => { - newUsernameCache[player.spa_id] = player.nickname; - newClanCache[player.spa_id] = player.clan_tag; + newNameCache[player.spa_id] = { + nickname: player.nickname, + clan: player.clan_tag, + }; }); return leaguePlayers.result.reduce< @@ -163,14 +162,9 @@ export default function Page() { }; }), ); - useUsernameCache.setState( - produce((draft: UsernameCache) => { - draft[region] = { ...draft[region], ...newUsernameCache }; - }), - ); - useClanCache.setState( - produce((draft: ClanCache) => { - draft[region] = { ...draft[region], ...newClanCache }; + useNameCache.setState( + produce((draft: NameCache) => { + draft[region] = { ...draft[region], ...newNameCache }; }), ); })(); @@ -186,13 +180,18 @@ export default function Page() { ); }); } - }, []); + }, [season in players[region]]); // 2nd page caching for current season useEffect(() => { - if (players !== null && 0 in players[region][season]) { + if ( + season === 0 && + players !== null && + players[region][season] && + 0 in players[region][season] + ) { cacheNeighbors(players[region][season][0].id, rowsPerPage * 3); } - }, [0 in players[region][season]]); + }, [players[region]?.[season] && 0 in players[region][season]]); // neighboring page caching for all seasons useEffect(() => { if (!ratingsInfo || ratingsInfo?.detail) return; @@ -209,40 +208,53 @@ export default function Page() { ) .map((index) => players[region][season][index].id) .filter(Boolean) - .filter((id) => !(id in usernames[region])); + .filter((id) => !(id in names[region])); if (ids && ids.length > 0) { - getAccountInfo(region, ids).then((data) => { - data.map((player, index) => { - useUsernameCache.setState( - produce((draft: UsernameCache) => { - if (player) { - draft[region][ids[index]] = player.nickname; - } else { - draft[region][ids[index]] = null; - } - }), - ); - }); - }); - getClanAccountInfo(region, ids, ['clan']).then((data) => { data.map((player, index) => { - useClanCache.setState( - produce((draft: ClanCache) => { - if (player) { - draft[region][ids[index]] = player.clan?.tag; - } + useNameCache.setState( + produce((draft: NameCache) => { + draft[region][ids[index]] = player + ? { + nickname: player.account_name, + clan: player.clan?.tag, + } + : null; }), ); }); }); } } - }, [page]); + }, [page, region, season, ratingsInfo?.detail]); - async function cacheNeighbors(id: number, size: number) { + async function cacheNeighbors( + id: number, + size: number, + ): Promise { const { neighbors } = await getRatingsNeighbors(region, id, size); + const radius = Math.round(size / 2); + + const targetPosition = Object.entries(players[region][season]).find( + ([, player]) => id === player.id, + )?.[0]; + + if (targetPosition !== undefined) { + let isMissing = false; + const targetPositionAsNumber = parseInt(targetPosition); + + for ( + let position = Math.max(0, targetPositionAsNumber - radius); + position <= targetPositionAsNumber + radius; + position++ + ) { + isMissing = !(position in players[region][season]); + if (isMissing) break; + } + + if (!isMissing) return []; + } usePlayersCache.setState( produce((draft: PlayersCache) => { @@ -255,21 +267,14 @@ export default function Page() { }), ); - useUsernameCache.setState( - produce((draft: UsernameCache) => { + useNameCache.setState( + produce((draft: NameCache) => { neighbors.forEach((neighbor) => { if (neighbor.nickname) { - draft[region][neighbor.spa_id] = neighbor.nickname; - } - }); - }), - ); - - useClanCache.setState( - produce((draft: ClanCache) => { - neighbors.forEach((neighbor) => { - if (neighbor.clan_tag) { - draft[region][neighbor.spa_id] = neighbor.clan_tag; + draft[region][neighbor.spa_id] = { + nickname: neighbor.nickname, + clan: neighbor.clan_tag, + }; } }); }), @@ -666,22 +671,14 @@ export default function Page() { } }), ); - useUsernameCache.setState( - produce((draft: typeof usernames) => { + useNameCache.setState( + produce((draft: typeof names) => { valuesWithTypes.forEach((searchedPlayer) => { if (searchedPlayer.nickname) { - draft[region][searchedPlayer.spa_id] = - searchedPlayer.nickname; - } - }); - }), - ); - useClanCache.setState( - produce((draft: typeof clans) => { - valuesWithTypes.forEach((searchedPlayer) => { - if (searchedPlayer.clan_tag) { - draft[region][searchedPlayer.spa_id] = - searchedPlayer.clan_tag; + draft[region][searchedPlayer.spa_id] = { + nickname: searchedPlayer.nickname, + clan: searchedPlayer.clan_tag, + }; } }); }), @@ -824,26 +821,20 @@ export default function Page() { ratingsInfo?.detail ? 0 : (ratingsInfo?.count ?? 1) - 1, ), ).map((index) => { - const id = players[region][season][index]?.id; + const id = players[region][season]?.[index]?.id; return ( ); }) From 7e4f195f9495b76efc6f496295649332eafb0163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 11 Nov 2023 20:59:48 -0600 Subject: [PATCH 46/88] feat: optimized highlighted players --- src/app/tools/ratings/page.tsx | 57 +++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 647394252..60267809a 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -78,9 +78,9 @@ export default function Page() { const [loadingProgress, setLoadingProgress] = useState< [number, number] | null >(null); - const [highlightedPlayerId, setHighlightedPlayerId] = useState( - null, - ); + const [highlightedPlayer, setHighlightedPlayer] = useState< + { type: 'id'; id: number } | { type: 'position'; position: number } | null + >(null); const { data: ratingsInfo } = useSWR( `ratings-info-${region}-${season}`, @@ -311,9 +311,7 @@ export default function Page() { if (closest === null || closestPosition === null) return; if (closestPositionDistance === 0) { - setHighlightedPlayerId( - (closest as BlitzkriegRatingsLeaderboardEntry).id, - ); + setHighlightedPlayer({ type: 'position', position: targetPosition }); setPage(Math.floor(targetPosition / rowsPerPage)); return; @@ -349,15 +347,15 @@ export default function Page() { setLoadingProgress(null); setPage(Math.floor(targetPosition / rowsPerPage)); - setHighlightedPlayerId( - neighbors!.find((neighbor) => neighbor.number - 1 === targetPosition)! - .spa_id, - ); + setHighlightedPlayer({ type: 'position', position: closestPosition }); } else { setPage(Math.floor(targetPosition / rowsPerPage)); if (players) { - setHighlightedPlayerId(players[region][season][targetPosition].id); + setHighlightedPlayer({ + type: 'id', + id: players[region][season][targetPosition].id, + }); } } } @@ -381,7 +379,10 @@ export default function Page() { setPage(Math.floor(playerIndex / rowsPerPage)); if (players) { - setHighlightedPlayerId(players[region][season][playerIndex].id); + setHighlightedPlayer({ + type: 'id', + id: players[region][season][playerIndex].id, + }); } } @@ -551,9 +552,10 @@ export default function Page() { setPage(newPage); if (players) { - setHighlightedPlayerId( - players[region][season][position].id, - ); + setHighlightedPlayer({ + type: 'id', + id: players[region][season][position].id, + }); cacheNeighbors( players[region][season][position].id, rowsPerPage * 3, @@ -583,9 +585,10 @@ export default function Page() { if (firstPlayerIndex === -1) return; setPage(Math.floor(firstPlayerIndex / rowsPerPage)); - setHighlightedPlayerId( - players[region][season][firstPlayerIndex].id, - ); + setHighlightedPlayer({ + type: 'id', + id: players[region][season][firstPlayerIndex].id, + }); } }} > @@ -744,7 +747,10 @@ export default function Page() { player.account_id, rowsPerPage * 3, ); - setHighlightedPlayerId(player.account_id); + setHighlightedPlayer({ + type: 'id', + id: player.account_id, + }); } else { if (!ratingsInfo || ratingsInfo.detail) return; let playerIndex = -1; @@ -768,7 +774,10 @@ export default function Page() { ); setPage(playerPage); - setHighlightedPlayerId(player.account_id); + setHighlightedPlayer({ + type: 'id', + id: player.account_id, + }); } }} > @@ -834,7 +843,13 @@ export default function Page() { score={players[region][season]?.[index]?.score} clan={id ? names[region][id]?.clan : undefined} key={index} - highlight={highlightedPlayerId === id} + highlight={ + highlightedPlayer + ? highlightedPlayer.type === 'id' + ? highlightedPlayer.id === id + : highlightedPlayer.position === index + : undefined + } /> ); }) From 14bfc3018bea7bb431313f4f8905f7ada56f8b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:37:59 -0600 Subject: [PATCH 47/88] feat: proxy APIs --- .../[region]/ratings/current/info/route.ts | 10 +++++ .../ratings/current/league/[league]/route.ts | 12 ++++++ .../current/neighbors/[id]/[count]/route.ts | 12 ++++++ .../current/search/[username]/route.ts | 12 ++++++ src/app/api/tank-averages/route.ts | 6 +++ src/app/api/tankopedia/route.ts | 6 +++ src/app/tools/ratings/page.tsx | 35 +++------------- src/core/blitz/getRatingsInfo.ts | 12 +++--- src/core/blitz/getRatingsLeague.ts | 12 +++--- src/core/blitz/getRatingsNeighbors.ts | 14 +++---- src/core/blitz/searchCurrentRatingsPlayers.ts | 42 +++++++++++++++++++ src/core/blitzkrieg/withCORSProxy.ts | 7 ---- src/core/blitzstars/tankAverages.ts | 5 ++- src/core/blitzstars/tankopedia.ts | 5 ++- 14 files changed, 130 insertions(+), 60 deletions(-) create mode 100644 src/app/api/[region]/ratings/current/info/route.ts create mode 100644 src/app/api/[region]/ratings/current/league/[league]/route.ts create mode 100644 src/app/api/[region]/ratings/current/neighbors/[id]/[count]/route.ts create mode 100644 src/app/api/[region]/ratings/current/search/[username]/route.ts create mode 100644 src/app/api/tank-averages/route.ts create mode 100644 src/app/api/tankopedia/route.ts create mode 100644 src/core/blitz/searchCurrentRatingsPlayers.ts delete mode 100644 src/core/blitzkrieg/withCORSProxy.ts diff --git a/src/app/api/[region]/ratings/current/info/route.ts b/src/app/api/[region]/ratings/current/info/route.ts new file mode 100644 index 000000000..f7245897c --- /dev/null +++ b/src/app/api/[region]/ratings/current/info/route.ts @@ -0,0 +1,10 @@ +import { NextResponse } from 'next/server'; +import { Region } from '../../../../../../constants/regions'; +import getRatingsInfo from '../../../../../../core/blitz/getRatingsInfo'; + +export async function GET( + request: Request, + { params }: { params: { region: Region } }, +) { + return NextResponse.json(await getRatingsInfo(params.region)); +} diff --git a/src/app/api/[region]/ratings/current/league/[league]/route.ts b/src/app/api/[region]/ratings/current/league/[league]/route.ts new file mode 100644 index 000000000..4e2b57735 --- /dev/null +++ b/src/app/api/[region]/ratings/current/league/[league]/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server'; +import { Region } from '../../../../../../../constants/regions'; +import { getRatingsLeague } from '../../../../../../../core/blitz/getRatingsLeague'; + +export async function GET( + request: Request, + { params }: { params: { region: Region; league: number } }, +) { + return NextResponse.json( + await getRatingsLeague(params.region, params.league), + ); +} diff --git a/src/app/api/[region]/ratings/current/neighbors/[id]/[count]/route.ts b/src/app/api/[region]/ratings/current/neighbors/[id]/[count]/route.ts new file mode 100644 index 000000000..10de3f1b1 --- /dev/null +++ b/src/app/api/[region]/ratings/current/neighbors/[id]/[count]/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server'; +import { Region } from '../../../../../../../../constants/regions'; +import { getRatingsNeighbors } from '../../../../../../../../core/blitz/getRatingsNeighbors'; + +export async function GET( + request: Request, + { params }: { params: { region: Region; id: number; count: number } }, +) { + return NextResponse.json( + await getRatingsNeighbors(params.region, params.id, params.count), + ); +} diff --git a/src/app/api/[region]/ratings/current/search/[username]/route.ts b/src/app/api/[region]/ratings/current/search/[username]/route.ts new file mode 100644 index 000000000..0f5e51b53 --- /dev/null +++ b/src/app/api/[region]/ratings/current/search/[username]/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from 'next/server'; +import { Region } from '../../../../../../../constants/regions'; +import { searchCurrentRatingsPlayers } from '../../../../../../../core/blitz/searchCurrentRatingsPlayers'; + +export async function GET( + request: Request, + { params }: { params: { region: Region; username: string } }, +) { + return NextResponse.json( + await searchCurrentRatingsPlayers(params.region, params.username), + ); +} diff --git a/src/app/api/tank-averages/route.ts b/src/app/api/tank-averages/route.ts new file mode 100644 index 000000000..7f5ab96bf --- /dev/null +++ b/src/app/api/tank-averages/route.ts @@ -0,0 +1,6 @@ +import { NextResponse } from 'next/server'; +import { tankAverages } from '../../../core/blitzstars/tankAverages'; + +export async function GET() { + return NextResponse.json(await tankAverages); +} diff --git a/src/app/api/tankopedia/route.ts b/src/app/api/tankopedia/route.ts new file mode 100644 index 000000000..1ee08729c --- /dev/null +++ b/src/app/api/tankopedia/route.ts @@ -0,0 +1,6 @@ +import { NextResponse } from 'next/server'; +import { tankopedia } from '../../../core/blitzstars/tankopedia'; + +export async function GET() { + return NextResponse.json(await tankopedia); +} diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 60267809a..a753856fb 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -28,7 +28,7 @@ import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../../../core/blitz/getRatingsInfo'; import { getRatingsLeague } from '../../../core/blitz/getRatingsLeague'; import { getRatingsNeighbors } from '../../../core/blitz/getRatingsNeighbors'; -import regionToRegionSubdomain from '../../../core/blitz/regionToRegionSubdomain'; +import { searchCurrentRatingsPlayers } from '../../../core/blitz/searchCurrentRatingsPlayers'; import { AccountList, AccountListItem, @@ -36,7 +36,6 @@ import { import { getArchivedLatestSeasonNumber } from '../../../core/blitzkrieg/getArchivedLatestSeasonNumber'; import getArchivedRatingsInfo from '../../../core/blitzkrieg/getArchivedRatingsInfo'; import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; -import { withCORSProxy } from '../../../core/blitzkrieg/withCORSProxy'; import { PageTurner } from './components/PageTurner'; const ROWS_OPTIONS = [5, 10, 15, 25, 30]; @@ -621,35 +620,11 @@ export default function Page() { if (season === 0) { setSearchLoading(true); - const response = await fetch( - withCORSProxy( - `https://${regionToRegionSubdomain( - region, - )}.wotblitz.com/en/api/rating-leaderboards/search/?prefix=${encodedSearch}`, - ), - ); const accountList = - (await response.json()) as Record< - number, - { - spa_id: number; - nickname: string; - clan_tag: string; - } & ( - | { - skip: true; - } - | { - skip: false; - mmr: number; - season_number: number; - calibrationBattlesLeft: number; - number: number; - updated_at: string; - neighbors: []; - } - ) - >; + await searchCurrentRatingsPlayers( + region, + trimmedSearch, + ); const values = Object.values(accountList).filter( (searchedPlayer) => !searchedPlayer.skip, ); diff --git a/src/core/blitz/getRatingsInfo.ts b/src/core/blitz/getRatingsInfo.ts index 7980f3cbf..ec914d043 100644 --- a/src/core/blitz/getRatingsInfo.ts +++ b/src/core/blitz/getRatingsInfo.ts @@ -1,15 +1,15 @@ import { RatingsInfo } from '../../commands/ratings'; import { Region } from '../../constants/regions'; -import { withCORSProxy } from '../blitzkrieg/withCORSProxy'; +import { context } from '../blitzkrieg/context'; import regionToRegionSubdomain from './regionToRegionSubdomain'; export default async function getRatingsInfo(region: Region) { const response = await fetch( - withCORSProxy( - `https://${regionToRegionSubdomain( - region, - )}.wotblitz.com/en/api/rating-leaderboards/season/`, - ), + context === 'website' + ? `/api/${region}/ratings/current/info` + : `https://${regionToRegionSubdomain( + region, + )}.wotblitz.com/en/api/rating-leaderboards/season/`, ); return (await response.json()) as Promise; diff --git a/src/core/blitz/getRatingsLeague.ts b/src/core/blitz/getRatingsLeague.ts index 29ea7be9e..49445335e 100644 --- a/src/core/blitz/getRatingsLeague.ts +++ b/src/core/blitz/getRatingsLeague.ts @@ -1,7 +1,7 @@ import { LeagueTop } from '../../commands/ratings'; import { Region } from '../../constants/regions'; +import { context } from '../blitzkrieg/context'; import { patientFetch } from '../blitzkrieg/patientFetch'; -import { withCORSProxy } from '../blitzkrieg/withCORSProxy'; import regionToRegionSubdomain from './regionToRegionSubdomain'; export async function getRatingsLeague( @@ -11,11 +11,11 @@ export async function getRatingsLeague( ) { const fetcher = usePatientFetcher ? patientFetch : fetch; const response = await fetcher( - withCORSProxy( - `https://${regionToRegionSubdomain( - region, - )}.wotblitz.com/en/api/rating-leaderboards/league/${league}/top/`, - ), + context === 'website' + ? `/api/${region}/ratings/current/league/${league}` + : `https://${regionToRegionSubdomain( + region, + )}.wotblitz.com/en/api/rating-leaderboards/league/${league}/top/`, ); const json = (await response.json()) as LeagueTop; diff --git a/src/core/blitz/getRatingsNeighbors.ts b/src/core/blitz/getRatingsNeighbors.ts index 407627e54..b7320b104 100644 --- a/src/core/blitz/getRatingsNeighbors.ts +++ b/src/core/blitz/getRatingsNeighbors.ts @@ -1,22 +1,22 @@ import { RatingsNeighbors } from '../../commands/ratings'; import { Region } from '../../constants/regions'; +import { context } from '../blitzkrieg/context'; import { patientFetch } from '../blitzkrieg/patientFetch'; -import { withCORSProxy } from '../blitzkrieg/withCORSProxy'; import regionToRegionSubdomain from './regionToRegionSubdomain'; export async function getRatingsNeighbors( region: Region, id: number, - count = 30, + count: number, usePatientFetcher = false, ) { const fetcher = usePatientFetcher ? patientFetch : fetch; const response = await fetcher( - withCORSProxy( - `https://${regionToRegionSubdomain( - region, - )}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${count}`, - ), + context === 'website' + ? `/api/${region}/ratings/current/neighbors/${id}/${count}` + : `https://${regionToRegionSubdomain( + region, + )}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${count}`, ); const json = (await response.json()) as RatingsNeighbors; diff --git a/src/core/blitz/searchCurrentRatingsPlayers.ts b/src/core/blitz/searchCurrentRatingsPlayers.ts new file mode 100644 index 000000000..259618cb8 --- /dev/null +++ b/src/core/blitz/searchCurrentRatingsPlayers.ts @@ -0,0 +1,42 @@ +import { Region } from '../../constants/regions'; +import { context } from '../blitzkrieg/context'; +import regionToRegionSubdomain from './regionToRegionSubdomain'; + +type CurrentRatingsPlayerSearch = Record< + number, + { + spa_id: number; + nickname: string; + clan_tag: string; + } & ( + | { + skip: true; + } + | { + skip: false; + mmr: number; + season_number: number; + calibrationBattlesLeft: number; + number: number; + updated_at: string; + neighbors: []; + } + ) +>; + +export async function searchCurrentRatingsPlayers( + region: Region, + name: string, +) { + const encodedSearch = encodeURIComponent(name); + const response = await fetch( + context === 'website' + ? `/api/${region}/ratings/current/search/${encodedSearch}` + : `https://${regionToRegionSubdomain( + region, + )}.wotblitz.com/en/api/rating-leaderboards/search/?prefix=${encodedSearch}`, + ); + const accountList = (await response.json()) as CurrentRatingsPlayerSearch; + + return accountList; +} diff --git a/src/core/blitzkrieg/withCORSProxy.ts b/src/core/blitzkrieg/withCORSProxy.ts deleted file mode 100644 index cce2d45ad..000000000 --- a/src/core/blitzkrieg/withCORSProxy.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { context } from './context'; - -export function withCORSProxy(url: string) { - return context === 'website' - ? `https://corsproxy.io/?${encodeURIComponent(url)}` - : url; -} diff --git a/src/core/blitzstars/tankAverages.ts b/src/core/blitzstars/tankAverages.ts index a2d1bf8ae..6316d7db9 100644 --- a/src/core/blitzstars/tankAverages.ts +++ b/src/core/blitzstars/tankAverages.ts @@ -1,6 +1,5 @@ import { AllStats } from '../blitz/getAccountInfo'; import { context } from '../blitzkrieg/context'; -import { withCORSProxy } from '../blitzkrieg/withCORSProxy'; import { tankopedia } from './tankopedia'; export interface SpecialStats { @@ -86,7 +85,9 @@ tankopedia; export type TankAverages = Record; export const tankAverages = fetch( - withCORSProxy('https://www.blitzstars.com/api/tankaverages.json'), + context === 'website' + ? '/api/tank-averages' + : 'https://www.blitzstars.com/api/tankaverages.json', ) .then((response) => response.json()) .then((json) => { diff --git a/src/core/blitzstars/tankopedia.ts b/src/core/blitzstars/tankopedia.ts index 3d12d61cb..faf794f6c 100644 --- a/src/core/blitzstars/tankopedia.ts +++ b/src/core/blitzstars/tankopedia.ts @@ -1,6 +1,5 @@ import { deburr } from 'lodash'; import { context } from '../blitzkrieg/context'; -import { withCORSProxy } from '../blitzkrieg/withCORSProxy'; export interface TankopediaEntry { name: string; @@ -19,7 +18,9 @@ export interface Tankopedia { } export const tankopedia = fetch( - withCORSProxy('https://www.blitzstars.com/bs-tankopedia.json'), + context === 'website' + ? '/api/tankopedia' + : 'https://www.blitzstars.com/bs-tankopedia.json', ) .then(async (response) => response.json()) .then((wrapperTankopedia) => From 6d05e607b3de118b951ce63916c2a6f4eea678d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 11 Nov 2023 21:42:26 -0600 Subject: [PATCH 48/88] feat: fixed rows per page --- src/app/tools/ratings/page.tsx | 58 ++++++++++++++-------------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index a753856fb..e9690fbc3 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -38,7 +38,7 @@ import getArchivedRatingsInfo from '../../../core/blitzkrieg/getArchivedRatingsI import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; import { PageTurner } from './components/PageTurner'; -const ROWS_OPTIONS = [5, 10, 15, 25, 30]; +const ROWS_PER_PAGE = 30; const SEEDING_SIZE = 2 ** 7; interface NameCacheEntry { @@ -66,7 +66,6 @@ export default function Page() { const [region, setRegion] = useState('com'); const [season, setSeason] = useState(0); const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(25); const [jumpToLeague, setJumpToLeague] = useState(0); const [searchLoading, setSearchLoading] = useState(false); const [jumpToScoreOpen, setJumpToScoreOpen] = useState(false); @@ -102,7 +101,7 @@ export default function Page() { const pages = Math.ceil( (ratingsInfo && ratingsInfo.detail === undefined ? ratingsInfo?.count : 1) / - rowsPerPage, + ROWS_PER_PAGE, ); // page reset on season or region change @@ -188,7 +187,7 @@ export default function Page() { players[region][season] && 0 in players[region][season] ) { - cacheNeighbors(players[region][season][0].id, rowsPerPage * 3); + cacheNeighbors(players[region][season][0].id, ROWS_PER_PAGE * 3); } }, [players[region]?.[season] && 0 in players[region][season]]); // neighboring page caching for all seasons @@ -196,14 +195,17 @@ export default function Page() { if (!ratingsInfo || ratingsInfo?.detail) return; if (season === 0) { - const middleIndex = Math.round(page * rowsPerPage + rowsPerPage / 2); + const middleIndex = Math.round(page * ROWS_PER_PAGE + ROWS_PER_PAGE / 2); if (!(middleIndex in players[region][season])) return; const middleId = players[region][season][middleIndex].id; - cacheNeighbors(middleId, rowsPerPage * 3); + cacheNeighbors(middleId, ROWS_PER_PAGE * 3); } else { const ids = range( - Math.max(0, (page - 1) * rowsPerPage), - Math.min(ratingsInfo.count - 1, (page + 1) * rowsPerPage + rowsPerPage), + Math.max(0, (page - 1) * ROWS_PER_PAGE), + Math.min( + ratingsInfo.count - 1, + (page + 1) * ROWS_PER_PAGE + ROWS_PER_PAGE, + ), ) .map((index) => players[region][season][index].id) .filter(Boolean) @@ -281,6 +283,7 @@ export default function Page() { return neighbors; } + async function handleJumpToPosition() { if (!ratingsInfo || ratingsInfo?.detail) return; @@ -311,7 +314,7 @@ export default function Page() { if (closestPositionDistance === 0) { setHighlightedPlayer({ type: 'position', position: targetPosition }); - setPage(Math.floor(targetPosition / rowsPerPage)); + setPage(Math.floor(targetPosition / ROWS_PER_PAGE)); return; } @@ -345,10 +348,10 @@ export default function Page() { } setLoadingProgress(null); - setPage(Math.floor(targetPosition / rowsPerPage)); + setPage(Math.floor(targetPosition / ROWS_PER_PAGE)); setHighlightedPlayer({ type: 'position', position: closestPosition }); } else { - setPage(Math.floor(targetPosition / rowsPerPage)); + setPage(Math.floor(targetPosition / ROWS_PER_PAGE)); if (players) { setHighlightedPlayer({ @@ -375,7 +378,7 @@ export default function Page() { if (playerIndex === -1) return; - setPage(Math.floor(playerIndex / rowsPerPage)); + setPage(Math.floor(playerIndex / ROWS_PER_PAGE)); if (players) { setHighlightedPlayer({ @@ -432,21 +435,6 @@ export default function Page() { - - setRowsPerPage(parseInt(value))} - value={`${rowsPerPage}`} - > - - - - {ROWS_OPTIONS.map((size) => ( - - {size} per page - - ))} - - @@ -547,7 +535,7 @@ export default function Page() { if (season === 0) { const position = leaguePositionCache.current[jumpToLeague]; - const newPage = Math.floor(position / rowsPerPage); + const newPage = Math.floor(position / ROWS_PER_PAGE); setPage(newPage); if (players) { @@ -557,7 +545,7 @@ export default function Page() { }); cacheNeighbors( players[region][season][position].id, - rowsPerPage * 3, + ROWS_PER_PAGE * 3, ); } } else { @@ -583,7 +571,7 @@ export default function Page() { if (firstPlayerIndex === -1) return; - setPage(Math.floor(firstPlayerIndex / rowsPerPage)); + setPage(Math.floor(firstPlayerIndex / ROWS_PER_PAGE)); setHighlightedPlayer({ type: 'id', id: players[region][season][firstPlayerIndex].id, @@ -716,11 +704,11 @@ export default function Page() { onClick={() => { if (season === 0) { setPage( - Math.floor((player.number - 1) / rowsPerPage), + Math.floor((player.number - 1) / ROWS_PER_PAGE), ); cacheNeighbors( player.account_id, - rowsPerPage * 3, + ROWS_PER_PAGE * 3, ); setHighlightedPlayer({ type: 'id', @@ -745,7 +733,7 @@ export default function Page() { } const playerPage = Math.floor( - playerIndex! / rowsPerPage, + playerIndex! / ROWS_PER_PAGE, ); setPage(playerPage); @@ -799,9 +787,9 @@ export default function Page() { ) : ( range( - page * rowsPerPage, + page * ROWS_PER_PAGE, Math.min( - page * rowsPerPage + rowsPerPage, + page * ROWS_PER_PAGE + ROWS_PER_PAGE, ratingsInfo?.detail ? 0 : (ratingsInfo?.count ?? 1) - 1, ), ).map((index) => { From 34665bd97a3efebbdaccb48a5a67942b60986e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 11 Nov 2023 22:10:32 -0600 Subject: [PATCH 49/88] fix: fixed archived search results --- src/app/tools/ratings/page.tsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index e9690fbc3..5cda8814d 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -660,28 +660,30 @@ export default function Page() { } else { setSearchLoading(true); const accountList = await fetchBlitz( - `https://api.wotblitz.${region}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}&limit=100`, + `https://api.wotblitz.${region}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}`, + ); + const playerKeys = Object.keys( + players[region][season], ); - const playerKeys = Object.keys(players!); setSearchResults( accountList - .filter( - (searchedPlayer) => - players && - searchedPlayer.account_id in players, - ) .map((searchedPlayer) => ({ ...searchedPlayer, number: parseInt( playerKeys.find( - (index) => - players[region][season][parseInt(index)] - .id === searchedPlayer.account_id, + (indexAsString) => + players[region][season][ + parseInt(indexAsString) + ].id === searchedPlayer.account_id, )!, ), - })), + })) + .filter( + (player) => !Number.isNaN(player.number), + ), ); + setSearchLoading(false); } } else { From dbc117a9d441cb151bfc7dda8336fc278853a09c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 11 Nov 2023 22:20:36 -0600 Subject: [PATCH 50/88] fix: archived jump to score --- src/app/tools/ratings/page.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 5cda8814d..537d854a9 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -355,8 +355,8 @@ export default function Page() { if (players) { setHighlightedPlayer({ - type: 'id', - id: players[region][season][targetPosition].id, + type: 'position', + position: targetPosition, }); } } @@ -366,7 +366,9 @@ export default function Page() { let playerIndex = -1; - for (let index = ratingsInfo.count - 1; index >= 0; index--) { + for (let index = ratingsInfo.count; index >= 0; index--) { + if (!(index in players[region][season])) continue; + if ( players[region][season][index].score >= scoreInput.current!.valueAsNumber @@ -382,8 +384,8 @@ export default function Page() { if (players) { setHighlightedPlayer({ - type: 'id', - id: players[region][season][playerIndex].id, + type: 'position', + position: playerIndex, }); } } From c026007937ea415417fb2cc36f68b5cccc082092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sat, 11 Nov 2023 23:07:13 -0600 Subject: [PATCH 51/88] fix: first page caching --- src/app/tools/ratings/page.tsx | 128 +++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 23 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 537d854a9..9e606a2b4 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -228,7 +228,13 @@ export default function Page() { }); } } - }, [page, region, season, ratingsInfo?.detail]); + }, [ + page, + region, + season, + ratingsInfo?.detail, + players[region][season] !== undefined && 0 in players[region][season], + ]); async function cacheNeighbors( id: number, @@ -361,32 +367,108 @@ export default function Page() { } } } - function handleJumpToScore() { + async function handleJumpToScore() { if (!ratingsInfo || ratingsInfo?.detail) return; - let playerIndex = -1; + const targetScore = scoreInput.current!.valueAsNumber; - for (let index = ratingsInfo.count; index >= 0; index--) { - if (!(index in players[region][season])) continue; + if (season === 0) { + const playerEntries = Object.entries(players[region][season]); + let closest: BlitzkriegRatingsLeaderboardEntry | null = null; + let closestScore: number | null = null; + let closestScoreDistance = Infinity; + let closesIndex = -1; - if ( - players[region][season][index].score >= - scoreInput.current!.valueAsNumber - ) { - playerIndex = index; - break; - } - } + playerEntries.forEach(([positionAsString, player]) => { + const position = parseInt(positionAsString); + const distance = Math.abs( + player.score - scoreInput.current!.valueAsNumber, + ); - if (playerIndex === -1) return; - - setPage(Math.floor(playerIndex / ROWS_PER_PAGE)); - - if (players) { - setHighlightedPlayer({ - type: 'position', - position: playerIndex, + if (distance < closestScoreDistance) { + closestScoreDistance = distance; + closestScore = player.score; + closest = player; + closesIndex = position; + } }); + + if (closest === null || closestScore === null) return; + + if (closestScoreDistance === 0) { + setHighlightedPlayer({ + type: 'id', + id: (closest as BlitzkriegRatingsLeaderboardEntry).id, + }); + setPage(Math.floor(closesIndex / ROWS_PER_PAGE)); + + return; + } + + // 1 going down and -1 going up + const loadingDirection = Math.sign(closesIndex - closestScore); + let seedingPlayer = (closest as BlitzkriegRatingsLeaderboardEntry).id; + let targetScoreAcquired = false; + let neighbors: RatingsPlayer[]; + let targetPlayer: RatingsPlayer | undefined; + + setLoadingProgress([0, closestScoreDistance]); + + while (!targetScoreAcquired) { + neighbors = await cacheNeighbors(seedingPlayer, SEEDING_SIZE); + seedingPlayer = neighbors.at(loadingDirection === -1 ? 0 : -1)!.spa_id; + + if (loadingDirection === 1) { + targetScoreAcquired = targetScore >= neighbors.at(-1)!.score; + setLoadingProgress([ + Math.abs(neighbors.at(-1)!.score - closestScore), + closestScoreDistance, + ]); + } else { + targetScoreAcquired = targetScore <= neighbors[0].score; + setLoadingProgress([ + Math.abs(neighbors[0].score - closestScore), + closestScoreDistance, + ]); + } + + if (targetScoreAcquired) { + targetPlayer = neighbors.find((player) => + loadingDirection === 1 + ? player.score <= targetScore + : player.score >= targetScore, + ); + } + } + + setLoadingProgress(null); + setPage(Math.floor((targetPlayer!.number - 1) / ROWS_PER_PAGE)); + setHighlightedPlayer({ + type: 'id', + id: targetPlayer!.spa_id, + }); + } else { + let playerIndex = -1; + + for (let index = ratingsInfo.count; index >= 0; index--) { + if (!(index in players[region][season])) continue; + + if (players[region][season][index].score >= targetScore) { + playerIndex = index; + break; + } + } + + if (playerIndex === -1) return; + + setPage(Math.floor(playerIndex / ROWS_PER_PAGE)); + + if (players) { + setHighlightedPlayer({ + type: 'position', + position: playerIndex, + }); + } } } @@ -845,7 +927,7 @@ export default function Page() { direction="column" > - Loading {loadingProgress[0]} of {loadingProgress[1]} players... + Loading {loadingProgress[0]} of {loadingProgress[1]} assets...
Please be patient. Wargaming is slow. - Players are loaded in batches of {SEEDING_SIZE}. + Assets are loaded in batches of {SEEDING_SIZE}. From cc193b99d605dff6a81a450f7c7d497d7b45b921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 12:27:06 -0600 Subject: [PATCH 52/88] fix: bug fixes --- src/app/tools/ratings/page.tsx | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 9e606a2b4..ba84da7e9 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -301,7 +301,7 @@ export default function Page() { if (season === 0) { const playerEntries = Object.entries(players[region][season]); - let closest: BlitzkriegRatingsLeaderboardEntry | null = null; + let closestPlayer: BlitzkriegRatingsLeaderboardEntry | null = null; let closestPosition: number | null = null; let closestPositionDistance = Infinity; @@ -312,11 +312,11 @@ export default function Page() { if (distance < closestPositionDistance) { closestPositionDistance = distance; closestPosition = position; - closest = player; + closestPlayer = player; } }); - if (closest === null || closestPosition === null) return; + if (closestPlayer === null || closestPosition === null) return; if (closestPositionDistance === 0) { setHighlightedPlayer({ type: 'position', position: targetPosition }); @@ -327,7 +327,8 @@ export default function Page() { // 1 going down and -1 going up const loadingDirection = Math.sign(targetPosition - closestPosition); - let seedingPlayer = (closest as BlitzkriegRatingsLeaderboardEntry).id; + let seedingPlayer = (closestPlayer as BlitzkriegRatingsLeaderboardEntry) + .id; let targetPositionAcquired = false; let neighbors: RatingsPlayer[]; @@ -355,7 +356,7 @@ export default function Page() { setLoadingProgress(null); setPage(Math.floor(targetPosition / ROWS_PER_PAGE)); - setHighlightedPlayer({ type: 'position', position: closestPosition }); + setHighlightedPlayer({ type: 'position', position: targetPosition }); } else { setPage(Math.floor(targetPosition / ROWS_PER_PAGE)); @@ -432,12 +433,19 @@ export default function Page() { ]); } + console.log(neighbors.map((player) => player.score - targetScore)); + if (targetScoreAcquired) { - targetPlayer = neighbors.find((player) => - loadingDirection === 1 - ? player.score <= targetScore - : player.score >= targetScore, - ); + neighbors.findLast; + if (loadingDirection === 1) { + targetPlayer = neighbors.find( + (neighbor) => neighbor.score <= targetScore, + ); + } else { + targetPlayer = neighbors.findLast( + (neighbor) => neighbor.score >= targetScore, + ); + } } } @@ -954,9 +962,9 @@ export default function Page() { Please be patient. Wargaming is slow. - - Assets are loaded in batches of {SEEDING_SIZE}. - + {loadingProgress[0] === 0 && ( + First assets will be fetched shortly. + )} )} From 86e385c5488d4c074a8cde8fac51983d24537d73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 12:52:39 -0600 Subject: [PATCH 53/88] feat: rewrote jump to score for current season --- src/app/tools/ratings/page.tsx | 78 +++++++++++++--------------------- 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index ba84da7e9..7019e4587 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -375,86 +375,66 @@ export default function Page() { if (season === 0) { const playerEntries = Object.entries(players[region][season]); - let closest: BlitzkriegRatingsLeaderboardEntry | null = null; + let closestPlayer: BlitzkriegRatingsLeaderboardEntry | null = null; let closestScore: number | null = null; let closestScoreDistance = Infinity; let closesIndex = -1; - playerEntries.forEach(([positionAsString, player]) => { + playerEntries.forEach(([positionAsString, player], index) => { const position = parseInt(positionAsString); - const distance = Math.abs( - player.score - scoreInput.current!.valueAsNumber, - ); + const distance = Math.abs(player.score - targetScore); if (distance < closestScoreDistance) { closestScoreDistance = distance; closestScore = player.score; - closest = player; + closestPlayer = player; closesIndex = position; } }); - if (closest === null || closestScore === null) return; + if (closestPlayer === null || closestScore === null) return; - if (closestScoreDistance === 0) { - setHighlightedPlayer({ - type: 'id', - id: (closest as BlitzkriegRatingsLeaderboardEntry).id, - }); - setPage(Math.floor(closesIndex / ROWS_PER_PAGE)); - - return; - } - - // 1 going down and -1 going up - const loadingDirection = Math.sign(closesIndex - closestScore); - let seedingPlayer = (closest as BlitzkriegRatingsLeaderboardEntry).id; - let targetScoreAcquired = false; - let neighbors: RatingsPlayer[]; - let targetPlayer: RatingsPlayer | undefined; + const loadingDirection = Math.sign(closestScore - targetScore); + let targetPlayer: RatingsPlayer; + let scoreAcquired = false; + let seedingPlayer = closestPlayer as BlitzkriegRatingsLeaderboardEntry; setLoadingProgress([0, closestScoreDistance]); + while (!scoreAcquired) { + const neighbors = await cacheNeighbors(seedingPlayer.id, SEEDING_SIZE); + const seedingNeighbor = neighbors.at(loadingDirection === 1 ? -1 : 0)!; + seedingPlayer = { + id: seedingNeighbor.spa_id, + score: seedingNeighbor.score, + }; - while (!targetScoreAcquired) { - neighbors = await cacheNeighbors(seedingPlayer, SEEDING_SIZE); - seedingPlayer = neighbors.at(loadingDirection === -1 ? 0 : -1)!.spa_id; + setLoadingProgress([ + closestScoreDistance - Math.abs(seedingNeighbor.score - targetScore), + closestScoreDistance, + ]); if (loadingDirection === 1) { - targetScoreAcquired = targetScore >= neighbors.at(-1)!.score; - setLoadingProgress([ - Math.abs(neighbors.at(-1)!.score - closestScore), - closestScoreDistance, - ]); + scoreAcquired = seedingPlayer.score <= targetScore; } else { - targetScoreAcquired = targetScore <= neighbors[0].score; - setLoadingProgress([ - Math.abs(neighbors[0].score - closestScore), - closestScoreDistance, - ]); + scoreAcquired = seedingPlayer.score >= targetScore; } - console.log(neighbors.map((player) => player.score - targetScore)); - - if (targetScoreAcquired) { - neighbors.findLast; + if (scoreAcquired) { if (loadingDirection === 1) { targetPlayer = neighbors.find( - (neighbor) => neighbor.score <= targetScore, - ); + (player) => player.score <= targetScore, + )!; } else { targetPlayer = neighbors.findLast( - (neighbor) => neighbor.score >= targetScore, - ); + (player) => player.score >= targetScore, + )!; } } } + setPage(Math.floor(targetPlayer!.number / ROWS_PER_PAGE)); + setHighlightedPlayer({ type: 'id', id: targetPlayer!.spa_id }); setLoadingProgress(null); - setPage(Math.floor((targetPlayer!.number - 1) / ROWS_PER_PAGE)); - setHighlightedPlayer({ - type: 'id', - id: targetPlayer!.spa_id, - }); } else { let playerIndex = -1; From daef04ccd8e593544cd5ebe9846bd52fe8a8e328 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 13:15:55 -0600 Subject: [PATCH 54/88] feat: lowered retry time to 1 second --- src/core/blitzkrieg/patientFetch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/blitzkrieg/patientFetch.ts b/src/core/blitzkrieg/patientFetch.ts index 2d3f8e8c0..4a8d17121 100644 --- a/src/core/blitzkrieg/patientFetch.ts +++ b/src/core/blitzkrieg/patientFetch.ts @@ -1,4 +1,4 @@ -export async function patientFetch(url: string, timeout = 1 * 60 * 1000) { +export async function patientFetch(url: string, timeout = 1000) { let patientResponse: Response; while (patientResponse! === undefined) { From 95588ef6b65877b4b004dc705bd0fe6cbc9bcbd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:14:56 -0600 Subject: [PATCH 55/88] feat: patient fetching --- src/app/tools/ratings/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 7019e4587..8c379a3d8 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -240,7 +240,7 @@ export default function Page() { id: number, size: number, ): Promise { - const { neighbors } = await getRatingsNeighbors(region, id, size); + const { neighbors } = await getRatingsNeighbors(region, id, size, true); const radius = Math.round(size / 2); const targetPosition = Object.entries(players[region][season]).find( From 286936131df9d817daa39ebd44a943308570e1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:16:45 -0600 Subject: [PATCH 56/88] feat: simpler welcome message --- src/events/guildMemberAdd.ts | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/events/guildMemberAdd.ts b/src/events/guildMemberAdd.ts index ad798295f..01bf564f1 100644 --- a/src/events/guildMemberAdd.ts +++ b/src/events/guildMemberAdd.ts @@ -1,12 +1,5 @@ -import { - AttachmentBuilder, - EmbedBuilder, - GuildMember, - TextChannel, -} from 'discord.js'; -import markdownEscape from 'markdown-escape'; +import { AttachmentBuilder, GuildMember, TextChannel } from 'discord.js'; import discord from '../../discord.json' assert { type: 'json' }; -import { ACCENT_COLOR } from '../constants/colors'; export default async function (member: GuildMember) { if (member.guild.id === discord.sklld_guild_id) { @@ -15,15 +8,7 @@ export default async function (member: GuildMember) { discord.sklld_verify_channel, ) as TextChannel ).send({ - content: member.toString(), - embeds: [ - new EmbedBuilder() - .setColor(ACCENT_COLOR) - .setTitle(`Welcome, ${markdownEscape(member.user.displayName)}`) - .setDescription( - `Welcome to the Skilled server! To continue, please use the \`/link\` command.`, - ), - ], + content: `# Welcome, ${member.toString()}!\nPlease use the \`/link\` command like the video below to continue.`, files: [new AttachmentBuilder('https://i.imgur.com/2p6GFgC.gif')], }); } From 64253d01201e31256afd1e6a16b10eb67eac8169 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 21:31:36 -0600 Subject: [PATCH 57/88] feat: page turning in current season --- .../tools/ratings/components/PageTurner.tsx | 10 +++---- src/app/tools/ratings/page.tsx | 29 ++++++++++++++----- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/app/tools/ratings/components/PageTurner.tsx b/src/app/tools/ratings/components/PageTurner.tsx index f8bdf0fd5..ec25ba8aa 100644 --- a/src/app/tools/ratings/components/PageTurner.tsx +++ b/src/app/tools/ratings/components/PageTurner.tsx @@ -5,10 +5,10 @@ import { useEffect, useRef } from 'react'; interface PageTurnerProps { page: number; pages: number; - setPage: (page: number | ((page: number) => number)) => void; + onPageChange: (page: number) => void; } -export function PageTurner({ page, setPage, pages }: PageTurnerProps) { +export function PageTurner({ page, onPageChange, pages }: PageTurnerProps) { const pageInput = useRef(null); useEffect(() => { @@ -20,7 +20,7 @@ export function PageTurner({ page, setPage, pages }: PageTurnerProps) { - + @@ -854,7 +859,11 @@ export default function Page() { - + jumpToPosition(page * ROWS_PER_PAGE)} + /> {players === null ? ( @@ -893,7 +902,11 @@ export default function Page() { )} - + jumpToPosition(page * ROWS_PER_PAGE)} + /> {loadingProgress !== null && ( Date: Mon, 13 Nov 2023 21:41:37 -0600 Subject: [PATCH 58/88] feat: button page turning exception --- .../tools/ratings/components/PageTurner.tsx | 7 ++++--- src/app/tools/ratings/page.tsx | 21 +++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/app/tools/ratings/components/PageTurner.tsx b/src/app/tools/ratings/components/PageTurner.tsx index ec25ba8aa..850b89ba5 100644 --- a/src/app/tools/ratings/components/PageTurner.tsx +++ b/src/app/tools/ratings/components/PageTurner.tsx @@ -5,7 +5,7 @@ import { useEffect, useRef } from 'react'; interface PageTurnerProps { page: number; pages: number; - onPageChange: (page: number) => void; + onPageChange: (page: number, isButtonClick: boolean) => void; } export function PageTurner({ page, onPageChange, pages }: PageTurnerProps) { @@ -20,7 +20,7 @@ export function PageTurner({ page, onPageChange, pages }: PageTurnerProps) { - + @@ -862,7 +866,9 @@ export default function Page() { jumpToPosition(page * ROWS_PER_PAGE)} + onPageChange={(page, isButtonClick) => + isButtonClick ? setPage(page) : jumpToPosition(page * ROWS_PER_PAGE) + } /> @@ -905,7 +911,9 @@ export default function Page() { jumpToPosition(page * ROWS_PER_PAGE)} + onPageChange={(page, isButtonClick) => + isButtonClick ? setPage(page) : jumpToPosition(page * ROWS_PER_PAGE) + } /> {loadingProgress !== null && ( @@ -938,6 +946,7 @@ export default function Page() { maxWidth: 256, borderRadius: 8, backgroundColor: orangeDark.orange2, + overflow: 'hidden', }} >
Date: Mon, 13 Nov 2023 21:52:21 -0600 Subject: [PATCH 59/88] feat: CORS proxy for neighbors --- .../ratings/current/neighbors/[id]/[count]/route.ts | 12 ------------ src/core/blitz/getRatingsNeighbors.ts | 12 ++++++------ src/core/blitzkrieg/withCORSProxy.ts | 7 +++++++ 3 files changed, 13 insertions(+), 18 deletions(-) delete mode 100644 src/app/api/[region]/ratings/current/neighbors/[id]/[count]/route.ts create mode 100644 src/core/blitzkrieg/withCORSProxy.ts diff --git a/src/app/api/[region]/ratings/current/neighbors/[id]/[count]/route.ts b/src/app/api/[region]/ratings/current/neighbors/[id]/[count]/route.ts deleted file mode 100644 index 10de3f1b1..000000000 --- a/src/app/api/[region]/ratings/current/neighbors/[id]/[count]/route.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { NextResponse } from 'next/server'; -import { Region } from '../../../../../../../../constants/regions'; -import { getRatingsNeighbors } from '../../../../../../../../core/blitz/getRatingsNeighbors'; - -export async function GET( - request: Request, - { params }: { params: { region: Region; id: number; count: number } }, -) { - return NextResponse.json( - await getRatingsNeighbors(params.region, params.id, params.count), - ); -} diff --git a/src/core/blitz/getRatingsNeighbors.ts b/src/core/blitz/getRatingsNeighbors.ts index b7320b104..09ae9e88a 100644 --- a/src/core/blitz/getRatingsNeighbors.ts +++ b/src/core/blitz/getRatingsNeighbors.ts @@ -1,7 +1,7 @@ import { RatingsNeighbors } from '../../commands/ratings'; import { Region } from '../../constants/regions'; -import { context } from '../blitzkrieg/context'; import { patientFetch } from '../blitzkrieg/patientFetch'; +import { withCORSProxy } from '../blitzkrieg/withCORSProxy'; import regionToRegionSubdomain from './regionToRegionSubdomain'; export async function getRatingsNeighbors( @@ -12,11 +12,11 @@ export async function getRatingsNeighbors( ) { const fetcher = usePatientFetcher ? patientFetch : fetch; const response = await fetcher( - context === 'website' - ? `/api/${region}/ratings/current/neighbors/${id}/${count}` - : `https://${regionToRegionSubdomain( - region, - )}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${count}`, + withCORSProxy( + `https://${regionToRegionSubdomain( + region, + )}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${count}`, + ), ); const json = (await response.json()) as RatingsNeighbors; diff --git a/src/core/blitzkrieg/withCORSProxy.ts b/src/core/blitzkrieg/withCORSProxy.ts new file mode 100644 index 000000000..cce2d45ad --- /dev/null +++ b/src/core/blitzkrieg/withCORSProxy.ts @@ -0,0 +1,7 @@ +import { context } from './context'; + +export function withCORSProxy(url: string) { + return context === 'website' + ? `https://corsproxy.io/?${encodeURIComponent(url)}` + : url; +} From d7c7814b3ad1d8bd9e3255e7f9fd0db349f82ae4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:19:49 -0600 Subject: [PATCH 60/88] feat: credits command --- src/commands/credits.ts | 15 +++++++++++++++ src/events/interactionCreate/index.ts | 2 ++ 2 files changed, 17 insertions(+) create mode 100644 src/commands/credits.ts diff --git a/src/commands/credits.ts b/src/commands/credits.ts new file mode 100644 index 000000000..f7f87f0cf --- /dev/null +++ b/src/commands/credits.ts @@ -0,0 +1,15 @@ +import { AttachmentBuilder, SlashCommandBuilder } from 'discord.js'; +import { CommandRegistry } from '../events/interactionCreate'; + +export const creditsCommand: CommandRegistry = { + inProduction: true, + inPublic: true, + + command: new SlashCommandBuilder() + .setName('credits') + .setDescription('Me and the people who made this bot!'), + + async handler() { + return new AttachmentBuilder('https://i.imgur.com/ZF5ch33.png'); + }, +}; diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index 7a41cc454..e181dfdec 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -16,6 +16,7 @@ import { import discord from '../../../discord.json' assert { type: 'json' }; import { aboutCommand } from '../../commands/about'; import { breakdownCommand } from '../../commands/breakdown'; +import { creditsCommand } from '../../commands/credits'; import { debugCommand } from '../../commands/debug'; import { eligibleCommand } from '../../commands/eligible'; import { evolutionCommand } from '../../commands/evolution'; @@ -103,6 +104,7 @@ export const COMMANDS_RAW: CommandRegistry[] = [ statsCommand, ratingsCommand, todayCommand, + creditsCommand, ]; export const commands = Promise.all(COMMANDS_RAW).then((rawCommands) => From 8a78ac7d8203d28892854304d86d868deac352dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:20:09 -0600 Subject: [PATCH 61/88] feat: removed /today command --- src/commands/today.tsx | 18 ------------------ src/events/interactionCreate/index.ts | 2 -- 2 files changed, 20 deletions(-) delete mode 100644 src/commands/today.tsx diff --git a/src/commands/today.tsx b/src/commands/today.tsx deleted file mode 100644 index 3b3e5e348..000000000 --- a/src/commands/today.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { SlashCommandBuilder } from 'discord.js'; -import { CommandRegistry } from '../events/interactionCreate'; - -/** - * TODO: @deprecated - */ -export const todayCommand: CommandRegistry = { - inProduction: true, - inPublic: true, - - command: new SlashCommandBuilder() - .setName('today') - .setDescription('This command has been renamed to `/breakdown today`'), - - handler() { - return '# This command has been renamed to `/breakdown today`\n`/today` now comes with filters, periods and more! Use `/breakdown today` for same old functionality but also do checkout the other options.'; - }, -}; diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index e181dfdec..6dedea8b9 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -32,7 +32,6 @@ import { searchClansCommand } from '../../commands/searchClans'; import { searchPlayersCommand } from '../../commands/searchPlayers'; import { searchTanksCommand } from '../../commands/searchTanks'; import { statsCommand } from '../../commands/stats'; -import { todayCommand } from '../../commands/today'; import getClientId from '../../core/blitzkrieg/getClientId'; import isDev from '../../core/blitzkrieg/isDev'; import { secrets } from '../../core/blitzkrieg/secrets'; @@ -103,7 +102,6 @@ export const COMMANDS_RAW: CommandRegistry[] = [ evolutionCommand, statsCommand, ratingsCommand, - todayCommand, creditsCommand, ]; From faf33b5f2decf0c5f15f18acfa665521365cceb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:27:26 -0600 Subject: [PATCH 62/88] fix: punctuation --- src/commands/credits.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/credits.ts b/src/commands/credits.ts index f7f87f0cf..9cbe952d1 100644 --- a/src/commands/credits.ts +++ b/src/commands/credits.ts @@ -7,7 +7,7 @@ export const creditsCommand: CommandRegistry = { command: new SlashCommandBuilder() .setName('credits') - .setDescription('Me and the people who made this bot!'), + .setDescription('Me and the people who made this bot'), async handler() { return new AttachmentBuilder('https://i.imgur.com/ZF5ch33.png'); From 9e6343a11112eb5722c76cd84121635750c423e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 22:47:17 -0600 Subject: [PATCH 63/88] feat: remove overwrite avoidance --- src/app/tools/ratings/page.tsx | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 9b0b1326b..5c7878baa 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -241,27 +241,27 @@ export default function Page() { size: number, ): Promise { const { neighbors } = await getRatingsNeighbors(region, id, size, true); - const radius = Math.round(size / 2); + // const radius = Math.round(size / 2); - const targetPosition = Object.entries(players[region][season]).find( - ([, player]) => id === player.id, - )?.[0]; + // const targetPosition = Object.entries(players[region][season]).find( + // ([, player]) => id === player.id, + // )?.[0]; - if (targetPosition !== undefined) { - let isMissing = false; - const targetPositionAsNumber = parseInt(targetPosition); + // if (targetPosition !== undefined) { + // let isMissing = false; + // const targetPositionAsNumber = parseInt(targetPosition); - for ( - let position = Math.max(0, targetPositionAsNumber - radius); - position <= targetPositionAsNumber + radius; - position++ - ) { - isMissing = !(position in players[region][season]); - if (isMissing) break; - } + // for ( + // let position = Math.max(0, targetPositionAsNumber - radius); + // position <= targetPositionAsNumber + radius; + // position++ + // ) { + // isMissing = !(position in players[region][season]); + // if (isMissing) break; + // } - if (!isMissing) return []; - } + // if (!isMissing) return []; + // } usePlayersCache.setState( produce((draft: PlayersCache) => { From c742c0725d2ed194e656651038b71a3c41e7b256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 23:00:57 -0600 Subject: [PATCH 64/88] feat: removed swr caching --- package.json | 1 - src/app/tools/ratings/page.tsx | 42 +++++++++++++++++++++++----------- yarn.lock | 12 ++-------- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index ce0f448b9..e72f2e69d 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "react-dom": "^18.2.0", "react-fuzzysort": "^1.0.1", "satori": "^0.10.8", - "swr": "^2.2.4", "vite-node": "^0.34.5", "vitepress": "^1.0.0-rc.20", "vitepress-plugin-auto-sidebar": "^1.1.0", diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 5c7878baa..0325b1d16 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -13,10 +13,9 @@ import { import { produce } from 'immer'; import { debounce, range } from 'lodash'; import { ChangeEvent, useEffect, useRef, useState } from 'react'; -import useSWR from 'swr'; import { create } from 'zustand'; import { BlitzkriegRatingsLeaderboardEntry } from '../../../../scripts/buildRatingsLeaderboard'; -import { RatingsPlayer } from '../../../commands/ratings'; +import { RatingsInfo, RatingsPlayer } from '../../../commands/ratings'; import * as Leaderboard from '../../../components/Leaderboard'; import PageWrapper from '../../../components/PageWrapper'; import { LEAGUES } from '../../../constants/leagues'; @@ -50,6 +49,7 @@ type PlayersCache = Record< Region, Record> >; +type InfoCache = Record>; const useNameCache = create(() => ({ asia: {}, @@ -61,6 +61,11 @@ const usePlayersCache = create(() => ({ com: {}, eu: {}, })); +const useInfoCache = create(() => ({ + asia: {}, + com: {}, + eu: {}, +})); export default function Page() { const [region, setRegion] = useState('com'); @@ -80,17 +85,10 @@ export default function Page() { { type: 'id'; id: number } | { type: 'position'; position: number } | null >(null); - const { data: ratingsInfo } = useSWR( - `ratings-info-${region}-${season}`, - () => - season === 0 - ? getRatingsInfo(region) - : getArchivedRatingsInfo(region, season), - ); - const { data: latestArchivedSeasonNumber } = useSWR( - getArchivedLatestSeasonNumber.name, - getArchivedLatestSeasonNumber, - ); + const ratingsInfo = useInfoCache()[region][season]; + const [latestArchivedSeasonNumber, setLatestArchivedSeasonNumber] = useState< + number | null + >(null); const names = useNameCache(); const players = usePlayersCache(); @@ -107,6 +105,18 @@ export default function Page() { // page reset on season or region change useEffect(() => { setPage(0); + + (async () => { + const newInfo = + season === 0 + ? await getRatingsInfo(region) + : await getArchivedRatingsInfo(region, season); + useInfoCache.setState( + produce((draft: InfoCache) => { + draft[region][season] = newInfo; + }), + ); + })(); }, [season, region]); // initial leagues caching for current season useEffect(() => { @@ -235,6 +245,12 @@ export default function Page() { ratingsInfo?.detail, players[region][season] !== undefined && 0 in players[region][season], ]); + // latest archived season number caching + useEffect(() => {}, [ + getArchivedLatestSeasonNumber().then((result) => { + setLatestArchivedSeasonNumber(result); + }), + ]); async function cacheNeighbors( id: number, diff --git a/yarn.lock b/yarn.lock index d77daf632..5ae1040b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2518,7 +2518,7 @@ clean-stack@^2.0.0: resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -client-only@0.0.1, client-only@^0.0.1: +client-only@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== @@ -4302,14 +4302,6 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -swr@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.4.tgz#03ec4c56019902fbdc904d78544bd7a9a6fa3f07" - integrity sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ== - dependencies: - client-only "^0.0.1" - use-sync-external-store "^1.2.0" - tabbable@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" @@ -4470,7 +4462,7 @@ use-sidecar@^1.1.2: detect-node-es "^1.1.0" tslib "^2.0.0" -use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0: +use-sync-external-store@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== From d2d0477186d38e281f29b79996feba65bfca1bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 23:03:28 -0600 Subject: [PATCH 65/88] fix: remove number fetcher --- src/core/blitzkrieg/numberFetcher.ts | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 src/core/blitzkrieg/numberFetcher.ts diff --git a/src/core/blitzkrieg/numberFetcher.ts b/src/core/blitzkrieg/numberFetcher.ts deleted file mode 100644 index 0292a3040..000000000 --- a/src/core/blitzkrieg/numberFetcher.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Fetcher } from 'swr'; - -export const numberFetcher: Fetcher = (url) => - fetch(url) - .then((res) => res.text()) - .then(parseInt); From a160ccd095823ba4d4ac61fd6f0924aee1aa49a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 13 Nov 2023 23:06:43 -0600 Subject: [PATCH 66/88] feat: reintroduced overwriting --- src/app/tools/ratings/page.tsx | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 0325b1d16..365b77fc4 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -257,27 +257,27 @@ export default function Page() { size: number, ): Promise { const { neighbors } = await getRatingsNeighbors(region, id, size, true); - // const radius = Math.round(size / 2); + const radius = Math.round(size / 2); - // const targetPosition = Object.entries(players[region][season]).find( - // ([, player]) => id === player.id, - // )?.[0]; + const targetPosition = Object.entries(players[region][season]).find( + ([, player]) => id === player.id, + )?.[0]; - // if (targetPosition !== undefined) { - // let isMissing = false; - // const targetPositionAsNumber = parseInt(targetPosition); + if (targetPosition !== undefined) { + let isMissing = false; + const targetPositionAsNumber = parseInt(targetPosition); - // for ( - // let position = Math.max(0, targetPositionAsNumber - radius); - // position <= targetPositionAsNumber + radius; - // position++ - // ) { - // isMissing = !(position in players[region][season]); - // if (isMissing) break; - // } + for ( + let position = Math.max(0, targetPositionAsNumber - radius); + position <= targetPositionAsNumber + radius; + position++ + ) { + isMissing = !(position in players[region][season]); + if (isMissing) break; + } - // if (!isMissing) return []; - // } + if (!isMissing) return []; + } usePlayersCache.setState( produce((draft: PlayersCache) => { From e19d0d948a28c973d6464ebaf800f3d706ee9a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Nov 2023 07:34:47 -0600 Subject: [PATCH 67/88] fix: removed ratings info caching --- src/core/blitz/getRatingsInfo.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/blitz/getRatingsInfo.ts b/src/core/blitz/getRatingsInfo.ts index ec914d043..15dc750f2 100644 --- a/src/core/blitz/getRatingsInfo.ts +++ b/src/core/blitz/getRatingsInfo.ts @@ -10,6 +10,7 @@ export default async function getRatingsInfo(region: Region) { : `https://${regionToRegionSubdomain( region, )}.wotblitz.com/en/api/rating-leaderboards/season/`, + { cache: 'no-store' }, ); return (await response.json()) as Promise; From e79c4a1e3f503003664262a69db2e4bae8378831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:24:10 -0600 Subject: [PATCH 68/88] fix: no cache neighbors --- src/core/blitz/getRatingsNeighbors.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/blitz/getRatingsNeighbors.ts b/src/core/blitz/getRatingsNeighbors.ts index 09ae9e88a..045cf9a01 100644 --- a/src/core/blitz/getRatingsNeighbors.ts +++ b/src/core/blitz/getRatingsNeighbors.ts @@ -10,7 +10,9 @@ export async function getRatingsNeighbors( count: number, usePatientFetcher = false, ) { - const fetcher = usePatientFetcher ? patientFetch : fetch; + const fetcher = usePatientFetcher + ? patientFetch + : (url: string) => fetch(url, { cache: 'no-store' }); const response = await fetcher( withCORSProxy( `https://${regionToRegionSubdomain( From ca8dd5a7026f2f3d3b02ca5ce61db076d7886d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:29:57 -0600 Subject: [PATCH 69/88] fix: no existing cache overwrites for neighbors --- src/app/tools/ratings/page.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 365b77fc4..df0f09055 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -282,10 +282,14 @@ export default function Page() { usePlayersCache.setState( produce((draft: PlayersCache) => { neighbors.forEach((neighbor) => { - draft[region][season][neighbor.number - 1] = { - id: neighbor.spa_id, - score: neighbor.score, - } satisfies BlitzkriegRatingsLeaderboardEntry; + const position = neighbor.number - 1; + + if (!(position in draft[region][season])) { + draft[region][season][position] = { + id: neighbor.spa_id, + score: neighbor.score, + } satisfies BlitzkriegRatingsLeaderboardEntry; + } }); }), ); @@ -642,7 +646,7 @@ export default function Page() { }); cacheNeighbors( players[region][season][position].id, - ROWS_PER_PAGE * 3, + 4 * ROWS_PER_PAGE - 2, ); } } else { From aea01072fb0b18ff15775777000b9970310d49be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:37:09 -0600 Subject: [PATCH 70/88] fix: fetch neighbors after redundancy check --- src/app/tools/ratings/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index df0f09055..e367c7a9e 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -256,9 +256,7 @@ export default function Page() { id: number, size: number, ): Promise { - const { neighbors } = await getRatingsNeighbors(region, id, size, true); const radius = Math.round(size / 2); - const targetPosition = Object.entries(players[region][season]).find( ([, player]) => id === player.id, )?.[0]; @@ -279,6 +277,8 @@ export default function Page() { if (!isMissing) return []; } + const { neighbors } = await getRatingsNeighbors(region, id, size, true); + usePlayersCache.setState( produce((draft: PlayersCache) => { neighbors.forEach((neighbor) => { From c7995340095bd3453d5ef4e78adecbcf15266770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:42:38 -0600 Subject: [PATCH 71/88] feat: position relative neighbor caching --- src/app/tools/ratings/page.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index e367c7a9e..60bc17e71 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -277,12 +277,18 @@ export default function Page() { if (!isMissing) return []; } + const seedingPlayerPosition = parseInt( + Object.entries(players[region][season]).find( + ([, player]) => id === player.id, + )![0], + ); const { neighbors } = await getRatingsNeighbors(region, id, size, true); usePlayersCache.setState( produce((draft: PlayersCache) => { - neighbors.forEach((neighbor) => { - const position = neighbor.number - 1; + neighbors.forEach((neighbor, neighborIndex) => { + // const position = neighbor.number - 1; + const position = neighborIndex - radius + seedingPlayerPosition; if (!(position in draft[region][season])) { draft[region][season][position] = { From deb9e463e1c9147f7eb4b7fbd16e3437c3bc5011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Nov 2023 11:24:38 -0600 Subject: [PATCH 72/88] feat: highlighted player stats --- src/app/tools/ratings/page.tsx | 70 +++++++++++++++++++ .../Leaderboard/components/Item.tsx | 1 + 2 files changed, 71 insertions(+) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 60bc17e71..a6ae8eb47 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -7,6 +7,7 @@ import { Dialog, Flex, Select, + Table, Text, TextField, } from '@radix-ui/themes'; @@ -489,6 +490,39 @@ export default function Page() { } } + const highlightedId = highlightedPlayer + ? highlightedPlayer.type === 'id' + ? highlightedPlayer.id + : players[region][season][highlightedPlayer.position]?.id + : undefined; + const highlightedUsername = + (highlightedPlayer && highlightedId + ? names[region][highlightedId]?.nickname + : undefined) ?? '--'; + const highlightedClan = + highlightedPlayer && highlightedId + ? names[region][highlightedId]?.clan + : undefined; + const highlightedPosition = highlightedPlayer + ? highlightedPlayer.type === 'position' + ? highlightedPlayer.position + : (() => { + const stringId = Object.entries(players[region][season]).find( + ([, player]) => player.id === highlightedId, + )?.[0]; + return stringId === undefined ? undefined : parseInt(stringId); + })() + : undefined; + const totalPlayers = + !ratingsInfo?.detail && ratingsInfo?.count ? ratingsInfo.count : undefined; + const highlightedPercentile = + highlightedPosition && totalPlayers + ? Math.ceil((highlightedPosition / totalPlayers) * 100) + : undefined; + const highlightedScore = highlightedPosition + ? players[region][season][highlightedPosition]?.score + : undefined; + return ( @@ -889,6 +923,39 @@ export default function Page() { + + + + Selected player + Position + Percentile + Score + + + + + + + {highlightedUsername} + {highlightedClan ? ` [${highlightedClan}]` : ''} + + + {highlightedPosition + ? (highlightedPosition + 1).toLocaleString() + : '--'} + {' / '} + {totalPlayers?.toLocaleString() ?? '--'} + + + {highlightedPercentile ? `Top ${highlightedPercentile}%` : '--'} + + + {highlightedScore?.toLocaleString() ?? '--'} + + + + + { + setHighlightedPlayer({ type: 'position', position: index }); + }} /> ); }) diff --git a/src/components/Leaderboard/components/Item.tsx b/src/components/Leaderboard/components/Item.tsx index e9554c48b..79a707ebe 100644 --- a/src/components/Leaderboard/components/Item.tsx +++ b/src/components/Leaderboard/components/Item.tsx @@ -32,6 +32,7 @@ export function Item({
Date: Tue, 14 Nov 2023 12:40:49 -0600 Subject: [PATCH 73/88] feat: radix table --- src/app/tools/ratings/page.tsx | 165 +++++++++++++-------------------- src/stitches.config.ts | 2 + 2 files changed, 65 insertions(+), 102 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index a6ae8eb47..dc7d320e2 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -17,7 +17,6 @@ import { ChangeEvent, useEffect, useRef, useState } from 'react'; import { create } from 'zustand'; import { BlitzkriegRatingsLeaderboardEntry } from '../../../../scripts/buildRatingsLeaderboard'; import { RatingsInfo, RatingsPlayer } from '../../../commands/ratings'; -import * as Leaderboard from '../../../components/Leaderboard'; import PageWrapper from '../../../components/PageWrapper'; import { LEAGUES } from '../../../constants/leagues'; import { FIRST_ARCHIVED_RATINGS_SEASON } from '../../../constants/ratings'; @@ -36,6 +35,7 @@ import { import { getArchivedLatestSeasonNumber } from '../../../core/blitzkrieg/getArchivedLatestSeasonNumber'; import getArchivedRatingsInfo from '../../../core/blitzkrieg/getArchivedRatingsInfo'; import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboard'; +import { theme } from '../../../stitches.config'; import { PageTurner } from './components/PageTurner'; const ROWS_PER_PAGE = 30; @@ -490,38 +490,8 @@ export default function Page() { } } - const highlightedId = highlightedPlayer - ? highlightedPlayer.type === 'id' - ? highlightedPlayer.id - : players[region][season][highlightedPlayer.position]?.id - : undefined; - const highlightedUsername = - (highlightedPlayer && highlightedId - ? names[region][highlightedId]?.nickname - : undefined) ?? '--'; - const highlightedClan = - highlightedPlayer && highlightedId - ? names[region][highlightedId]?.clan - : undefined; - const highlightedPosition = highlightedPlayer - ? highlightedPlayer.type === 'position' - ? highlightedPlayer.position - : (() => { - const stringId = Object.entries(players[region][season]).find( - ([, player]) => player.id === highlightedId, - )?.[0]; - return stringId === undefined ? undefined : parseInt(stringId); - })() - : undefined; const totalPlayers = !ratingsInfo?.detail && ratingsInfo?.count ? ratingsInfo.count : undefined; - const highlightedPercentile = - highlightedPosition && totalPlayers - ? Math.ceil((highlightedPosition / totalPlayers) * 100) - : undefined; - const highlightedScore = highlightedPosition - ? players[region][season][highlightedPosition]?.score - : undefined; return ( @@ -923,39 +893,6 @@ export default function Page() { - - - - Selected player - Position - Percentile - Score - - - - - - - {highlightedUsername} - {highlightedClan ? ` [${highlightedClan}]` : ''} - - - {highlightedPosition - ? (highlightedPosition + 1).toLocaleString() - : '--'} - {' / '} - {totalPlayers?.toLocaleString() ?? '--'} - - - {highlightedPercentile ? `Top ${highlightedPercentile}%` : '--'} - - - {highlightedScore?.toLocaleString() ?? '--'} - - - - - - - {players === null ? ( - - ) : ( - range( - page * ROWS_PER_PAGE, - Math.min( - page * ROWS_PER_PAGE + ROWS_PER_PAGE, - ratingsInfo?.detail ? 0 : (ratingsInfo?.count ?? 1) - 1, - ), - ).map((index) => { - const id = players[region][season]?.[index]?.id; + + + + Position + Player + + Percentile + + Score + + - return ( - { - setHighlightedPlayer({ type: 'position', position: index }); - }} - /> - ); - }) - )} - + {range( + page * ROWS_PER_PAGE, + Math.min( + page * ROWS_PER_PAGE + ROWS_PER_PAGE, + ratingsInfo?.detail ? 0 : (ratingsInfo?.count ?? 1) - 1, + ), + ).map((index) => { + const id = players[region][season]?.[index]?.id; + const clan = id ? names[region][id]?.clan : undefined; + const highlight = highlightedPlayer + ? highlightedPlayer.type === 'id' + ? highlightedPlayer.id === id + : highlightedPlayer.position === index + : false; + const row = ( + { + setHighlightedPlayer({ type: 'position', position: index }); + }} + > + + {(index + 1).toLocaleString()}. + + + {names[region][id] === null + ? `Deleted player ${id}` + : names[region][id]?.nickname ?? `Loading player...`} + {clan ? ` [${clan}]` : ''} + + + {totalPlayers + ? Math.ceil(((index + 1) / totalPlayers) * 100) + : '--'} + % + + + {players[region][season]?.[index]?.score.toLocaleString()} + + + ); + + return row; + })} + Date: Tue, 14 Nov 2023 13:07:17 -0600 Subject: [PATCH 74/88] feat: rewards --- .../ratings/current/league/[league]/route.ts | 2 +- src/app/tools/ratings/page.tsx | 44 +++++++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/app/api/[region]/ratings/current/league/[league]/route.ts b/src/app/api/[region]/ratings/current/league/[league]/route.ts index 4e2b57735..9871416e4 100644 --- a/src/app/api/[region]/ratings/current/league/[league]/route.ts +++ b/src/app/api/[region]/ratings/current/league/[league]/route.ts @@ -7,6 +7,6 @@ export async function GET( { params }: { params: { region: Region; league: number } }, ) { return NextResponse.json( - await getRatingsLeague(params.region, params.league), + await getRatingsLeague(params.region, params.league, true), ); } diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index dc7d320e2..be0232178 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -38,7 +38,7 @@ import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchi import { theme } from '../../../stitches.config'; import { PageTurner } from './components/PageTurner'; -const ROWS_PER_PAGE = 30; +const ROWS_PER_PAGE = Math.floor(100 / 3); const SEEDING_SIZE = 2 ** 7; interface NameCacheEntry { @@ -906,6 +906,7 @@ export default function Page() { Position Player + Reward Percentile @@ -922,12 +923,22 @@ export default function Page() { ).map((index) => { const id = players[region][season]?.[index]?.id; const clan = id ? names[region][id]?.clan : undefined; + const position = index + 1; const highlight = highlightedPlayer ? highlightedPlayer.type === 'id' ? highlightedPlayer.id === id : highlightedPlayer.position === index : false; - const row = ( + const reward = + !ratingsInfo?.detail && ratingsInfo?.rewards + ? ratingsInfo.rewards.find( + (reward) => + reward.from_position <= position && + reward.to_position >= position, + ) + : undefined; + + return ( - - {(index + 1).toLocaleString()}. - + {position.toLocaleString()}. {names[region][id] === null ? `Deleted player ${id}` : names[region][id]?.nickname ?? `Loading player...`} {clan ? ` [${clan}]` : ''} - + + {reward ? ( + + + {reward.count === 1 ? '' : `x${reward.count}`} + + ) : ( + '--' + )} + + {totalPlayers ? Math.ceil(((index + 1) / totalPlayers) * 100) : '--'} @@ -960,8 +990,6 @@ export default function Page() { ); - - return row; })} From 02a03b4d685fb22e9bf8243ce2a39c025dc5f978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Nov 2023 13:42:15 -0600 Subject: [PATCH 75/88] feat: advanced PSAs --- src/core/blitz/getRatingsInfo.ts | 4 +- src/core/blitzkrieg/patientFetch.ts | 13 ++++- src/core/discord/psa.ts | 26 +++++++-- .../handlers/chatInputCommand.ts | 56 ++++++++++++++++++- 4 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/core/blitz/getRatingsInfo.ts b/src/core/blitz/getRatingsInfo.ts index 15dc750f2..6c77df9f7 100644 --- a/src/core/blitz/getRatingsInfo.ts +++ b/src/core/blitz/getRatingsInfo.ts @@ -1,15 +1,17 @@ import { RatingsInfo } from '../../commands/ratings'; import { Region } from '../../constants/regions'; import { context } from '../blitzkrieg/context'; +import { patientFetch } from '../blitzkrieg/patientFetch'; import regionToRegionSubdomain from './regionToRegionSubdomain'; export default async function getRatingsInfo(region: Region) { - const response = await fetch( + const response = await patientFetch( context === 'website' ? `/api/${region}/ratings/current/info` : `https://${regionToRegionSubdomain( region, )}.wotblitz.com/en/api/rating-leaderboards/season/`, + undefined, { cache: 'no-store' }, ); diff --git a/src/core/blitzkrieg/patientFetch.ts b/src/core/blitzkrieg/patientFetch.ts index 4a8d17121..ebb4e1f71 100644 --- a/src/core/blitzkrieg/patientFetch.ts +++ b/src/core/blitzkrieg/patientFetch.ts @@ -1,11 +1,18 @@ -export async function patientFetch(url: string, timeout = 1000) { +export async function patientFetch( + url: string, + timeout = 1000, + options?: Partial<{ cache: 'no-store' }>, +) { let patientResponse: Response; while (patientResponse! === undefined) { try { - patientResponse = await fetch(url); + patientResponse = await fetch(url, options); } catch (error) { - console.warn(`Failed to fetch ${url}; retrying in ${timeout}ms...`); + console.warn( + `Failed to fetch ${url}; retrying in ${timeout}ms...`, + error, + ); await new Promise((resolve) => setTimeout(resolve, timeout)); } } diff --git a/src/core/discord/psa.ts b/src/core/discord/psa.ts index 06441f663..7fd6fff7c 100644 --- a/src/core/discord/psa.ts +++ b/src/core/discord/psa.ts @@ -1,13 +1,29 @@ -export interface PSAData { - title: string; - description: string; -} +export type PSAData = { + secret?: boolean; + links?: { + title: string; + url: string; + }[]; +} & ( + | { + type: 'image'; + image: string; + title?: string; + } + | { + type: 'embed'; + title: string; + description: string; + } +); export interface PSA { data: PSAData | null; } -export const psa: PSA = { data: null }; +export const psa: PSA = { + data: null, +}; fetch( 'https://gist.githubusercontent.com/tresabhi/ed4b136f08856e615212e573aabe1968/raw', diff --git a/src/events/interactionCreate/handlers/chatInputCommand.ts b/src/events/interactionCreate/handlers/chatInputCommand.ts index fbad9dc6b..471d08248 100644 --- a/src/events/interactionCreate/handlers/chatInputCommand.ts +++ b/src/events/interactionCreate/handlers/chatInputCommand.ts @@ -1,13 +1,16 @@ import { ActionRowBuilder, + AttachmentBuilder, ButtonBuilder, ButtonStyle, CacheType, ChatInputCommandInteraction, + InteractionReplyOptions, } from 'discord.js'; import { commands } from '..'; import discord from '../../../../discord.json' assert { type: 'json' }; import { UserError } from '../../../core/blitzkrieg/userError'; +import buttonLink from '../../../core/discord/buttonLink'; import embedNegative from '../../../core/discord/embedNegative'; import embedWarning from '../../../core/discord/embedWarning'; import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable'; @@ -40,12 +43,59 @@ export default async function handleChatInputCommand( const reply = await normalizeInteractionReturnable(returnable!); - if (psa.data) { - if (!reply.embeds) reply.embeds = []; - reply.embeds.push(embedWarning(psa.data.title, psa.data.description)); + if (psa.data && !psa.data.secret) { + if (psa.data.type === 'embed') { + if (!reply.embeds) reply.embeds = []; + reply.embeds.push(embedWarning(psa.data.title, psa.data.description)); + } else { + if (!reply.files) reply.files = []; + reply.files.push(new AttachmentBuilder(psa.data.image)); + reply.content = reply.content ?? psa.data.title; + } + + if (psa.data.links) { + if (!reply.components?.[0]) { + reply.components = [new ActionRowBuilder()]; + } + + (reply.components[0] as ActionRowBuilder).addComponents( + psa.data.links.map(({ title, url }) => buttonLink(url, title)), + ); + } } await interaction.editReply(reply); + + if (psa.data && psa.data.secret) { + const followUp: InteractionReplyOptions = { + ephemeral: true, + }; + + if (psa.data.type === 'embed') { + if (!followUp.embeds) followUp.embeds = []; + followUp.embeds.push( + embedWarning(psa.data.title, psa.data.description), + ); + } else { + if (!followUp.files) followUp.files = []; + followUp.files.push(new AttachmentBuilder(psa.data.image)); + followUp.content = followUp.content ?? psa.data.title; + } + + if (psa.data.links) { + if (!followUp.components?.[0]) { + followUp.components = [new ActionRowBuilder()]; + } + + ( + followUp.components[0] as ActionRowBuilder + ).addComponents( + psa.data.links.map(({ title, url }) => buttonLink(url, title)), + ); + } + + interaction.followUp(followUp); + } } catch (error) { const components = [ new ActionRowBuilder().addComponents( From 0d1eb9b12e44631b4fb644554988c90f00e9fb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Nov 2023 20:12:34 -0600 Subject: [PATCH 76/88] feat: better reward handling --- src/app/tools/ratings/page.tsx | 65 ++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index be0232178..36617dd15 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -906,7 +906,9 @@ export default function Page() { Position Player - Reward + {season === 0 && ( + Reward + )} Percentile @@ -946,46 +948,55 @@ export default function Page() { : 'unset', cursor: 'pointer', }} - key={index} + key={`${region}${season}${index}`} onClick={() => { setHighlightedPlayer({ type: 'position', position: index }); }} > - {position.toLocaleString()}. - + {position.toLocaleString()}. + {names[region][id] === null ? `Deleted player ${id}` : names[region][id]?.nickname ?? `Loading player...`} {clan ? ` [${clan}]` : ''} - - {reward ? ( - - - {reward.count === 1 ? '' : `x${reward.count}`} - - ) : ( - '--' - )} - - + {season === 0 && ( + + {reward ? ( + + { + + {reward.count === 1 ? '' : `x ${reward.count}`} + + + ) : ( + '--' + )} + + )} + {totalPlayers ? Math.ceil(((index + 1) / totalPlayers) * 100) : '--'} % - + {players[region][season]?.[index]?.score.toLocaleString()} From 933c468e2d55205540a025a7d9114f53e08f3d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Nov 2023 20:56:27 -0600 Subject: [PATCH 77/88] feat: split id and score --- src/app/tools/ratings/page.tsx | 259 +++++++++++++++++---------------- 1 file changed, 133 insertions(+), 126 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 36617dd15..a97ae6139 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -15,7 +15,6 @@ import { produce } from 'immer'; import { debounce, range } from 'lodash'; import { ChangeEvent, useEffect, useRef, useState } from 'react'; import { create } from 'zustand'; -import { BlitzkriegRatingsLeaderboardEntry } from '../../../../scripts/buildRatingsLeaderboard'; import { RatingsInfo, RatingsPlayer } from '../../../commands/ratings'; import PageWrapper from '../../../components/PageWrapper'; import { LEAGUES } from '../../../constants/leagues'; @@ -46,18 +45,16 @@ interface NameCacheEntry { clan?: string; } type NameCache = Record>; -type PlayersCache = Record< - Region, - Record> ->; +type LeaderboardCache = Record>>; type InfoCache = Record>; +type ScoreCache = Record>>; const useNameCache = create(() => ({ asia: {}, com: {}, eu: {}, })); -const usePlayersCache = create(() => ({ +const useLeaderboardCache = create(() => ({ asia: {}, com: {}, eu: {}, @@ -67,6 +64,11 @@ const useInfoCache = create(() => ({ com: {}, eu: {}, })); +const useScoreCache = create(() => ({ + asia: {}, + com: {}, + eu: {}, +})); export default function Page() { const [region, setRegion] = useState('com'); @@ -90,9 +92,10 @@ export default function Page() { const [latestArchivedSeasonNumber, setLatestArchivedSeasonNumber] = useState< number | null >(null); + const scores = useScoreCache()[region][season]; const names = useNameCache(); - const players = usePlayersCache(); + const leaderboard = useLeaderboardCache(); const positionInput = useRef(null); const scoreInput = useRef(null); @@ -121,94 +124,91 @@ export default function Page() { }, [season, region]); // initial leagues caching for current season useEffect(() => { - if (season in players[region]) return; + if (season in leaderboard[region]) return; if (season === 0) { (async () => { - const newNameCache: Record = {}; - const results = await Promise.all( - range(0, 5).map(async (league: number) => { - const leaguePlayers = await getRatingsLeague(region, league); - leaguePositionCache.current[league] = - leaguePlayers.result[0].number - 1; - leaguePlayers.result.forEach((player) => { - newNameCache[player.spa_id] = { - nickname: player.nickname, - clan: player.clan_tag, - }; + const results = ( + await Promise.all( + range(0, 5).map((league: number) => { + return getRatingsLeague(region, league); + }), + ) + ) + .map(({ result }) => result) + .flat(); + + useLeaderboardCache.setState( + produce((draft: LeaderboardCache) => { + if (!(season in draft[region])) draft[region][season] = {}; + + results.forEach((result) => { + draft[region][season][result.number - 1] = result.spa_id; }); - - return leaguePlayers.result.reduce< - Record - >( - (accumulator, player) => ({ - ...accumulator, - - [player.number - 1]: { - id: player.spa_id, - score: player.score, - } satisfies BlitzkriegRatingsLeaderboardEntry, - }), - {}, - ); - }), - ); - const newPlayers = results.reduce< - Record - >( - (accumulator, result) => ({ - ...accumulator, - ...result, - }), - {}, - ); - - usePlayersCache.setState( - produce((draft: PlayersCache) => { - draft[region][season] = { - ...draft[region][season], - ...newPlayers, - }; }), ); useNameCache.setState( produce((draft: NameCache) => { - draft[region] = { ...draft[region], ...newNameCache }; + results.forEach((result) => { + draft[region][result.spa_id] = { + nickname: result.nickname, + clan: result.clan_tag, + }; + }); + }), + ); + useScoreCache.setState( + produce((draft: ScoreCache) => { + if (!(season in draft[region])) draft[region][season] = {}; + + results.forEach((result) => { + draft[region][season][result.spa_id] = result.score; + }); }), ); })(); } else { - getArchivedRatingsLeaderboard(region, season).then((result) => { - usePlayersCache.setState( - produce((draft: PlayersCache) => { - draft[region][season] = { - ...draft[region][season], - ...result, - }; + getArchivedRatingsLeaderboard(region, season).then((results) => { + useLeaderboardCache.setState( + produce((draft: LeaderboardCache) => { + if (!(season in draft[region])) draft[region][season] = {}; + + results.forEach((result, index) => { + draft[region][season][index] = result.id; + }); + }), + ); + useScoreCache.setState( + produce((draft: ScoreCache) => { + if (!(season in draft[region])) draft[region][season] = {}; + + results.forEach((result) => { + draft[region][season][result.id] = result.score; + }); }), ); }); } - }, [season in players[region]]); + }, [season in leaderboard[region]]); // 2nd page caching for current season useEffect(() => { if ( season === 0 && - players !== null && - players[region][season] && - 0 in players[region][season] + leaderboard !== null && + leaderboard[region][season] && + 0 in leaderboard[region][season] ) { - cacheNeighbors(players[region][season][0].id, ROWS_PER_PAGE * 3); + cacheNeighbors(leaderboard[region][season][0], ROWS_PER_PAGE * 3); } - }, [players[region]?.[season] && 0 in players[region][season]]); + }, [leaderboard[region]?.[season] && 0 in leaderboard[region][season]]); // neighboring page caching for all seasons useEffect(() => { if (!ratingsInfo || ratingsInfo?.detail) return; if (season === 0) { const middleIndex = Math.round(page * ROWS_PER_PAGE + ROWS_PER_PAGE / 2); - if (!(middleIndex in players[region][season])) return; - const middleId = players[region][season][middleIndex].id; + if (!(middleIndex in leaderboard[region][season])) return; + const middleId = leaderboard[region][season][middleIndex]; cacheNeighbors(middleId, ROWS_PER_PAGE * 3); } else { const ids = range( @@ -218,7 +218,7 @@ export default function Page() { (page + 1) * ROWS_PER_PAGE + ROWS_PER_PAGE, ), ) - .map((index) => players[region][season][index].id) + .map((index) => leaderboard[region][season][index]) .filter(Boolean) .filter((id) => !(id in names[region])); @@ -244,7 +244,8 @@ export default function Page() { region, season, ratingsInfo?.detail, - players[region][season] !== undefined && 0 in players[region][season], + leaderboard[region][season] !== undefined && + 0 in leaderboard[region][season], ]); // latest archived season number caching useEffect(() => {}, [ @@ -258,8 +259,8 @@ export default function Page() { size: number, ): Promise { const radius = Math.round(size / 2); - const targetPosition = Object.entries(players[region][season]).find( - ([, player]) => id === player.id, + const targetPosition = Object.entries(leaderboard[region][season]).find( + ([, player]) => id === player, )?.[0]; if (targetPosition !== undefined) { @@ -271,7 +272,7 @@ export default function Page() { position <= targetPositionAsNumber + radius; position++ ) { - isMissing = !(position in players[region][season]); + isMissing = !(position in leaderboard[region][season]); if (isMissing) break; } @@ -279,28 +280,32 @@ export default function Page() { } const seedingPlayerPosition = parseInt( - Object.entries(players[region][season]).find( - ([, player]) => id === player.id, + Object.entries(leaderboard[region][season]).find( + ([, player]) => id === player, )![0], ); const { neighbors } = await getRatingsNeighbors(region, id, size, true); - usePlayersCache.setState( - produce((draft: PlayersCache) => { + useLeaderboardCache.setState( + produce((draft: LeaderboardCache) => { neighbors.forEach((neighbor, neighborIndex) => { - // const position = neighbor.number - 1; const position = neighborIndex - radius + seedingPlayerPosition; if (!(position in draft[region][season])) { - draft[region][season][position] = { - id: neighbor.spa_id, - score: neighbor.score, - } satisfies BlitzkriegRatingsLeaderboardEntry; + draft[region][season][position] = neighbor.spa_id; + } + }); + }), + ); + useScoreCache.setState( + produce((draft: ScoreCache) => { + neighbors.forEach((neighbor, neighborIndex) => { + if (!(neighbor.spa_id in draft[region][season])) { + draft[region][season][neighbor.spa_id] = neighbor.score; } }); }), ); - useNameCache.setState( produce((draft: NameCache) => { neighbors.forEach((neighbor) => { @@ -325,8 +330,8 @@ export default function Page() { ); if (season === 0) { - const playerEntries = Object.entries(players[region][season]); - let closestPlayer: BlitzkriegRatingsLeaderboardEntry | null = null; + const playerEntries = Object.entries(leaderboard[region][season]); + let closestPlayerId: number | null = null; let closestPosition: number | null = null; let closestPositionDistance = Infinity; @@ -337,11 +342,11 @@ export default function Page() { if (distance < closestPositionDistance) { closestPositionDistance = distance; closestPosition = position; - closestPlayer = player; + closestPlayerId = player; } }); - if (closestPlayer === null || closestPosition === null) return; + if (closestPlayerId === null || closestPosition === null) return; if (closestPositionDistance === 0) { setHighlightedPlayer({ type: 'position', position: targetPosition }); @@ -352,8 +357,7 @@ export default function Page() { // 1 going down and -1 going up const loadingDirection = Math.sign(targetPosition - closestPosition); - let seedingPlayer = (closestPlayer as BlitzkriegRatingsLeaderboardEntry) - .id; + let seedingPlayer = closestPlayerId as number; let targetPositionAcquired = false; let neighbors: RatingsPlayer[]; @@ -385,7 +389,7 @@ export default function Page() { } else { setPage(Math.floor(targetPosition / ROWS_PER_PAGE)); - if (players) { + if (leaderboard) { setHighlightedPlayer({ type: 'position', position: targetPosition, @@ -399,25 +403,25 @@ export default function Page() { const targetScore = scoreInput.current!.valueAsNumber; if (season === 0) { - const playerEntries = Object.entries(players[region][season]); - let closestPlayer: BlitzkriegRatingsLeaderboardEntry | null = null; + const playerEntries = Object.entries(leaderboard[region][season]); + let closestId: number | null = null; let closestScore: number | null = null; let closestScoreDistance = Infinity; let closesIndex = -1; - playerEntries.forEach(([positionAsString, player], index) => { + playerEntries.forEach(([positionAsString, id]) => { const position = parseInt(positionAsString); - const distance = Math.abs(player.score - targetScore); + const distance = Math.abs(scores[id] - targetScore); if (distance < closestScoreDistance) { closestScoreDistance = distance; - closestScore = player.score; - closestPlayer = player; + closestScore = scores[id]; + closestId = id; closesIndex = position; } }); - if (closestPlayer === null || closestScore === null) return; + if (closestId === null || closestScore === null) return; if (closestScoreDistance === 0) { setHighlightedPlayer({ type: 'id', id: closesIndex }); @@ -427,16 +431,15 @@ export default function Page() { const loadingDirection = Math.sign(closestScore - targetScore); let targetPlayer: RatingsPlayer; let scoreAcquired = false; - let seedingPlayer = closestPlayer as BlitzkriegRatingsLeaderboardEntry; + let seedingId = closestId as number; + let seedingScore = scores[seedingId]; setLoadingProgress([0, closestScoreDistance]); while (!scoreAcquired) { - const neighbors = await cacheNeighbors(seedingPlayer.id, SEEDING_SIZE); + const neighbors = await cacheNeighbors(seedingId, SEEDING_SIZE); const seedingNeighbor = neighbors.at(loadingDirection === 1 ? -1 : 0)!; - seedingPlayer = { - id: seedingNeighbor.spa_id, - score: seedingNeighbor.score, - }; + seedingId = seedingNeighbor.spa_id; + seedingScore = scores[seedingId]; setLoadingProgress([ closestScoreDistance - Math.abs(seedingNeighbor.score - targetScore), @@ -444,9 +447,9 @@ export default function Page() { ]); if (loadingDirection === 1) { - scoreAcquired = seedingPlayer.score <= targetScore; + scoreAcquired = seedingScore <= targetScore; } else { - scoreAcquired = seedingPlayer.score >= targetScore; + scoreAcquired = seedingScore >= targetScore; } if (scoreAcquired) { @@ -469,9 +472,9 @@ export default function Page() { let playerIndex = -1; for (let index = ratingsInfo.count; index >= 0; index--) { - if (!(index in players[region][season])) continue; + if (!(index in leaderboard[region][season])) continue; - if (players[region][season][index].score >= targetScore) { + if (scores[leaderboard[region][season][index]] >= targetScore) { playerIndex = index; break; } @@ -481,7 +484,7 @@ export default function Page() { setPage(Math.floor(playerIndex / ROWS_PER_PAGE)); - if (players) { + if (leaderboard) { setHighlightedPlayer({ type: 'position', position: playerIndex, @@ -649,18 +652,22 @@ export default function Page() { const newPage = Math.floor(position / ROWS_PER_PAGE); setPage(newPage); - if (players) { + if (leaderboard) { setHighlightedPlayer({ type: 'id', - id: players[region][season][position].id, + id: leaderboard[region][season][position], }); cacheNeighbors( - players[region][season][position].id, + leaderboard[region][season][position], 4 * ROWS_PER_PAGE - 2, ); } } else { - if (!ratingsInfo || ratingsInfo.detail || !players) + if ( + !ratingsInfo || + ratingsInfo.detail || + !leaderboard + ) return; const minScore = @@ -673,7 +680,8 @@ export default function Page() { index++ ) { if ( - players[region][season][index].score < minScore + scores[leaderboard[region][season][index]] < + minScore ) { firstPlayerIndex = index; break; @@ -685,7 +693,7 @@ export default function Page() { setPage(Math.floor(firstPlayerIndex / ROWS_PER_PAGE)); setHighlightedPlayer({ type: 'id', - id: players[region][season][firstPlayerIndex].id, + id: leaderboard[region][season][firstPlayerIndex], }); } }} @@ -732,18 +740,15 @@ export default function Page() { skip: false; })[]; - usePlayersCache.setState( - produce((draft: PlayersCache) => { + useLeaderboardCache.setState( + produce((draft: LeaderboardCache) => { if (draft) { - values.map((player) => { + values.forEach((player) => { const playerWithTypes = player as typeof player & { skip: false }; draft[region][season][ playerWithTypes.number - 1 - ] = { - id: playerWithTypes.spa_id, - score: 0, - }; + ] = playerWithTypes.spa_id; }); } }), @@ -774,7 +779,7 @@ export default function Page() { `https://api.wotblitz.${region}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}`, ); const playerKeys = Object.keys( - players[region][season], + leaderboard[region][season], ); setSearchResults( @@ -784,9 +789,9 @@ export default function Page() { number: parseInt( playerKeys.find( (indexAsString) => - players[region][season][ + leaderboard[region][season][ parseInt(indexAsString) - ].id === searchedPlayer.account_id, + ] === searchedPlayer.account_id, )!, ), })) @@ -837,7 +842,7 @@ export default function Page() { index++ ) { if ( - players[region][season][index].id === + leaderboard[region][season][index] === player.account_id ) { playerIndex = index; @@ -923,7 +928,7 @@ export default function Page() { ratingsInfo?.detail ? 0 : (ratingsInfo?.count ?? 1) - 1, ), ).map((index) => { - const id = players[region][season]?.[index]?.id; + const id = leaderboard[region][season]?.[index]; const clan = id ? names[region][id]?.clan : undefined; const position = index + 1; const highlight = highlightedPlayer @@ -997,7 +1002,9 @@ export default function Page() { % - {players[region][season]?.[index]?.score.toLocaleString()} + {(leaderboard[region][season]?.[index] + ? scores[leaderboard[region][season][index]]?.toLocaleString() + : undefined) ?? '--'} ); From 171da2a194ab54385c36bbb34c5ae0aa8ad341bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:00:54 -0600 Subject: [PATCH 78/88] fix: no search caching --- src/app/tools/ratings/page.tsx | 1 + src/core/blitz/searchCurrentRatingsPlayers.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index a97ae6139..dbc6c3cc5 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -746,6 +746,7 @@ export default function Page() { values.forEach((player) => { const playerWithTypes = player as typeof player & { skip: false }; + draft[region][season][ playerWithTypes.number - 1 ] = playerWithTypes.spa_id; diff --git a/src/core/blitz/searchCurrentRatingsPlayers.ts b/src/core/blitz/searchCurrentRatingsPlayers.ts index 259618cb8..4be3a943c 100644 --- a/src/core/blitz/searchCurrentRatingsPlayers.ts +++ b/src/core/blitz/searchCurrentRatingsPlayers.ts @@ -35,6 +35,7 @@ export async function searchCurrentRatingsPlayers( : `https://${regionToRegionSubdomain( region, )}.wotblitz.com/en/api/rating-leaderboards/search/?prefix=${encodedSearch}`, + { cache: 'no-store' }, ); const accountList = (await response.json()) as CurrentRatingsPlayerSearch; From 2909e61721a713994a857103cd17407b43454b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:13:09 -0600 Subject: [PATCH 79/88] feat: middle-most caching --- src/app/tools/ratings/page.tsx | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index dbc6c3cc5..b67e59609 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -206,10 +206,22 @@ export default function Page() { if (!ratingsInfo || ratingsInfo?.detail) return; if (season === 0) { - const middleIndex = Math.round(page * ROWS_PER_PAGE + ROWS_PER_PAGE / 2); - if (!(middleIndex in leaderboard[region][season])) return; - const middleId = leaderboard[region][season][middleIndex]; - cacheNeighbors(middleId, ROWS_PER_PAGE * 3); + const middleIndex = page * ROWS_PER_PAGE + ROWS_PER_PAGE / 2; + const best = range( + page * ROWS_PER_PAGE, + page * ROWS_PER_PAGE + ROWS_PER_PAGE + 1, + ) + .filter((index) => index in leaderboard[region][season]) + .map((index) => ({ index, distance: Math.abs(index - middleIndex) })) + .sort((a, b) => b.distance - a.distance) + .at(0); + + if (best === undefined) return; + + cacheNeighbors( + leaderboard[region][season][best.index], + ROWS_PER_PAGE * 3, + ); } else { const ids = range( Math.max(0, (page - 1) * ROWS_PER_PAGE), @@ -258,6 +270,8 @@ export default function Page() { id: number, size: number, ): Promise { + console.log(id, 'working'); + const radius = Math.round(size / 2); const targetPosition = Object.entries(leaderboard[region][season]).find( ([, player]) => id === player, @@ -276,7 +290,10 @@ export default function Page() { if (isMissing) break; } - if (!isMissing) return []; + if (!isMissing) { + console.log(id, 'skipping'); + return []; + } } const seedingPlayerPosition = parseInt( @@ -319,6 +336,8 @@ export default function Page() { }), ); + console.log(id, 'cached'); + return neighbors; } async function jumpToPosition(position: number) { From 2bb4c78551d495b5236df2d049e762a71b81e79a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:00:57 -0600 Subject: [PATCH 80/88] fix: smart neighbor count for next page caching --- src/app/tools/ratings/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index b67e59609..2f477ea9d 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -220,7 +220,7 @@ export default function Page() { cacheNeighbors( leaderboard[region][season][best.index], - ROWS_PER_PAGE * 3, + 2 * (Math.floor(ROWS_PER_PAGE / 2) + best.distance + ROWS_PER_PAGE), ); } else { const ids = range( From a63415461afaed0e12b8c1cb3d34260a37bf1e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:16:49 -0600 Subject: [PATCH 81/88] feat: non-loaded page blocking --- .../components/PageTurner/index.css.ts | 13 ++++++ .../{PageTurner.tsx => PageTurner/index.tsx} | 44 ++++++++++++++++--- src/app/tools/ratings/page.tsx | 10 +++++ 3 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 src/app/tools/ratings/components/PageTurner/index.css.ts rename src/app/tools/ratings/components/{PageTurner.tsx => PageTurner/index.tsx} (58%) diff --git a/src/app/tools/ratings/components/PageTurner/index.css.ts b/src/app/tools/ratings/components/PageTurner/index.css.ts new file mode 100644 index 000000000..e7521e1c9 --- /dev/null +++ b/src/app/tools/ratings/components/PageTurner/index.css.ts @@ -0,0 +1,13 @@ +import { keyframes, style } from '@vanilla-extract/css'; + +const spinKeyframes = keyframes({ + from: { transform: 'rotate(0deg)' }, + to: { transform: 'rotate(360deg)' }, +}); + +export const infiniteSpin = style({ + animationName: spinKeyframes, + animationIterationCount: 'infinite', + animationTimingFunction: 'linear', + animationDuration: '1s', +}); diff --git a/src/app/tools/ratings/components/PageTurner.tsx b/src/app/tools/ratings/components/PageTurner/index.tsx similarity index 58% rename from src/app/tools/ratings/components/PageTurner.tsx rename to src/app/tools/ratings/components/PageTurner/index.tsx index 850b89ba5..ba009d392 100644 --- a/src/app/tools/ratings/components/PageTurner.tsx +++ b/src/app/tools/ratings/components/PageTurner/index.tsx @@ -1,15 +1,39 @@ -import { CaretLeftIcon, CaretRightIcon } from '@radix-ui/react-icons'; +import { + CaretLeftIcon, + CaretRightIcon, + SymbolIcon, +} from '@radix-ui/react-icons'; import { Button, Flex, TextField } from '@radix-ui/themes'; +import { range } from 'lodash'; import { useEffect, useRef } from 'react'; +import { infiniteSpin } from './index.css'; interface PageTurnerProps { + leaderboard: Record; page: number; + rowsPerPage: number; pages: number; onPageChange: (page: number, isButtonClick: boolean) => void; + totalPlayers: number; } -export function PageTurner({ page, onPageChange, pages }: PageTurnerProps) { +export function PageTurner({ + page, + rowsPerPage, + onPageChange, + pages, + leaderboard, + totalPlayers, +}: PageTurnerProps) { const pageInput = useRef(null); + const rightLoading = range( + page * rowsPerPage, + Math.min(totalPlayers - 1, page * rowsPerPage + rowsPerPage + 1), + ).some((index) => !(index in leaderboard)); + const leftLoading = range( + Math.max(0, page * rowsPerPage - 1), + Math.max(0, page * rowsPerPage - rowsPerPage - 1) + 1, + ).some((index) => !(index in leaderboard)); useEffect(() => { if (pageInput.current) pageInput.current.value = `${page + 1}`; @@ -21,9 +45,13 @@ export function PageTurner({ page, onPageChange, pages }: PageTurnerProps) { Page @@ -53,9 +81,13 @@ export function PageTurner({ page, onPageChange, pages }: PageTurnerProps) { onClick={() => { onPageChange(Math.min(page + 1, pages - 1), true); }} - disabled={page === pages - 1} + disabled={page === pages - 1 || rightLoading} > - + {rightLoading ? ( + + ) : ( + + )} diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 2f477ea9d..6a48fdffd 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -919,6 +919,11 @@ export default function Page() { @@ -1032,6 +1037,11 @@ export default function Page() { From 20da294b36ac360daf1a28462d585f932aec854e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:18:32 -0600 Subject: [PATCH 82/88] feat: empty no-rewards --- src/app/tools/ratings/page.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 6a48fdffd..9d98293a9 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -1015,9 +1015,7 @@ export default function Page() { {reward.count === 1 ? '' : `x ${reward.count}`} - ) : ( - '--' - )} + ) : null} )} From 8f1017195fb79ef8a6835c97d672c0d24643a8b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 15 Nov 2023 16:42:52 -0600 Subject: [PATCH 83/88] fix: league jumping for current season --- src/app/tools/ratings/page.tsx | 83 +++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 9d98293a9..416590b86 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -17,6 +17,7 @@ import { ChangeEvent, useEffect, useRef, useState } from 'react'; import { create } from 'zustand'; import { RatingsInfo, RatingsPlayer } from '../../../commands/ratings'; import PageWrapper from '../../../components/PageWrapper'; +import { Skeleton } from '../../../components/Skeleton'; import { LEAGUES } from '../../../constants/leagues'; import { FIRST_ARCHIVED_RATINGS_SEASON } from '../../../constants/ratings'; import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; @@ -42,7 +43,7 @@ const SEEDING_SIZE = 2 ** 7; interface NameCacheEntry { nickname: string; - clan?: string; + clan: string | null; } type NameCache = Record>; type LeaderboardCache = Record>>; @@ -99,7 +100,11 @@ export default function Page() { const positionInput = useRef(null); const scoreInput = useRef(null); - const leaguePositionCache = useRef>({}); + const leaguePositionCache = useRef>>({ + asia: {}, + com: {}, + eu: {}, + }); const pages = Math.ceil( (ratingsInfo && ratingsInfo.detail === undefined ? ratingsInfo?.count : 1) / @@ -122,7 +127,7 @@ export default function Page() { ); })(); }, [season, region]); - // initial leagues caching for current season + // initial caching useEffect(() => { if (season in leaderboard[region]) return; @@ -130,8 +135,12 @@ export default function Page() { (async () => { const results = ( await Promise.all( - range(0, 5).map((league: number) => { - return getRatingsLeague(region, league); + range(0, 5).map(async (league: number) => { + const data = await getRatingsLeague(region, league); + leaguePositionCache.current[region][league] = + data.result[0].number - 1; + + return data; }), ) ) @@ -242,7 +251,7 @@ export default function Page() { draft[region][ids[index]] = player ? { nickname: player.account_name, - clan: player.clan?.tag, + clan: player.clan?.tag ?? null, } : null; }), @@ -270,8 +279,6 @@ export default function Page() { id: number, size: number, ): Promise { - console.log(id, 'working'); - const radius = Math.round(size / 2); const targetPosition = Object.entries(leaderboard[region][season]).find( ([, player]) => id === player, @@ -291,16 +298,17 @@ export default function Page() { } if (!isMissing) { - console.log(id, 'skipping'); return []; } } - const seedingPlayerPosition = parseInt( - Object.entries(leaderboard[region][season]).find( - ([, player]) => id === player, - )![0], - ); + const seedingPlayerNeighborEntry = Object.entries( + leaderboard[region][season], + ).find(([, player]) => id === player); + + if (seedingPlayerNeighborEntry === undefined) return []; + + const seedingPlayerPosition = parseInt(seedingPlayerNeighborEntry[0]); const { neighbors } = await getRatingsNeighbors(region, id, size, true); useLeaderboardCache.setState( @@ -316,7 +324,7 @@ export default function Page() { ); useScoreCache.setState( produce((draft: ScoreCache) => { - neighbors.forEach((neighbor, neighborIndex) => { + neighbors.forEach((neighbor) => { if (!(neighbor.spa_id in draft[region][season])) { draft[region][season][neighbor.spa_id] = neighbor.score; } @@ -336,8 +344,6 @@ export default function Page() { }), ); - console.log(id, 'cached'); - return neighbors; } async function jumpToPosition(position: number) { @@ -667,7 +673,7 @@ export default function Page() { onClick={async () => { if (season === 0) { const position = - leaguePositionCache.current[jumpToLeague]; + leaguePositionCache.current[region][jumpToLeague]; const newPage = Math.floor(position / ROWS_PER_PAGE); setPage(newPage); @@ -985,10 +991,28 @@ export default function Page() { > {position.toLocaleString()}. - {names[region][id] === null - ? `Deleted player ${id}` - : names[region][id]?.nickname ?? `Loading player...`} - {clan ? ` [${clan}]` : ''} + + {names[region][id] === null + ? `Deleted player ${id}` + : names[region][id]?.nickname ?? ( + + )} + {clan ? ( + {`[${clan}]`} + ) : ( + clan === undefined && + names[region][id] !== null && ( + + ) + )} + {season === 0 && ( @@ -1019,15 +1043,20 @@ export default function Page() { )} - {totalPlayers - ? Math.ceil(((index + 1) / totalPlayers) * 100) - : '--'} - % + {totalPlayers ? ( + `${Math.ceil(((index + 1) / totalPlayers) * 100)}%` + ) : ( + + )} {(leaderboard[region][season]?.[index] ? scores[leaderboard[region][season][index]]?.toLocaleString() - : undefined) ?? '--'} + : undefined) ?? ( + + )} ); From 9257ebdd1e215b86d6eb65f090136fb2e618315c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 15 Nov 2023 16:42:56 -0600 Subject: [PATCH 84/88] feat: skeletons --- src/components/Skeleton/index.css.ts | 13 +++++++++++++ src/components/Skeleton/index.tsx | 6 ++++++ 2 files changed, 19 insertions(+) create mode 100644 src/components/Skeleton/index.css.ts create mode 100644 src/components/Skeleton/index.tsx diff --git a/src/components/Skeleton/index.css.ts b/src/components/Skeleton/index.css.ts new file mode 100644 index 000000000..99acbb31b --- /dev/null +++ b/src/components/Skeleton/index.css.ts @@ -0,0 +1,13 @@ +import { slateDarkA } from '@radix-ui/colors'; +import { keyframes, style } from '@vanilla-extract/css'; + +const animation = keyframes({ + '0%': { backgroundColor: slateDarkA.slateA2 }, + '50%': { backgroundColor: slateDarkA.slateA3 }, + '100%': { backgroundColor: slateDarkA.slateA2 }, +}); + +export const skeleton = style({ + borderRadius: 8, + animation: `${animation} 2s ease-in-out infinite`, +}); diff --git a/src/components/Skeleton/index.tsx b/src/components/Skeleton/index.tsx new file mode 100644 index 000000000..ca110b831 --- /dev/null +++ b/src/components/Skeleton/index.tsx @@ -0,0 +1,6 @@ +import { ComponentProps } from 'react'; +import { skeleton } from './index.css'; + +export function Skeleton({ className, ...props }: ComponentProps<'div'>) { + return
; +} From 6c36f343ea23af48bbaa655ef7ffc4fd6013cdb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 15 Nov 2023 19:21:08 -0600 Subject: [PATCH 85/88] fix: current season score jumping --- src/app/tools/ratings/page.tsx | 50 ++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 416590b86..918811615 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -278,7 +278,7 @@ export default function Page() { async function cacheNeighbors( id: number, size: number, - ): Promise { + ): Promise { const radius = Math.round(size / 2); const targetPosition = Object.entries(leaderboard[region][season]).find( ([, player]) => id === player, @@ -298,7 +298,7 @@ export default function Page() { } if (!isMissing) { - return []; + return null; } } @@ -306,7 +306,7 @@ export default function Page() { leaderboard[region][season], ).find(([, player]) => id === player); - if (seedingPlayerNeighborEntry === undefined) return []; + if (seedingPlayerNeighborEntry === undefined) return null; const seedingPlayerPosition = parseInt(seedingPlayerNeighborEntry[0]); const { neighbors } = await getRatingsNeighbors(region, id, size, true); @@ -384,12 +384,18 @@ export default function Page() { const loadingDirection = Math.sign(targetPosition - closestPosition); let seedingPlayer = closestPlayerId as number; let targetPositionAcquired = false; - let neighbors: RatingsPlayer[]; + let neighbors: RatingsPlayer[] | null; setLoadingProgress([0, closestPositionDistance]); while (!targetPositionAcquired) { neighbors = await cacheNeighbors(seedingPlayer, SEEDING_SIZE); + + if (neighbors === null) { + targetPositionAcquired = true; + break; + } + seedingPlayer = neighbors.at(loadingDirection === -1 ? 0 : -1)!.spa_id; if (loadingDirection === 1) { @@ -454,7 +460,8 @@ export default function Page() { } const loadingDirection = Math.sign(closestScore - targetScore); - let targetPlayer: RatingsPlayer; + let targetPlayerId: number; + let targetPlayerPosition: number; let scoreAcquired = false; let seedingId = closestId as number; let seedingScore = scores[seedingId]; @@ -462,6 +469,25 @@ export default function Page() { setLoadingProgress([0, closestScoreDistance]); while (!scoreAcquired) { const neighbors = await cacheNeighbors(seedingId, SEEDING_SIZE); + + if (neighbors === null) { + const closesEntry = Object.entries(scores) + .sort(([, a], [, b]) => b - a) + .findLast(([, score]) => score >= targetScore); + + if (closesEntry === undefined) return; + + targetPlayerId = parseInt(closesEntry[0]); + targetPlayerPosition = parseInt( + Object.entries(leaderboard[region][season]).find( + ([, id]) => id === targetPlayerId, + )![0], + ); + scoreAcquired = true; + + break; + } + const seedingNeighbor = neighbors.at(loadingDirection === 1 ? -1 : 0)!; seedingId = seedingNeighbor.spa_id; seedingScore = scores[seedingId]; @@ -479,19 +505,25 @@ export default function Page() { if (scoreAcquired) { if (loadingDirection === 1) { - targetPlayer = neighbors.find( + const targetPlayer = neighbors.find( (player) => player.score <= targetScore, )!; + + targetPlayerId = targetPlayer.spa_id; + targetPlayerPosition = targetPlayer.number - 1; } else { - targetPlayer = neighbors.findLast( + const targetPlayer = neighbors.findLast( (player) => player.score >= targetScore, )!; + + targetPlayerId = targetPlayer.spa_id; + targetPlayerPosition = targetPlayer.number - 1; } } } - setPage(Math.floor(targetPlayer!.number / ROWS_PER_PAGE)); - setHighlightedPlayer({ type: 'id', id: targetPlayer!.spa_id }); + setPage(Math.floor(targetPlayerPosition! / ROWS_PER_PAGE)); + setHighlightedPlayer({ type: 'id', id: targetPlayerId! }); setLoadingProgress(null); } else { let playerIndex = -1; From 95f4dbf6a0ac9ef48c9bc9c39a95f07532866e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 15 Nov 2023 19:23:18 -0600 Subject: [PATCH 86/88] fix: missing season --- src/app/tools/ratings/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 918811615..3d3661c09 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -239,7 +239,7 @@ export default function Page() { (page + 1) * ROWS_PER_PAGE + ROWS_PER_PAGE, ), ) - .map((index) => leaderboard[region][season][index]) + .map((index) => leaderboard[region][season]?.[index]) .filter(Boolean) .filter((id) => !(id in names[region])); From da56c563fc2a35a7775ade6fc5f832c1ba775b25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 15 Nov 2023 19:30:03 -0600 Subject: [PATCH 87/88] feat: more advanced PSAs --- src/core/discord/psa.ts | 1 + .../interactionCreate/handlers/chatInputCommand.ts | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/core/discord/psa.ts b/src/core/discord/psa.ts index 7fd6fff7c..5f6d78f7c 100644 --- a/src/core/discord/psa.ts +++ b/src/core/discord/psa.ts @@ -4,6 +4,7 @@ export type PSAData = { title: string; url: string; }[]; + commands?: string[]; } & ( | { type: 'image'; diff --git a/src/events/interactionCreate/handlers/chatInputCommand.ts b/src/events/interactionCreate/handlers/chatInputCommand.ts index 471d08248..adb323b07 100644 --- a/src/events/interactionCreate/handlers/chatInputCommand.ts +++ b/src/events/interactionCreate/handlers/chatInputCommand.ts @@ -43,7 +43,12 @@ export default async function handleChatInputCommand( const reply = await normalizeInteractionReturnable(returnable!); - if (psa.data && !psa.data.secret) { + if ( + psa.data && + !psa.data.secret && + (psa.data.commands === undefined || + psa.data.commands.includes(interaction.commandName)) + ) { if (psa.data.type === 'embed') { if (!reply.embeds) reply.embeds = []; reply.embeds.push(embedWarning(psa.data.title, psa.data.description)); @@ -66,7 +71,12 @@ export default async function handleChatInputCommand( await interaction.editReply(reply); - if (psa.data && psa.data.secret) { + if ( + psa.data && + psa.data.secret && + (psa.data.commands === undefined || + psa.data.commands.includes(interaction.commandName)) + ) { const followUp: InteractionReplyOptions = { ephemeral: true, }; From bc4da17f107f4c8ae7e680bd1cd52d930e4c5fb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 15 Nov 2023 19:53:34 -0600 Subject: [PATCH 88/88] feat: changelogs --- .vscode/settings.json | 1 + docs/changelogs/1.15.0.md | 72 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 docs/changelogs/1.15.0.md diff --git a/.vscode/settings.json b/.vscode/settings.json index 5aa864800..b89dc3f62 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "blitzstars", "cleandist", "cockroackdb", + "corsproxy", "customperiod", "deburr", "diacriticless", diff --git a/docs/changelogs/1.15.0.md b/docs/changelogs/1.15.0.md new file mode 100644 index 000000000..9abb80c32 --- /dev/null +++ b/docs/changelogs/1.15.0.md @@ -0,0 +1,72 @@ +# Blitzkrieg 1.15.0 + +Ratings will never be the same. + +## Features + +![Blitzkrieg Ratings. Elevate your game. Keep it simple.](https://i.imgur.com/7PZVAGQ.png) + +- Blitzkrieg ratings leaderboard + - Available at https://blitz-krieg.vercel.app/tools/ratings + - **Highly experimental; [please report bugs on Discord server](https://discord.gg/nDt7AjGJQH)** + - Current season leaderboards are live meaning that there may be scores and positions that are out of order + - Options + - Regions can be swapped between NA, EU, and Asia + - The current season and all archived season (from 49 and onwards) are available + - Jump: used to jump to certain players + - Score: finds the first player with at least your desired score + - Position: finds the player at that position + - League: finds the top-most player in that league + - Player: finds the player + - **Jumping to scores and positions for the current season is a compounding function (hence the loading screen) and is limited to the speed of Wargaming's APIs which is pretty darn slow** + - Page controls + - Flip between pages left and right + - 33 players are loaded at once + - Left control is disabled on the first page and vice versa + - Page turning controls are also disabled if the next/previous page hasn't loaded yet + - Page numbers can be manually typed in the text field too + - This is available both at the top and the bottom of the screen + - Players + - Shows the position of the player + - Shows the player nickname and clan name + - "Deleted player ID" us shown when loading a deleted player + - A skeleton is shown when the player and clan is loading + - Shows the player percentile + - Shows reward for the position + - This is blank when there is no reward + - This column isn't available for archived seasons + - Shows the score + - A skeleton is shown when loading + - Click on a row to highlight it + - Using a jump button also highlights a row +- `/credits`: thanks who helped made this project + +## Changes + +- Error messages that user face are now even more simplified +- Increased username and clan autocomplete size limits to `25` +- Added capability for per-page accent theming + - Example: the ratings page is red while the session tracker is blue +- Simplified welcome message for SKLLD clan server + +## Technical Changes + +- Isolated tankopedia info +- Implemented `corsproxy.io` for non-timeout tolerant endpoints +- Latest archived season number is now data driven +- Ratings archives are now public + - This eliminates the need of `octokit` +- Ratings leaderboard archiver is now error tolerant + - Fetch attempts are made every `1000ms` +- Blitz errors are now managed better freeing up the queue faster +- Isolated first archived season number +- Added more advanced PSAs + - These can now feature images or embeds + - These can now modify message contents + - These can also now be secret + +## Experimental Changes + +- Added tankopedia extractor + - Retrieves tankopedia data from the game files + - This is not utilized as of right now