mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: renamed /ratings to /ratings-leaderboard
This commit is contained in:
1 parent
91d48eeadd
commit
f7c6a065c1
35 files changed
+646
-680
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
name: CD Ratings Asia
|
name: CD Rating Asia
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
@@ -31,4 +31,4 @@ jobs:
|
|||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
- name: Generate leaderboard
|
- name: Generate leaderboard
|
||||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingsleaderboard --region=asia --target=midnight --production
|
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingleaderboard --region=asia --target=midnight --production
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: CD Ratings Europe
|
name: CD Rating Europe
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
@@ -31,4 +31,4 @@ jobs:
|
|||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
- name: Generate leaderboard
|
- name: Generate leaderboard
|
||||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingsleaderboard --region=eu --target=midnight --production
|
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingleaderboard --region=eu --target=midnight --production
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: CD Ratings North America
|
name: CD Rating North America
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
@@ -31,13 +31,13 @@ jobs:
|
|||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
- name: Generate NA leaderboard
|
- name: Generate NA leaderboard
|
||||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingsleaderboard --region=na --target=latest --production
|
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingleaderboard --region=na --target=latest --production
|
||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
- name: Generate EU leaderboard
|
- name: Generate EU leaderboard
|
||||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingsleaderboard --region=eu --target=latest --production
|
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingleaderboard --region=eu --target=latest --production
|
||||||
if: always()
|
if: always()
|
||||||
|
|
||||||
- name: Generate Asia leaderboard
|
- name: Generate Asia leaderboard
|
||||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingsleaderboard --region=asia --target=latest --production
|
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingleaderboard --region=asia --target=latest --production
|
||||||
if: always()
|
if: always()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
name: CD Ratings North America
|
name: CD Rating North America
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
@@ -31,4 +31,4 @@ jobs:
|
|||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
- name: Generate leaderboard
|
- name: Generate leaderboard
|
||||||
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingsleaderboard --region=na --target=midnight --production
|
run: GH_TOKEN=${{ secrets.GH_TOKEN }} DISCORD_TOKEN=${{ secrets.DISCORD_TOKEN }} DISCORD_BLITZ_DB=${{ secrets.DISCORD_BLITZ_DB }} yarn publish:ratingleaderboard --region=na --target=midnight --production
|
||||||
Vendored
+1
-2
@@ -100,8 +100,7 @@
|
|||||||
"POSITIVEZ",
|
"POSITIVEZ",
|
||||||
"Promisable",
|
"Promisable",
|
||||||
"Pyogenics",
|
"Pyogenics",
|
||||||
"ratingsleaderboard",
|
"ratingleaderboard",
|
||||||
"ratingsplot",
|
|
||||||
"raycaster",
|
"raycaster",
|
||||||
"researchable",
|
"researchable",
|
||||||
"Resvg",
|
"Resvg",
|
||||||
|
|||||||
+3
-3
@@ -1,9 +1,9 @@
|
|||||||
# Blitzkrieg Ratings Leaderboard
|
# Blitzkrieg Rating Leaderboard
|
||||||
|
|
||||||
A straightforward way of storing the entire World of Tanks Blitz ratings leaderboard to disk.
|
A straightforward way of storing the entire World of Tanks Blitz rating leaderboard to disk.
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
primary BlitzkriegRatingsLeaderboard {
|
primary BlitzkriegRatingLeaderboard {
|
||||||
utf8"BKRL" magic;
|
utf8"BKRL" magic;
|
||||||
Header header;
|
Header header;
|
||||||
primary Body<header> body;
|
primary Body<header> body;
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ Blitzkrieg is available as a Discord bot and can be [installed very easily](/gui
|
|||||||
Provides a bunch of very helpful tools; https://blitz-krieg.vercel.app/
|
Provides a bunch of very helpful tools; https://blitz-krieg.vercel.app/
|
||||||
|
|
||||||
- Session tracker
|
- Session tracker
|
||||||
- Ratings leaderboard
|
- Rating leaderboard
|
||||||
- Way more to come
|
- Way more to come
|
||||||
+2
-3
@@ -8,17 +8,16 @@
|
|||||||
"build:bot": "vite-node --script scripts/build",
|
"build:bot": "vite-node --script scripts/build",
|
||||||
"build:website": "next build",
|
"build:website": "next build",
|
||||||
"build:docs": "vitepress build docs",
|
"build:docs": "vitepress build docs",
|
||||||
"build:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard",
|
"build:ratingleaderboard": "vite-node --script scripts/buildRatingLeaderboard",
|
||||||
"build:assets": "vite-node --max_old_space_size=16384 --script scripts/buildAssets/index",
|
"build:assets": "vite-node --max_old_space_size=16384 --script scripts/buildAssets/index",
|
||||||
"build:test": "vite-node --script scripts/buildTest",
|
"build:test": "vite-node --script scripts/buildTest",
|
||||||
"build:ratingsplot": "vite-node --script scripts/buildRatingsPlot",
|
|
||||||
"dev:bot": "yarn build:bot --dev && yarn start:bot",
|
"dev:bot": "yarn build:bot --dev && yarn start:bot",
|
||||||
"dev:docs": "vitepress dev docs",
|
"dev:docs": "vitepress dev docs",
|
||||||
"dev:website": "next dev",
|
"dev:website": "next dev",
|
||||||
"start:bot": "node --enable-source-maps dist/bot/index.cjs",
|
"start:bot": "node --enable-source-maps dist/bot/index.cjs",
|
||||||
"start:website": "next start",
|
"start:website": "next start",
|
||||||
"host:docs": "vitepress preview docs",
|
"host:docs": "vitepress preview docs",
|
||||||
"publish:ratingsleaderboard": "vite-node --script scripts/buildRatingsLeaderboard --publish",
|
"publish:ratingleaderboard": "vite-node --script scripts/buildRatingLeaderboard --publish",
|
||||||
"setup:patches": "vite-node --script scripts/applyPatches",
|
"setup:patches": "vite-node --script scripts/applyPatches",
|
||||||
"lint": "npx tsc",
|
"lint": "npx tsc",
|
||||||
"lint:website": "next lint",
|
"lint:website": "next lint",
|
||||||
|
|||||||
File renamed without changes.
File renamed without changes.
@@ -1,10 +1,10 @@
|
|||||||
import { chunk } from 'lodash';
|
import { chunk } from 'lodash';
|
||||||
import { argv } from 'process';
|
import { argv } from 'process';
|
||||||
import {
|
import {
|
||||||
RatingsInfo,
|
RatingInfo,
|
||||||
RatingsNeighbors,
|
RatingNeighbors,
|
||||||
RatingsPlayer,
|
RatingPlayer,
|
||||||
} from '../src/commands/ratings';
|
} from '../src/commands/ratingLeaderboard';
|
||||||
import { RegionSubdomain } from '../src/constants/regions';
|
import { RegionSubdomain } from '../src/constants/regions';
|
||||||
import { getAccountInfo } from '../src/core/blitz/getAccountInfo';
|
import { getAccountInfo } from '../src/core/blitz/getAccountInfo';
|
||||||
import regionSubdomainToRegion from '../src/core/blitz/regionSubdomainToRegion';
|
import regionSubdomainToRegion from '../src/core/blitz/regionSubdomainToRegion';
|
||||||
@@ -40,20 +40,20 @@ if (!regionSubdomain) throw new Error('Region parameter not specified');
|
|||||||
const region = regionSubdomainToRegion(regionSubdomain);
|
const region = regionSubdomainToRegion(regionSubdomain);
|
||||||
|
|
||||||
const NEIGHBORS = 2 ** 8;
|
const NEIGHBORS = 2 ** 8;
|
||||||
const PLAYERS = await patientFetchJSON<RatingsInfo>(
|
const PLAYERS = await patientFetchJSON<RatingInfo>(
|
||||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/season/`,
|
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/season/`,
|
||||||
).then((data) => (data.detail === undefined ? data.count : undefined));
|
).then((data) => (data.detail === undefined ? data.count : undefined));
|
||||||
|
|
||||||
if (PLAYERS === undefined) {
|
if (PLAYERS === undefined) {
|
||||||
throw new Error('No current ratings season running');
|
throw new Error('No current rating season running');
|
||||||
}
|
}
|
||||||
|
|
||||||
const players: Record<number, RatingsPlayer> = {};
|
const players: Record<number, RatingPlayer> = {};
|
||||||
const leaderboard: BkrlMinimalEntry[] = [];
|
const leaderboard: BkrlMinimalEntry[] = [];
|
||||||
let registered = 0;
|
let registered = 0;
|
||||||
|
|
||||||
async function branchFromPlayer(id: number) {
|
async function branchFromPlayer(id: number) {
|
||||||
const { neighbors } = await patientFetchJSON<RatingsNeighbors>(
|
const { neighbors } = await patientFetchJSON<RatingNeighbors>(
|
||||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}`,
|
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@ async function branchFromPlayer(id: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function branchFromLeague(id: number) {
|
async function branchFromLeague(id: number) {
|
||||||
const { result } = await patientFetchJSON<{ result: RatingsPlayer[] }>(
|
const { result } = await patientFetchJSON<{ result: RatingPlayer[] }>(
|
||||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`,
|
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -164,7 +164,7 @@ for (const leaderboardChunk of chunk(leaderboard, 100)) {
|
|||||||
chunkIndex++;
|
chunkIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const info = await patientFetchJSON<RatingsInfo & { detail: undefined }>(
|
const info = await patientFetchJSON<RatingInfo & { detail: undefined }>(
|
||||||
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/season/`,
|
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/season/`,
|
||||||
);
|
);
|
||||||
const normalizedServer = regionSubdomain === 'na' ? 'com' : regionSubdomain;
|
const normalizedServer = regionSubdomain === 'na' ? 'com' : regionSubdomain;
|
||||||
@@ -180,19 +180,19 @@ leaderboardWriteStream.bkrl({
|
|||||||
|
|
||||||
const changes: FileChange[] = [
|
const changes: FileChange[] = [
|
||||||
{
|
{
|
||||||
path: `regions/${normalizedServer}/ratings/${info.current_season}/info.cdon.lz4`,
|
path: `regions/${normalizedServer}/rating/${info.current_season}/info.cdon.lz4`,
|
||||||
content: superCompress(infoContent),
|
content: superCompress(infoContent),
|
||||||
encoding: 'base64',
|
encoding: 'base64',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `regions/${normalizedServer}/ratings/${info.current_season}/${target}.bkrl`,
|
path: `regions/${normalizedServer}/rating/${info.current_season}/${target}.bkrl`,
|
||||||
content: Buffer.from(leaderboardWriteStream.uint8Array).toString('base64'),
|
content: Buffer.from(leaderboardWriteStream.uint8Array).toString('base64'),
|
||||||
encoding: 'base64',
|
encoding: 'base64',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
commitAssets(
|
commitAssets(
|
||||||
`ratings leaderboard ${regionSubdomain} ${target}`,
|
`rating leaderboard ${regionSubdomain} ${target}`,
|
||||||
changes,
|
changes,
|
||||||
production,
|
production,
|
||||||
);
|
);
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
// what the hell is this script???
|
|
||||||
|
|
||||||
import { writeFile } from 'fs/promises';
|
|
||||||
import { Region } from '../src/constants/regions';
|
|
||||||
import { getRatingsNeighbors } from '../src/core/blitz/getRatingsNeighbors';
|
|
||||||
|
|
||||||
const REGION: Region = 'com';
|
|
||||||
const PLAYER = 1041988373;
|
|
||||||
const ITERATIONS = 2 ** 4;
|
|
||||||
const MAX_NEIGHBORS = 2 ** 7;
|
|
||||||
const data: [number, number][] = [];
|
|
||||||
let finished = 0;
|
|
||||||
|
|
||||||
for (let index = 0; index < ITERATIONS; index++) {
|
|
||||||
const neighbors = Math.round((Math.random() * MAX_NEIGHBORS) / 2) * 2;
|
|
||||||
const start = Date.now();
|
|
||||||
|
|
||||||
console.log(`🟡 Iteration ${index} with ${neighbors} neighbors`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await getRatingsNeighbors(REGION, PLAYER, neighbors);
|
|
||||||
|
|
||||||
finished++;
|
|
||||||
console.log(
|
|
||||||
`🟢 (${Math.round(
|
|
||||||
100 * (finished / ITERATIONS),
|
|
||||||
)}%) Iteration ${index} with ${neighbors} neighbors`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const end = Date.now();
|
|
||||||
const time = end - start;
|
|
||||||
|
|
||||||
data.push([neighbors, time]);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(
|
|
||||||
`🔴 Iteration ${index} with ${neighbors} neighbors\n\n${error}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('🟢 Done, sorting');
|
|
||||||
data.sort(([neighborsA], [neighborsB]) => neighborsA - neighborsB);
|
|
||||||
|
|
||||||
console.log('🟢 Writing');
|
|
||||||
writeFile(
|
|
||||||
'dist/ratingsPlot.csv',
|
|
||||||
data.map(([neighbors, time]) => `${neighbors},${time}`).join('\n'),
|
|
||||||
);
|
|
||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { Region } from '../../../../../../constants/regions';
|
import { Region } from '../../../../../../constants/regions';
|
||||||
import getRatingsInfo from '../../../../../../core/blitz/getRatingsInfo';
|
import getRatingInfo from '../../../../../../core/blitz/getRatingInfo';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: Request,
|
request: Request,
|
||||||
{ params }: { params: { region: Region } },
|
{ params }: { params: { region: Region } },
|
||||||
) {
|
) {
|
||||||
return NextResponse.json(await getRatingsInfo(params.region));
|
return NextResponse.json(await getRatingInfo(params.region));
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { Region } from '../../../../../../../constants/regions';
|
import { Region } from '../../../../../../../constants/regions';
|
||||||
import { getRatingsLeague } from '../../../../../../../core/blitz/getRatingsLeague';
|
import { getRatingLeague } from '../../../../../../../core/blitz/getRatingLeague';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: Request,
|
request: Request,
|
||||||
{ params }: { params: { region: Region; league: number } },
|
{ params }: { params: { region: Region; league: number } },
|
||||||
) {
|
) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
await getRatingsLeague(params.region, params.league, true),
|
await getRatingLeague(params.region, params.league, true),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { Region } from '../../../../../../../constants/regions';
|
import { Region } from '../../../../../../../constants/regions';
|
||||||
import { searchCurrentRatingsPlayers } from '../../../../../../../core/blitz/searchCurrentRatingsPlayers';
|
import { searchCurrentRatingPlayers } from '../../../../../../../core/blitz/searchCurrentRatingPlayers';
|
||||||
|
|
||||||
export async function GET(
|
export async function GET(
|
||||||
request: Request,
|
request: Request,
|
||||||
{ params }: { params: { region: Region; username: string } },
|
{ params }: { params: { region: Region; username: string } },
|
||||||
) {
|
) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
await searchCurrentRatingsPlayers(params.region, params.username),
|
await searchCurrentRatingPlayers(params.region, params.username),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -15,11 +15,11 @@ import { produce } from 'immer';
|
|||||||
import { debounce, range } from 'lodash';
|
import { debounce, range } from 'lodash';
|
||||||
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
import { ChangeEvent, useEffect, useRef, useState } from 'react';
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { RatingsInfo, RatingsPlayer } from '../../../commands/ratings';
|
import { RatingInfo, RatingPlayer } from '../../../commands/ratingLeaderboard';
|
||||||
import PageWrapper from '../../../components/PageWrapper';
|
import PageWrapper from '../../../components/PageWrapper';
|
||||||
import { Skeleton } from '../../../components/Skeleton';
|
import { Skeleton } from '../../../components/Skeleton';
|
||||||
import { LEAGUES } from '../../../constants/leagues';
|
import { LEAGUES } from '../../../constants/leagues';
|
||||||
import { FIRST_ARCHIVED_RATINGS_SEASON } from '../../../constants/ratings';
|
import { FIRST_ARCHIVED_RATING_SEASON } from '../../../constants/rating';
|
||||||
import {
|
import {
|
||||||
REGIONS,
|
REGIONS,
|
||||||
Region,
|
Region,
|
||||||
@@ -28,17 +28,17 @@ import {
|
|||||||
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
|
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
|
||||||
import fetchBlitz from '../../../core/blitz/fetchBlitz';
|
import fetchBlitz from '../../../core/blitz/fetchBlitz';
|
||||||
import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo';
|
import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo';
|
||||||
import getRatingsInfo from '../../../core/blitz/getRatingsInfo';
|
import getRatingInfo from '../../../core/blitz/getRatingInfo';
|
||||||
import { getRatingsLeague } from '../../../core/blitz/getRatingsLeague';
|
import { getRatingLeague } from '../../../core/blitz/getRatingLeague';
|
||||||
import { getRatingsNeighbors } from '../../../core/blitz/getRatingsNeighbors';
|
import { getRatingNeighbors } from '../../../core/blitz/getRatingNeighbors';
|
||||||
import { searchCurrentRatingsPlayers } from '../../../core/blitz/searchCurrentRatingsPlayers';
|
import { searchCurrentRatingPlayers } from '../../../core/blitz/searchCurrentRatingPlayers';
|
||||||
import {
|
import {
|
||||||
AccountList,
|
AccountList,
|
||||||
AccountListItem,
|
AccountListItem,
|
||||||
} from '../../../core/blitz/searchPlayersAcrossRegions';
|
} from '../../../core/blitz/searchPlayersAcrossRegions';
|
||||||
import { getArchivedLatestSeasonNumber } from '../../../core/blitzkrieg/getArchivedLatestSeasonNumber';
|
import { getArchivedLatestSeasonNumber } from '../../../core/blitzkrieg/getArchivedLatestSeasonNumber';
|
||||||
import getArchivedRatingsInfo from '../../../core/blitzkrieg/getArchivedRatingsInfo';
|
import getArchivedRatingInfo from '../../../core/blitzkrieg/getArchivedRatingInfo';
|
||||||
import { getArchivedRatingsLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingsLeaderboard';
|
import { getArchivedRatingLeaderboard } from '../../../core/blitzkrieg/getArchivedRatingLeaderboard';
|
||||||
import { theme } from '../../../stitches.config';
|
import { theme } from '../../../stitches.config';
|
||||||
import { PageTurner } from './components/PageTurner';
|
import { PageTurner } from './components/PageTurner';
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ interface NameCacheEntry {
|
|||||||
}
|
}
|
||||||
type NameCache = Record<Region, Record<number, NameCacheEntry | null>>;
|
type NameCache = Record<Region, Record<number, NameCacheEntry | null>>;
|
||||||
type LeaderboardCache = Record<Region, Record<number, Record<number, number>>>;
|
type LeaderboardCache = Record<Region, Record<number, Record<number, number>>>;
|
||||||
type InfoCache = Record<Region, Record<number, RatingsInfo | undefined>>;
|
type InfoCache = Record<Region, Record<number, RatingInfo | undefined>>;
|
||||||
type ScoreCache = Record<Region, Record<number, Record<number, number>>>;
|
type ScoreCache = Record<Region, Record<number, Record<number, number>>>;
|
||||||
|
|
||||||
const useNameCache = create<NameCache>(() => ({
|
const useNameCache = create<NameCache>(() => ({
|
||||||
@@ -93,7 +93,7 @@ export default function Page() {
|
|||||||
{ type: 'id'; id: number } | { type: 'position'; position: number } | null
|
{ type: 'id'; id: number } | { type: 'position'; position: number } | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
const ratingsInfo = useInfoCache()[region][season];
|
const ratingInfo = useInfoCache()[region][season];
|
||||||
const [latestArchivedSeasonNumber, setLatestArchivedSeasonNumber] = useState<
|
const [latestArchivedSeasonNumber, setLatestArchivedSeasonNumber] = useState<
|
||||||
number | null
|
number | null
|
||||||
>(null);
|
>(null);
|
||||||
@@ -111,7 +111,7 @@ export default function Page() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const pages = Math.ceil(
|
const pages = Math.ceil(
|
||||||
(ratingsInfo && ratingsInfo.detail === undefined ? ratingsInfo?.count : 1) /
|
(ratingInfo && ratingInfo.detail === undefined ? ratingInfo?.count : 1) /
|
||||||
ROWS_PER_PAGE,
|
ROWS_PER_PAGE,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -122,8 +122,8 @@ export default function Page() {
|
|||||||
(async () => {
|
(async () => {
|
||||||
const newInfo =
|
const newInfo =
|
||||||
season === 0
|
season === 0
|
||||||
? await getRatingsInfo(region)
|
? await getRatingInfo(region)
|
||||||
: await getArchivedRatingsInfo(region, season);
|
: await getArchivedRatingInfo(region, season);
|
||||||
useInfoCache.setState(
|
useInfoCache.setState(
|
||||||
produce((draft: InfoCache) => {
|
produce((draft: InfoCache) => {
|
||||||
draft[region][season] = newInfo;
|
draft[region][season] = newInfo;
|
||||||
@@ -140,7 +140,7 @@ export default function Page() {
|
|||||||
const results = (
|
const results = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
range(0, 5).map(async (league: number) => {
|
range(0, 5).map(async (league: number) => {
|
||||||
const data = await getRatingsLeague(region, league);
|
const data = await getRatingLeague(region, league);
|
||||||
leaguePositionCache.current[region][league] = data[0].number - 1;
|
leaguePositionCache.current[region][league] = data[0].number - 1;
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
@@ -178,12 +178,12 @@ export default function Page() {
|
|||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
} else {
|
} else {
|
||||||
getArchivedRatingsLeaderboard(region, season).then((results) => {
|
getArchivedRatingLeaderboard(region, season).then((results) => {
|
||||||
useLeaderboardCache.setState(
|
useLeaderboardCache.setState(
|
||||||
produce((draft: LeaderboardCache) => {
|
produce((draft: LeaderboardCache) => {
|
||||||
if (!(season in draft[region])) draft[region][season] = {};
|
if (!(season in draft[region])) draft[region][season] = {};
|
||||||
|
|
||||||
results.forEach((result, index) => {
|
results.entries.forEach((result, index) => {
|
||||||
draft[region][season][index] = result.id;
|
draft[region][season][index] = result.id;
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
@@ -192,7 +192,7 @@ export default function Page() {
|
|||||||
produce((draft: ScoreCache) => {
|
produce((draft: ScoreCache) => {
|
||||||
if (!(season in draft[region])) draft[region][season] = {};
|
if (!(season in draft[region])) draft[region][season] = {};
|
||||||
|
|
||||||
results.forEach((result) => {
|
results.entries.forEach((result) => {
|
||||||
draft[region][season][result.id] = result.score;
|
draft[region][season][result.id] = result.score;
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
@@ -213,7 +213,7 @@ export default function Page() {
|
|||||||
}, [leaderboard[region]?.[season] && 0 in leaderboard[region][season]]);
|
}, [leaderboard[region]?.[season] && 0 in leaderboard[region][season]]);
|
||||||
// neighboring page caching for all seasons
|
// neighboring page caching for all seasons
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ratingsInfo || ratingsInfo?.detail) return;
|
if (!ratingInfo || ratingInfo?.detail) return;
|
||||||
|
|
||||||
if (season === 0) {
|
if (season === 0) {
|
||||||
const middleIndex = page * ROWS_PER_PAGE + ROWS_PER_PAGE / 2;
|
const middleIndex = page * ROWS_PER_PAGE + ROWS_PER_PAGE / 2;
|
||||||
@@ -236,7 +236,7 @@ export default function Page() {
|
|||||||
const ids = range(
|
const ids = range(
|
||||||
Math.max(0, (page - 1) * ROWS_PER_PAGE),
|
Math.max(0, (page - 1) * ROWS_PER_PAGE),
|
||||||
Math.min(
|
Math.min(
|
||||||
ratingsInfo.count - 1,
|
ratingInfo.count - 1,
|
||||||
(page + 1) * ROWS_PER_PAGE + ROWS_PER_PAGE,
|
(page + 1) * ROWS_PER_PAGE + ROWS_PER_PAGE,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -265,7 +265,7 @@ export default function Page() {
|
|||||||
page,
|
page,
|
||||||
region,
|
region,
|
||||||
season,
|
season,
|
||||||
ratingsInfo?.detail,
|
ratingInfo?.detail,
|
||||||
leaderboard[region][season] !== undefined &&
|
leaderboard[region][season] !== undefined &&
|
||||||
0 in leaderboard[region][season],
|
0 in leaderboard[region][season],
|
||||||
]);
|
]);
|
||||||
@@ -279,7 +279,7 @@ export default function Page() {
|
|||||||
async function cacheNeighbors(
|
async function cacheNeighbors(
|
||||||
id: number,
|
id: number,
|
||||||
size: number,
|
size: number,
|
||||||
): Promise<RatingsPlayer[] | null> {
|
): Promise<RatingPlayer[] | null> {
|
||||||
const radius = Math.round(size / 2);
|
const radius = Math.round(size / 2);
|
||||||
const targetPosition = Object.entries(leaderboard[region][season]).find(
|
const targetPosition = Object.entries(leaderboard[region][season]).find(
|
||||||
([, player]) => id === player,
|
([, player]) => id === player,
|
||||||
@@ -303,7 +303,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { neighbors } = await getRatingsNeighbors(region, id, size, true);
|
const { neighbors } = await getRatingNeighbors(region, id, size, true);
|
||||||
|
|
||||||
useLeaderboardCache.setState(
|
useLeaderboardCache.setState(
|
||||||
produce((draft: LeaderboardCache) => {
|
produce((draft: LeaderboardCache) => {
|
||||||
@@ -341,11 +341,11 @@ export default function Page() {
|
|||||||
return neighbors;
|
return neighbors;
|
||||||
}
|
}
|
||||||
async function jumpToPosition(position: number) {
|
async function jumpToPosition(position: number) {
|
||||||
if (!ratingsInfo || ratingsInfo?.detail) return;
|
if (!ratingInfo || ratingInfo?.detail) return;
|
||||||
|
|
||||||
const targetPosition = Math.min(
|
const targetPosition = Math.min(
|
||||||
ratingsInfo.count - 1,
|
ratingInfo.count - 1,
|
||||||
Math.max(0, Math.min(position, ratingsInfo.count - 1)),
|
Math.max(0, Math.min(position, ratingInfo.count - 1)),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (season === 0) {
|
if (season === 0) {
|
||||||
@@ -378,7 +378,7 @@ export default function Page() {
|
|||||||
const loadingDirection = Math.sign(targetPosition - closestPosition);
|
const loadingDirection = Math.sign(targetPosition - closestPosition);
|
||||||
let seedingPlayer = closestPlayerId as number;
|
let seedingPlayer = closestPlayerId as number;
|
||||||
let targetPositionAcquired = false;
|
let targetPositionAcquired = false;
|
||||||
let neighbors: RatingsPlayer[] | null;
|
let neighbors: RatingPlayer[] | null;
|
||||||
|
|
||||||
setLoadingProgress([0, closestPositionDistance]);
|
setLoadingProgress([0, closestPositionDistance]);
|
||||||
|
|
||||||
@@ -423,7 +423,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function jumpToScore() {
|
async function jumpToScore() {
|
||||||
if (!ratingsInfo || ratingsInfo?.detail) return;
|
if (!ratingInfo || ratingInfo?.detail) return;
|
||||||
|
|
||||||
const targetScore = scoreInput.current!.valueAsNumber;
|
const targetScore = scoreInput.current!.valueAsNumber;
|
||||||
|
|
||||||
@@ -522,7 +522,7 @@ export default function Page() {
|
|||||||
} else {
|
} else {
|
||||||
let playerIndex = -1;
|
let playerIndex = -1;
|
||||||
|
|
||||||
for (let index = ratingsInfo.count; index >= 0; index--) {
|
for (let index = ratingInfo.count; index >= 0; index--) {
|
||||||
if (!(index in leaderboard[region][season])) continue;
|
if (!(index in leaderboard[region][season])) continue;
|
||||||
|
|
||||||
if (scores[leaderboard[region][season][index]] >= targetScore) {
|
if (scores[leaderboard[region][season][index]] >= targetScore) {
|
||||||
@@ -545,7 +545,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const totalPlayers =
|
const totalPlayers =
|
||||||
!ratingsInfo?.detail && ratingsInfo?.count ? ratingsInfo.count : undefined;
|
!ratingInfo?.detail && ratingInfo?.count ? ratingInfo.count : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper color="orange">
|
<PageWrapper color="orange">
|
||||||
@@ -573,7 +573,7 @@ export default function Page() {
|
|||||||
<Select.Trigger style={{ flex: 1 }} />
|
<Select.Trigger style={{ flex: 1 }} />
|
||||||
|
|
||||||
<Select.Content>
|
<Select.Content>
|
||||||
{ratingsInfo?.detail === undefined && (
|
{ratingInfo?.detail === undefined && (
|
||||||
<Select.Item value="0">Current season</Select.Item>
|
<Select.Item value="0">Current season</Select.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -582,7 +582,7 @@ export default function Page() {
|
|||||||
|
|
||||||
{latestArchivedSeasonNumber &&
|
{latestArchivedSeasonNumber &&
|
||||||
range(
|
range(
|
||||||
FIRST_ARCHIVED_RATINGS_SEASON,
|
FIRST_ARCHIVED_RATING_SEASON,
|
||||||
latestArchivedSeasonNumber + 1,
|
latestArchivedSeasonNumber + 1,
|
||||||
)
|
)
|
||||||
.map((season) => (
|
.map((season) => (
|
||||||
@@ -681,8 +681,8 @@ export default function Page() {
|
|||||||
<Select.Trigger />
|
<Select.Trigger />
|
||||||
|
|
||||||
<Select.Content>
|
<Select.Content>
|
||||||
{ratingsInfo?.detail === undefined &&
|
{ratingInfo?.detail === undefined &&
|
||||||
ratingsInfo?.leagues.map(({ index, title }) => (
|
ratingInfo?.leagues.map(({ index, title }) => (
|
||||||
<Select.Item key={index} value={`${index}`}>
|
<Select.Item key={index} value={`${index}`}>
|
||||||
{title}
|
{title}
|
||||||
</Select.Item>
|
</Select.Item>
|
||||||
@@ -715,8 +715,8 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
!ratingsInfo ||
|
!ratingInfo ||
|
||||||
ratingsInfo.detail ||
|
ratingInfo.detail ||
|
||||||
!leaderboard
|
!leaderboard
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
@@ -727,7 +727,7 @@ export default function Page() {
|
|||||||
|
|
||||||
for (
|
for (
|
||||||
let index = 0;
|
let index = 0;
|
||||||
index < ratingsInfo.count;
|
index < ratingInfo.count;
|
||||||
index++
|
index++
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
@@ -779,7 +779,7 @@ export default function Page() {
|
|||||||
setSearchLoading(true);
|
setSearchLoading(true);
|
||||||
|
|
||||||
const accountList =
|
const accountList =
|
||||||
await searchCurrentRatingsPlayers(
|
await searchCurrentRatingPlayers(
|
||||||
region,
|
region,
|
||||||
trimmedSearch,
|
trimmedSearch,
|
||||||
);
|
);
|
||||||
@@ -885,12 +885,12 @@ export default function Page() {
|
|||||||
id: player.account_id,
|
id: player.account_id,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!ratingsInfo || ratingsInfo.detail) return;
|
if (!ratingInfo || ratingInfo.detail) return;
|
||||||
let playerIndex = -1;
|
let playerIndex = -1;
|
||||||
|
|
||||||
for (
|
for (
|
||||||
let index = 0;
|
let index = 0;
|
||||||
index < ratingsInfo.count;
|
index < ratingInfo.count;
|
||||||
index++
|
index++
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
@@ -952,7 +952,7 @@ export default function Page() {
|
|||||||
|
|
||||||
<PageTurner
|
<PageTurner
|
||||||
totalPlayers={
|
totalPlayers={
|
||||||
(ratingsInfo?.detail ? undefined : ratingsInfo?.count) ?? 0
|
(ratingInfo?.detail ? undefined : ratingInfo?.count) ?? 0
|
||||||
}
|
}
|
||||||
leaderboard={leaderboard[region][season] ?? {}}
|
leaderboard={leaderboard[region][season] ?? {}}
|
||||||
rowsPerPage={ROWS_PER_PAGE}
|
rowsPerPage={ROWS_PER_PAGE}
|
||||||
@@ -982,7 +982,7 @@ export default function Page() {
|
|||||||
page * ROWS_PER_PAGE,
|
page * ROWS_PER_PAGE,
|
||||||
Math.min(
|
Math.min(
|
||||||
page * ROWS_PER_PAGE + ROWS_PER_PAGE,
|
page * ROWS_PER_PAGE + ROWS_PER_PAGE,
|
||||||
ratingsInfo?.detail ? 0 : (ratingsInfo?.count ?? 1) - 1,
|
ratingInfo?.detail ? 0 : (ratingInfo?.count ?? 1) - 1,
|
||||||
),
|
),
|
||||||
).map((index) => {
|
).map((index) => {
|
||||||
const id = leaderboard[region][season]?.[index];
|
const id = leaderboard[region][season]?.[index];
|
||||||
@@ -994,8 +994,8 @@ export default function Page() {
|
|||||||
: highlightedPlayer.position === index
|
: highlightedPlayer.position === index
|
||||||
: false;
|
: false;
|
||||||
const reward =
|
const reward =
|
||||||
!ratingsInfo?.detail && ratingsInfo?.rewards
|
!ratingInfo?.detail && ratingInfo?.rewards
|
||||||
? ratingsInfo.rewards.find(
|
? ratingInfo.rewards.find(
|
||||||
(reward) =>
|
(reward) =>
|
||||||
reward.from_position <= position &&
|
reward.from_position <= position &&
|
||||||
reward.to_position >= position,
|
reward.to_position >= position,
|
||||||
@@ -1091,7 +1091,7 @@ export default function Page() {
|
|||||||
|
|
||||||
<PageTurner
|
<PageTurner
|
||||||
totalPlayers={
|
totalPlayers={
|
||||||
(ratingsInfo?.detail ? undefined : ratingsInfo?.count) ?? 0
|
(ratingInfo?.detail ? undefined : ratingInfo?.count) ?? 0
|
||||||
}
|
}
|
||||||
rowsPerPage={ROWS_PER_PAGE}
|
rowsPerPage={ROWS_PER_PAGE}
|
||||||
leaderboard={leaderboard[region][season] ?? {}}
|
leaderboard={leaderboard[region][season] ?? {}}
|
||||||
|
|||||||
@@ -0,0 +1,513 @@
|
|||||||
|
import {
|
||||||
|
APIApplicationCommandOptionChoice,
|
||||||
|
Locale,
|
||||||
|
SlashCommandSubcommandBuilder,
|
||||||
|
} from 'discord.js';
|
||||||
|
import { range } from 'lodash';
|
||||||
|
import markdownEscape from 'markdown-escape';
|
||||||
|
import CommandWrapper from '../components/CommandWrapper';
|
||||||
|
import * as Leaderboard from '../components/Leaderboard';
|
||||||
|
import TitleBar from '../components/TitleBar';
|
||||||
|
import { LEAGUES } from '../constants/leagues';
|
||||||
|
import { FIRST_ARCHIVED_RATING_SEASON } from '../constants/rating';
|
||||||
|
import { Region } from '../constants/regions';
|
||||||
|
import { getAccountInfo } from '../core/blitz/getAccountInfo';
|
||||||
|
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
|
||||||
|
import getRatingInfo from '../core/blitz/getRatingInfo';
|
||||||
|
import { getRatingLeague } from '../core/blitz/getRatingLeague';
|
||||||
|
import { getRatingNeighbors } from '../core/blitz/getRatingNeighbors';
|
||||||
|
import { isOnGoingRatingSeason } from '../core/blitz/isOnGoingRatingSeason';
|
||||||
|
import { getArchivedLatestSeasonNumber } from '../core/blitzkrieg/getArchivedLatestSeasonNumber';
|
||||||
|
import getArchivedRatingInfo from '../core/blitzkrieg/getArchivedRatingInfo';
|
||||||
|
import { getArchivedRatingLeaderboard } from '../core/blitzkrieg/getArchivedRatingLeaderboard';
|
||||||
|
import getArchivedRatingMidnightLeaderboard from '../core/blitzkrieg/getArchivedRatingMidnightLeaderboard';
|
||||||
|
import addRegionChoices from '../core/discord/addRegionChoices';
|
||||||
|
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||||
|
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
||||||
|
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||||
|
import { localizationObject } from '../core/discord/localizationObject';
|
||||||
|
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
||||||
|
import { translator } from '../core/localization/translator';
|
||||||
|
import { BkrlDiscriminatedEntries } from '../core/streams/bkrl';
|
||||||
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
|
import { UserError } from '../hooks/userError';
|
||||||
|
|
||||||
|
export interface RatingPlayer {
|
||||||
|
spa_id: number;
|
||||||
|
mmr: number;
|
||||||
|
season_number: number;
|
||||||
|
calibrationBattlesLeft: number;
|
||||||
|
number: number;
|
||||||
|
percentile: number;
|
||||||
|
skip: boolean;
|
||||||
|
updated_at: string;
|
||||||
|
score: number;
|
||||||
|
nickname: string;
|
||||||
|
clan_tag: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LeagueTop {
|
||||||
|
result: RatingPlayer[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RatingReward = {
|
||||||
|
from_position: number;
|
||||||
|
to_position: number;
|
||||||
|
count: number;
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
type: 'vehicle';
|
||||||
|
|
||||||
|
vehicle: {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
nation: string;
|
||||||
|
subnation: string;
|
||||||
|
use_subnation_flag: boolean;
|
||||||
|
type_slug: string;
|
||||||
|
level: number;
|
||||||
|
roman_level: string;
|
||||||
|
user_string: string;
|
||||||
|
image_url: string;
|
||||||
|
preview_url: string;
|
||||||
|
is_premium: boolean;
|
||||||
|
is_collectible: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'stuff';
|
||||||
|
|
||||||
|
stuff: {
|
||||||
|
name: string;
|
||||||
|
count: number;
|
||||||
|
title: string;
|
||||||
|
image_url: string;
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
sizes: {};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface RatingLeague {
|
||||||
|
title: string;
|
||||||
|
small_icon: string;
|
||||||
|
big_icon: string;
|
||||||
|
background: string;
|
||||||
|
index: number;
|
||||||
|
percentile: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RatingInfo =
|
||||||
|
| {
|
||||||
|
detail: undefined;
|
||||||
|
|
||||||
|
title: string;
|
||||||
|
icon: string | null;
|
||||||
|
start_at: string;
|
||||||
|
finish_at: string;
|
||||||
|
current_season: number;
|
||||||
|
updated_at: string;
|
||||||
|
count: number;
|
||||||
|
|
||||||
|
rewards: RatingReward[];
|
||||||
|
leagues: RatingLeague[];
|
||||||
|
}
|
||||||
|
| { detail: { error: string } };
|
||||||
|
|
||||||
|
export interface RatingNeighbors {
|
||||||
|
neighbors: RatingPlayer[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SimplifiedPlayer {
|
||||||
|
id: number;
|
||||||
|
score: number;
|
||||||
|
position: number;
|
||||||
|
clan: string | undefined;
|
||||||
|
nickname: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_LIMIT = 10;
|
||||||
|
|
||||||
|
export const ratingLeaderboardCommand = new Promise<CommandRegistry>(
|
||||||
|
async (resolve) => {
|
||||||
|
const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber();
|
||||||
|
const onGoingSeason = await isOnGoingRatingSeason();
|
||||||
|
const seasonNumbers = range(
|
||||||
|
FIRST_ARCHIVED_RATING_SEASON,
|
||||||
|
latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
function addOptions(option: SlashCommandSubcommandBuilder) {
|
||||||
|
const { t, translate } = translator(Locale.EnglishUS);
|
||||||
|
|
||||||
|
return option
|
||||||
|
.addIntegerOption((option) =>
|
||||||
|
option
|
||||||
|
.setName(t`bot.commands.rating_leaderboard.options.limit`)
|
||||||
|
.setNameLocalizations(
|
||||||
|
localizationObject(
|
||||||
|
'bot.commands.rating_leaderboard.options.limit',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setDescription(
|
||||||
|
translate(
|
||||||
|
'bot.commands.rating_leaderboard.options.limit.description',
|
||||||
|
[`${DEFAULT_LIMIT}`],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setDescriptionLocalizations(
|
||||||
|
localizationObject(
|
||||||
|
'bot.commands.rating_leaderboard.options.limit.description',
|
||||||
|
[`${DEFAULT_LIMIT}`],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setRequired(false)
|
||||||
|
.setMinValue(5)
|
||||||
|
.setMaxValue(30),
|
||||||
|
)
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName(t`bot.commands.rating_leaderboard.options.season`)
|
||||||
|
.setNameLocalizations(
|
||||||
|
localizationObject(
|
||||||
|
'bot.commands.rating_leaderboard.options.season',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setDescription(
|
||||||
|
translate(
|
||||||
|
'bot.commands.rating_leaderboard.options.season.description',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setDescriptionLocalizations(
|
||||||
|
localizationObject(
|
||||||
|
'bot.commands.rating_leaderboard.options.season.description',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setRequired(false)
|
||||||
|
.addChoices(
|
||||||
|
...seasonNumbers
|
||||||
|
.map((number, index) => {
|
||||||
|
const current =
|
||||||
|
index === seasonNumbers.length - 1 && onGoingSeason;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: current
|
||||||
|
? translate(
|
||||||
|
'bot.commands.rating_leaderboard.options.season.choices.current',
|
||||||
|
[`${number}`],
|
||||||
|
)
|
||||||
|
: `${number}`,
|
||||||
|
value: `${number}`,
|
||||||
|
name_localizations: current
|
||||||
|
? localizationObject(
|
||||||
|
'bot.commands.rating_leaderboard.options.season.choices.current',
|
||||||
|
[`${number}`],
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
} satisfies APIApplicationCommandOptionChoice<string>;
|
||||||
|
})
|
||||||
|
.reverse(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
inProduction: true,
|
||||||
|
inPublic: true,
|
||||||
|
|
||||||
|
command: createLocalizedCommand('rating-leaderboard', [
|
||||||
|
{
|
||||||
|
subcommand: 'neighbors',
|
||||||
|
modify(option: SlashCommandSubcommandBuilder) {
|
||||||
|
option.addStringOption(addUsernameChoices);
|
||||||
|
addOptions(option);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
subcommand: 'league',
|
||||||
|
modify(option: SlashCommandSubcommandBuilder) {
|
||||||
|
option
|
||||||
|
.addStringOption((option) => {
|
||||||
|
const { t, translate } = translator(Locale.EnglishUS);
|
||||||
|
|
||||||
|
return option
|
||||||
|
.setName(t`bot.commands.rating_leaderboard.options.league`)
|
||||||
|
.setNameLocalizations(
|
||||||
|
localizationObject(
|
||||||
|
'bot.commands.rating_leaderboard.options.league',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setDescription(
|
||||||
|
translate(
|
||||||
|
'bot.commands.rating_leaderboard.options.league.description',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setDescriptionLocalizations(
|
||||||
|
localizationObject(
|
||||||
|
'bot.commands.rating_leaderboard.options.league.description',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.addChoices(
|
||||||
|
...LEAGUES.map(
|
||||||
|
({ name }, value) =>
|
||||||
|
({
|
||||||
|
name: translate(`common.leagues.${name}`),
|
||||||
|
value: `${value}`,
|
||||||
|
name_localizations: localizationObject(
|
||||||
|
`common.leagues.${name}`,
|
||||||
|
),
|
||||||
|
}) satisfies APIApplicationCommandOptionChoice<string>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setRequired(true);
|
||||||
|
})
|
||||||
|
.addStringOption(addRegionChoices);
|
||||||
|
addOptions(option);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
|
||||||
|
// BIG TODO: title seems to be in english
|
||||||
|
|
||||||
|
async handler(interaction) {
|
||||||
|
const { t, translate } = translator(interaction.locale);
|
||||||
|
const subcommand = interaction.options.getSubcommand(true) as
|
||||||
|
| 'league'
|
||||||
|
| 'neighbors';
|
||||||
|
const limit = interaction.options.getInteger('limit') ?? DEFAULT_LIMIT;
|
||||||
|
const seasonOption = interaction.options.getString('season');
|
||||||
|
const season = seasonOption
|
||||||
|
? parseInt(seasonOption)
|
||||||
|
: onGoingSeason
|
||||||
|
? latestArchivedSeasonNumber + 1
|
||||||
|
: latestArchivedSeasonNumber;
|
||||||
|
const isCurrentSeason = season === latestArchivedSeasonNumber + 1;
|
||||||
|
let titleName: string;
|
||||||
|
let titleImage: string | undefined;
|
||||||
|
let titleDescription: string;
|
||||||
|
let playersAfter: number;
|
||||||
|
let players: SimplifiedPlayer[] | undefined;
|
||||||
|
let midnightLeaderboard: BkrlDiscriminatedEntries | null;
|
||||||
|
let playerId: number | undefined;
|
||||||
|
let regionRatingInfo: RatingInfo;
|
||||||
|
|
||||||
|
if (subcommand === 'league') {
|
||||||
|
const leagueIndex = parseInt(
|
||||||
|
interaction.options.getString('league')!,
|
||||||
|
);
|
||||||
|
const region = interaction.options.getString('region') as Region;
|
||||||
|
regionRatingInfo = await (isCurrentSeason
|
||||||
|
? getRatingInfo(region)
|
||||||
|
: getArchivedRatingInfo(region, season!));
|
||||||
|
|
||||||
|
if (regionRatingInfo.detail !== undefined)
|
||||||
|
return t`bot.commands.rating_leaderboard.body.no_ongoing_season`;
|
||||||
|
|
||||||
|
const result = isCurrentSeason
|
||||||
|
? await getRatingLeague(region, leagueIndex).then(async (result) =>
|
||||||
|
result.slice(0, limit).map(
|
||||||
|
(player) =>
|
||||||
|
({
|
||||||
|
id: player.spa_id,
|
||||||
|
score: player.score,
|
||||||
|
position: player.number,
|
||||||
|
clan: player.clan_tag,
|
||||||
|
nickname: player.nickname,
|
||||||
|
}) satisfies SimplifiedPlayer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: await getArchivedRatingLeaderboard(region, season!).then(
|
||||||
|
async (players) => {
|
||||||
|
const firstPlayerIndex =
|
||||||
|
leagueIndex === 0
|
||||||
|
? 0
|
||||||
|
: players.entries.findIndex(
|
||||||
|
(player) =>
|
||||||
|
player.score < LEAGUES[leagueIndex - 1].minScore,
|
||||||
|
);
|
||||||
|
const lastPlayerIndex = firstPlayerIndex + limit - 1;
|
||||||
|
const trimmed = players.entries.slice(
|
||||||
|
firstPlayerIndex,
|
||||||
|
lastPlayerIndex + 1,
|
||||||
|
);
|
||||||
|
const clanData = await getClanAccountInfo(
|
||||||
|
region,
|
||||||
|
trimmed.map(({ id }) => id),
|
||||||
|
['clan'],
|
||||||
|
);
|
||||||
|
|
||||||
|
return trimmed.map(
|
||||||
|
(player, index) =>
|
||||||
|
({
|
||||||
|
id: player.id,
|
||||||
|
score: player.score,
|
||||||
|
position: firstPlayerIndex + index + 1,
|
||||||
|
clan: clanData[player.id]?.clan?.tag,
|
||||||
|
nickname:
|
||||||
|
clanData[index]?.account_name ??
|
||||||
|
translate(
|
||||||
|
'bot.commands.rating_leaderboard.body.deleted_player',
|
||||||
|
[`${player.id}`],
|
||||||
|
),
|
||||||
|
}) satisfies SimplifiedPlayer,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const leagueInfo = regionRatingInfo.leagues[leagueIndex];
|
||||||
|
midnightLeaderboard = isCurrentSeason
|
||||||
|
? await getArchivedRatingMidnightLeaderboard(
|
||||||
|
region,
|
||||||
|
regionRatingInfo.current_season,
|
||||||
|
)
|
||||||
|
: await getArchivedRatingMidnightLeaderboard(region, season!);
|
||||||
|
|
||||||
|
players = result;
|
||||||
|
playersAfter =
|
||||||
|
regionRatingInfo.count - result[result.length - 1].position;
|
||||||
|
titleName = `${
|
||||||
|
leagueInfo
|
||||||
|
? translate(`common.leagues.${LEAGUES[leagueInfo.index].name}`)
|
||||||
|
: ''
|
||||||
|
} - ${translate(`common.regions.short.${region}`)}`;
|
||||||
|
titleImage = leagueInfo.big_icon.startsWith('http')
|
||||||
|
? leagueInfo.big_icon
|
||||||
|
: `https:${leagueInfo.big_icon}`;
|
||||||
|
titleDescription = translate(
|
||||||
|
'bot.commands.rating_leaderboard.body.top_players',
|
||||||
|
[`${limit}`],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const { region, id } = await resolvePlayerFromCommand(interaction);
|
||||||
|
regionRatingInfo = isCurrentSeason
|
||||||
|
? await getRatingInfo(region)
|
||||||
|
: await getArchivedRatingInfo(region, season!);
|
||||||
|
|
||||||
|
if (regionRatingInfo.detail !== undefined)
|
||||||
|
return t`bot.commands.rating_leaderboard.body.no_ongoing_season`;
|
||||||
|
|
||||||
|
const accountInfo = await getAccountInfo(region, id);
|
||||||
|
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
|
||||||
|
const neighbors = isCurrentSeason
|
||||||
|
? await getRatingNeighbors(region, id, Math.round(limit / 2)).then(
|
||||||
|
(players) =>
|
||||||
|
players.neighbors.map(
|
||||||
|
(player) =>
|
||||||
|
({
|
||||||
|
id: player.spa_id,
|
||||||
|
position: player.number,
|
||||||
|
score: player.score,
|
||||||
|
clan: player.clan_tag,
|
||||||
|
nickname: player.nickname,
|
||||||
|
}) satisfies SimplifiedPlayer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: await getArchivedRatingLeaderboard(region, season!).then(
|
||||||
|
async (players) => {
|
||||||
|
const playerIndex = players.entries.findIndex(
|
||||||
|
(player) => player.id === id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (playerIndex === -1) {
|
||||||
|
throw new UserError(
|
||||||
|
translate(
|
||||||
|
'bot.commands.rating_leaderboard.body.no_participation',
|
||||||
|
[markdownEscape(accountInfo.nickname), `${season}`],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const halfRange = Math.round(limit / 2);
|
||||||
|
const trimmed = players.entries.slice(
|
||||||
|
playerIndex - halfRange,
|
||||||
|
playerIndex + halfRange + 1,
|
||||||
|
);
|
||||||
|
const clanData = await getClanAccountInfo(
|
||||||
|
region,
|
||||||
|
trimmed.map((player) => player.id),
|
||||||
|
['clan'],
|
||||||
|
);
|
||||||
|
|
||||||
|
return trimmed.map((player, index) => ({
|
||||||
|
id: player.id,
|
||||||
|
score: player.score,
|
||||||
|
position: playerIndex + index + 1,
|
||||||
|
clan: clanData[index]?.clan?.tag,
|
||||||
|
nickname: clanData[index]
|
||||||
|
? clanData[index]!.account_name
|
||||||
|
: `Deleted ${player.id}`,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
midnightLeaderboard = isCurrentSeason
|
||||||
|
? await getArchivedRatingMidnightLeaderboard(
|
||||||
|
region,
|
||||||
|
regionRatingInfo.current_season,
|
||||||
|
)
|
||||||
|
: await getArchivedRatingMidnightLeaderboard(region, season!);
|
||||||
|
|
||||||
|
players = neighbors;
|
||||||
|
titleImage = clan
|
||||||
|
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
||||||
|
: undefined;
|
||||||
|
titleName = neighbors ? accountInfo.nickname : '';
|
||||||
|
titleDescription = t`bot.commands.rating_leaderboard.body.neighbors`;
|
||||||
|
playersAfter =
|
||||||
|
regionRatingInfo.count - neighbors[neighbors.length - 1].position;
|
||||||
|
playerId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = players.map((player) => {
|
||||||
|
const midnightIndex = midnightLeaderboard?.entries.findIndex(
|
||||||
|
(item) => item.id === player.id,
|
||||||
|
);
|
||||||
|
const midnightScore =
|
||||||
|
midnightIndex === undefined
|
||||||
|
? player.score
|
||||||
|
: midnightLeaderboard!.entries[midnightIndex].score;
|
||||||
|
const midnightPosition =
|
||||||
|
midnightIndex === undefined ? player.position : midnightIndex + 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Leaderboard.Item
|
||||||
|
nickname={player.nickname}
|
||||||
|
score={player.score}
|
||||||
|
position={player.position}
|
||||||
|
clan={player.clan}
|
||||||
|
deltaScore={player.score - midnightScore}
|
||||||
|
deltaPosition={midnightPosition - player.position}
|
||||||
|
key={player.id}
|
||||||
|
highlight={player.id === playerId}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommandWrapper>
|
||||||
|
<TitleBar
|
||||||
|
title={titleName}
|
||||||
|
image={titleImage}
|
||||||
|
description={titleDescription}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{items && (
|
||||||
|
<Leaderboard.Root>
|
||||||
|
{items}
|
||||||
|
{playersAfter > 0 && (
|
||||||
|
<Leaderboard.Gap
|
||||||
|
message={translate(
|
||||||
|
'bot.commands.rating_leaderboard.body.more',
|
||||||
|
[`${playersAfter.toLocaleString(interaction.locale)}`],
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Leaderboard.Root>
|
||||||
|
)}
|
||||||
|
</CommandWrapper>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
autocomplete: autocompleteUsername,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -1,495 +0,0 @@
|
|||||||
import {
|
|
||||||
APIApplicationCommandOptionChoice,
|
|
||||||
Locale,
|
|
||||||
SlashCommandSubcommandBuilder,
|
|
||||||
} from 'discord.js';
|
|
||||||
import { range } from 'lodash';
|
|
||||||
import markdownEscape from 'markdown-escape';
|
|
||||||
import CommandWrapper from '../components/CommandWrapper';
|
|
||||||
import * as Leaderboard from '../components/Leaderboard';
|
|
||||||
import TitleBar from '../components/TitleBar';
|
|
||||||
import { LEAGUES } from '../constants/leagues';
|
|
||||||
import { FIRST_ARCHIVED_RATINGS_SEASON } from '../constants/ratings';
|
|
||||||
import { Region } from '../constants/regions';
|
|
||||||
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 { isOnGoingRatingsSeason } from '../core/blitz/isOnGoingRatingsSeason';
|
|
||||||
import { getArchivedLatestSeasonNumber } from '../core/blitzkrieg/getArchivedLatestSeasonNumber';
|
|
||||||
import getArchivedRatingsInfo from '../core/blitzkrieg/getArchivedRatingsInfo';
|
|
||||||
import { getArchivedRatingsLeaderboard } from '../core/blitzkrieg/getArchivedRatingsLeaderboard';
|
|
||||||
import getArchivedRatingsMidnightLeaderboard from '../core/blitzkrieg/getArchivedRatingsMidnightLeaderboard';
|
|
||||||
import addRegionChoices from '../core/discord/addRegionChoices';
|
|
||||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
|
||||||
import autocompleteUsername from '../core/discord/autocompleteUsername';
|
|
||||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
|
||||||
import { localizationObject } from '../core/discord/localizationObject';
|
|
||||||
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
|
||||||
import { translator } from '../core/localization/translator';
|
|
||||||
import { BkrlDiscriminatedEntries } from '../core/streams/bkrl';
|
|
||||||
import { CommandRegistry } from '../events/interactionCreate';
|
|
||||||
import { UserError } from '../hooks/userError';
|
|
||||||
|
|
||||||
export interface RatingsPlayer {
|
|
||||||
spa_id: number;
|
|
||||||
mmr: number;
|
|
||||||
season_number: number;
|
|
||||||
calibrationBattlesLeft: number;
|
|
||||||
number: number;
|
|
||||||
percentile: number;
|
|
||||||
skip: boolean;
|
|
||||||
updated_at: string;
|
|
||||||
score: number;
|
|
||||||
nickname: string;
|
|
||||||
clan_tag: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LeagueTop {
|
|
||||||
result: RatingsPlayer[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RatingsReward = {
|
|
||||||
from_position: number;
|
|
||||||
to_position: number;
|
|
||||||
count: number;
|
|
||||||
} & (
|
|
||||||
| {
|
|
||||||
type: 'vehicle';
|
|
||||||
|
|
||||||
vehicle: {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
nation: string;
|
|
||||||
subnation: string;
|
|
||||||
use_subnation_flag: boolean;
|
|
||||||
type_slug: string;
|
|
||||||
level: number;
|
|
||||||
roman_level: string;
|
|
||||||
user_string: string;
|
|
||||||
image_url: string;
|
|
||||||
preview_url: string;
|
|
||||||
is_premium: boolean;
|
|
||||||
is_collectible: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'stuff';
|
|
||||||
|
|
||||||
stuff: {
|
|
||||||
name: string;
|
|
||||||
count: number;
|
|
||||||
title: string;
|
|
||||||
image_url: string;
|
|
||||||
type: string;
|
|
||||||
|
|
||||||
sizes: {};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export interface RatingsLeague {
|
|
||||||
title: string;
|
|
||||||
small_icon: string;
|
|
||||||
big_icon: string;
|
|
||||||
background: string;
|
|
||||||
index: number;
|
|
||||||
percentile: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RatingsInfo =
|
|
||||||
| {
|
|
||||||
detail: undefined;
|
|
||||||
|
|
||||||
title: string;
|
|
||||||
icon: string | null;
|
|
||||||
start_at: string;
|
|
||||||
finish_at: string;
|
|
||||||
current_season: number;
|
|
||||||
updated_at: string;
|
|
||||||
count: number;
|
|
||||||
|
|
||||||
rewards: RatingsReward[];
|
|
||||||
leagues: RatingsLeague[];
|
|
||||||
}
|
|
||||||
| { detail: { error: string } };
|
|
||||||
|
|
||||||
export interface RatingsNeighbors {
|
|
||||||
neighbors: RatingsPlayer[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SimplifiedPlayer {
|
|
||||||
id: number;
|
|
||||||
score: number;
|
|
||||||
position: number;
|
|
||||||
clan: string | undefined;
|
|
||||||
nickname: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT_LIMIT = 10;
|
|
||||||
|
|
||||||
export const ratingsCommand = new Promise<CommandRegistry>(async (resolve) => {
|
|
||||||
const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber();
|
|
||||||
const onGoingSeason = await isOnGoingRatingsSeason();
|
|
||||||
const seasonNumbers = range(
|
|
||||||
FIRST_ARCHIVED_RATINGS_SEASON,
|
|
||||||
latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
function addOptions(option: SlashCommandSubcommandBuilder) {
|
|
||||||
const { t, translate } = translator(Locale.EnglishUS);
|
|
||||||
|
|
||||||
return option
|
|
||||||
.addIntegerOption((option) =>
|
|
||||||
option
|
|
||||||
.setName(t`bot.commands.ratings.options.limit`)
|
|
||||||
.setNameLocalizations(
|
|
||||||
localizationObject('bot.commands.ratings.options.limit'),
|
|
||||||
)
|
|
||||||
.setDescription(
|
|
||||||
translate('bot.commands.ratings.options.limit.description', [
|
|
||||||
`${DEFAULT_LIMIT}`,
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
.setDescriptionLocalizations(
|
|
||||||
localizationObject(
|
|
||||||
'bot.commands.ratings.options.limit.description',
|
|
||||||
[`${DEFAULT_LIMIT}`],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.setRequired(false)
|
|
||||||
.setMinValue(5)
|
|
||||||
.setMaxValue(30),
|
|
||||||
)
|
|
||||||
.addStringOption((option) =>
|
|
||||||
option
|
|
||||||
.setName(t`bot.commands.ratings.options.season`)
|
|
||||||
.setNameLocalizations(
|
|
||||||
localizationObject('bot.commands.ratings.options.season'),
|
|
||||||
)
|
|
||||||
.setDescription(
|
|
||||||
translate('bot.commands.ratings.options.season.description'),
|
|
||||||
)
|
|
||||||
.setDescriptionLocalizations(
|
|
||||||
localizationObject(
|
|
||||||
'bot.commands.ratings.options.season.description',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.setRequired(false)
|
|
||||||
.addChoices(
|
|
||||||
...seasonNumbers
|
|
||||||
.map((number, index) => {
|
|
||||||
const current =
|
|
||||||
index === seasonNumbers.length - 1 && onGoingSeason;
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: current
|
|
||||||
? translate(
|
|
||||||
'bot.commands.ratings.options.season.choices.current',
|
|
||||||
[`${number}`],
|
|
||||||
)
|
|
||||||
: `${number}`,
|
|
||||||
value: `${number}`,
|
|
||||||
name_localizations: current
|
|
||||||
? localizationObject(
|
|
||||||
'bot.commands.ratings.options.season.choices.current',
|
|
||||||
[`${number}`],
|
|
||||||
)
|
|
||||||
: undefined,
|
|
||||||
} satisfies APIApplicationCommandOptionChoice<string>;
|
|
||||||
})
|
|
||||||
.reverse(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve({
|
|
||||||
inProduction: true,
|
|
||||||
inPublic: true,
|
|
||||||
|
|
||||||
command: createLocalizedCommand('ratings', [
|
|
||||||
{
|
|
||||||
subcommand: 'neighbors',
|
|
||||||
modify(option: SlashCommandSubcommandBuilder) {
|
|
||||||
option.addStringOption(addUsernameChoices);
|
|
||||||
addOptions(option);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
subcommand: 'league',
|
|
||||||
modify(option: SlashCommandSubcommandBuilder) {
|
|
||||||
option
|
|
||||||
.addStringOption((option) => {
|
|
||||||
const { t, translate } = translator(Locale.EnglishUS);
|
|
||||||
|
|
||||||
return option
|
|
||||||
.setName(t`bot.commands.ratings.options.league`)
|
|
||||||
.setNameLocalizations(
|
|
||||||
localizationObject('bot.commands.ratings.options.league'),
|
|
||||||
)
|
|
||||||
.setDescription(
|
|
||||||
translate('bot.commands.ratings.options.league.description'),
|
|
||||||
)
|
|
||||||
.setDescriptionLocalizations(
|
|
||||||
localizationObject(
|
|
||||||
'bot.commands.ratings.options.league.description',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.addChoices(
|
|
||||||
...LEAGUES.map(
|
|
||||||
({ name }, value) =>
|
|
||||||
({
|
|
||||||
name: translate(`common.leagues.${name}`),
|
|
||||||
value: `${value}`,
|
|
||||||
name_localizations: localizationObject(
|
|
||||||
`common.leagues.${name}`,
|
|
||||||
),
|
|
||||||
}) satisfies APIApplicationCommandOptionChoice<string>,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.setRequired(true);
|
|
||||||
})
|
|
||||||
.addStringOption(addRegionChoices);
|
|
||||||
addOptions(option);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
|
|
||||||
// BIG TODO: title seems to be in english
|
|
||||||
|
|
||||||
async handler(interaction) {
|
|
||||||
const { t, translate } = translator(interaction.locale);
|
|
||||||
const subcommand = interaction.options.getSubcommand(true) as
|
|
||||||
| 'league'
|
|
||||||
| 'neighbors';
|
|
||||||
const limit = interaction.options.getInteger('limit') ?? DEFAULT_LIMIT;
|
|
||||||
const seasonOption = interaction.options.getString('season');
|
|
||||||
const season = seasonOption
|
|
||||||
? parseInt(seasonOption)
|
|
||||||
: onGoingSeason
|
|
||||||
? latestArchivedSeasonNumber + 1
|
|
||||||
: latestArchivedSeasonNumber;
|
|
||||||
const isCurrentSeason = season === latestArchivedSeasonNumber + 1;
|
|
||||||
let titleName: string;
|
|
||||||
let titleImage: string | undefined;
|
|
||||||
let titleDescription: string;
|
|
||||||
let playersAfter: number;
|
|
||||||
let players: SimplifiedPlayer[] | undefined;
|
|
||||||
let midnightLeaderboard: BkrlDiscriminatedEntries | null;
|
|
||||||
let playerId: number | undefined;
|
|
||||||
let regionRatingsInfo: RatingsInfo;
|
|
||||||
|
|
||||||
if (subcommand === 'league') {
|
|
||||||
const leagueIndex = parseInt(interaction.options.getString('league')!);
|
|
||||||
const region = interaction.options.getString('region') as Region;
|
|
||||||
regionRatingsInfo = await (isCurrentSeason
|
|
||||||
? getRatingsInfo(region)
|
|
||||||
: getArchivedRatingsInfo(region, season!));
|
|
||||||
|
|
||||||
if (regionRatingsInfo.detail !== undefined)
|
|
||||||
return t`bot.commands.ratings.body.no_ongoing_season`;
|
|
||||||
|
|
||||||
const result = isCurrentSeason
|
|
||||||
? await getRatingsLeague(region, leagueIndex).then(async (result) =>
|
|
||||||
result.slice(0, limit).map(
|
|
||||||
(player) =>
|
|
||||||
({
|
|
||||||
id: player.spa_id,
|
|
||||||
score: player.score,
|
|
||||||
position: player.number,
|
|
||||||
clan: player.clan_tag,
|
|
||||||
nickname: player.nickname,
|
|
||||||
}) satisfies SimplifiedPlayer,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: await getArchivedRatingsLeaderboard(region, season!).then(
|
|
||||||
async (players) => {
|
|
||||||
const firstPlayerIndex =
|
|
||||||
leagueIndex === 0
|
|
||||||
? 0
|
|
||||||
: players.entries.findIndex(
|
|
||||||
(player) =>
|
|
||||||
player.score < LEAGUES[leagueIndex - 1].minScore,
|
|
||||||
);
|
|
||||||
const lastPlayerIndex = firstPlayerIndex + limit - 1;
|
|
||||||
const trimmed = players.entries.slice(
|
|
||||||
firstPlayerIndex,
|
|
||||||
lastPlayerIndex + 1,
|
|
||||||
);
|
|
||||||
const clanData = await getClanAccountInfo(
|
|
||||||
region,
|
|
||||||
trimmed.map(({ id }) => id),
|
|
||||||
['clan'],
|
|
||||||
);
|
|
||||||
|
|
||||||
return trimmed.map(
|
|
||||||
(player, index) =>
|
|
||||||
({
|
|
||||||
id: player.id,
|
|
||||||
score: player.score,
|
|
||||||
position: firstPlayerIndex + index + 1,
|
|
||||||
clan: clanData[player.id]?.clan?.tag,
|
|
||||||
nickname:
|
|
||||||
clanData[index]?.account_name ??
|
|
||||||
translate('bot.commands.ratings.body.deleted_player', [
|
|
||||||
`${player.id}`,
|
|
||||||
]),
|
|
||||||
}) satisfies SimplifiedPlayer,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const leagueInfo = regionRatingsInfo.leagues[leagueIndex];
|
|
||||||
midnightLeaderboard = isCurrentSeason
|
|
||||||
? await getArchivedRatingsMidnightLeaderboard(
|
|
||||||
region,
|
|
||||||
regionRatingsInfo.current_season,
|
|
||||||
)
|
|
||||||
: await getArchivedRatingsMidnightLeaderboard(region, season!);
|
|
||||||
|
|
||||||
players = result;
|
|
||||||
playersAfter =
|
|
||||||
regionRatingsInfo.count - result[result.length - 1].position;
|
|
||||||
titleName = `${
|
|
||||||
leagueInfo
|
|
||||||
? translate(`common.leagues.${LEAGUES[leagueInfo.index].name}`)
|
|
||||||
: ''
|
|
||||||
} - ${translate(`common.regions.short.${region}`)}`;
|
|
||||||
titleImage = leagueInfo.big_icon.startsWith('http')
|
|
||||||
? leagueInfo.big_icon
|
|
||||||
: `https:${leagueInfo.big_icon}`;
|
|
||||||
titleDescription = translate('bot.commands.ratings.body.top_players', [
|
|
||||||
`${limit}`,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
const { region, id } = await resolvePlayerFromCommand(interaction);
|
|
||||||
regionRatingsInfo = isCurrentSeason
|
|
||||||
? await getRatingsInfo(region)
|
|
||||||
: await getArchivedRatingsInfo(region, season!);
|
|
||||||
|
|
||||||
if (regionRatingsInfo.detail !== undefined)
|
|
||||||
return t`bot.commands.ratings.body.no_ongoing_season`;
|
|
||||||
|
|
||||||
const accountInfo = await getAccountInfo(region, id);
|
|
||||||
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
|
|
||||||
const neighbors = isCurrentSeason
|
|
||||||
? await getRatingsNeighbors(region, id, Math.round(limit / 2)).then(
|
|
||||||
(players) =>
|
|
||||||
players.neighbors.map(
|
|
||||||
(player) =>
|
|
||||||
({
|
|
||||||
id: player.spa_id,
|
|
||||||
position: player.number,
|
|
||||||
score: player.score,
|
|
||||||
clan: player.clan_tag,
|
|
||||||
nickname: player.nickname,
|
|
||||||
}) satisfies SimplifiedPlayer,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: await getArchivedRatingsLeaderboard(region, season!).then(
|
|
||||||
async (players) => {
|
|
||||||
const playerIndex = players.entries.findIndex(
|
|
||||||
(player) => player.id === id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (playerIndex === -1) {
|
|
||||||
throw new UserError(
|
|
||||||
translate('bot.commands.ratings.body.no_participation', [
|
|
||||||
markdownEscape(accountInfo.nickname),
|
|
||||||
`${season}`,
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const halfRange = Math.round(limit / 2);
|
|
||||||
const trimmed = players.entries.slice(
|
|
||||||
playerIndex - halfRange,
|
|
||||||
playerIndex + halfRange + 1,
|
|
||||||
);
|
|
||||||
const clanData = await getClanAccountInfo(
|
|
||||||
region,
|
|
||||||
trimmed.map((player) => player.id),
|
|
||||||
['clan'],
|
|
||||||
);
|
|
||||||
|
|
||||||
return trimmed.map((player, index) => ({
|
|
||||||
id: player.id,
|
|
||||||
score: player.score,
|
|
||||||
position: playerIndex + index + 1,
|
|
||||||
clan: clanData[index]?.clan?.tag,
|
|
||||||
nickname: clanData[index]
|
|
||||||
? clanData[index]!.account_name
|
|
||||||
: `Deleted ${player.id}`,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
midnightLeaderboard = isCurrentSeason
|
|
||||||
? await getArchivedRatingsMidnightLeaderboard(
|
|
||||||
region,
|
|
||||||
regionRatingsInfo.current_season,
|
|
||||||
)
|
|
||||||
: await getArchivedRatingsMidnightLeaderboard(region, season!);
|
|
||||||
|
|
||||||
players = neighbors;
|
|
||||||
titleImage = clan
|
|
||||||
? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png`
|
|
||||||
: undefined;
|
|
||||||
titleName = neighbors ? accountInfo.nickname : '';
|
|
||||||
titleDescription = t`bot.commands.ratings.body.neighbors`;
|
|
||||||
playersAfter =
|
|
||||||
regionRatingsInfo.count - neighbors[neighbors.length - 1].position;
|
|
||||||
playerId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = players.map((player) => {
|
|
||||||
const midnightIndex = midnightLeaderboard?.entries.findIndex(
|
|
||||||
(item) => item.id === player.id,
|
|
||||||
);
|
|
||||||
const midnightScore =
|
|
||||||
midnightIndex === undefined
|
|
||||||
? player.score
|
|
||||||
: midnightLeaderboard!.entries[midnightIndex].score;
|
|
||||||
const midnightPosition =
|
|
||||||
midnightIndex === undefined ? player.position : midnightIndex + 1;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Leaderboard.Item
|
|
||||||
nickname={player.nickname}
|
|
||||||
score={player.score}
|
|
||||||
position={player.position}
|
|
||||||
clan={player.clan}
|
|
||||||
deltaScore={player.score - midnightScore}
|
|
||||||
deltaPosition={midnightPosition - player.position}
|
|
||||||
key={player.id}
|
|
||||||
highlight={player.id === playerId}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CommandWrapper>
|
|
||||||
<TitleBar
|
|
||||||
title={titleName}
|
|
||||||
image={titleImage}
|
|
||||||
description={titleDescription}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{items && (
|
|
||||||
<Leaderboard.Root>
|
|
||||||
{items}
|
|
||||||
{playersAfter > 0 && (
|
|
||||||
<Leaderboard.Gap
|
|
||||||
message={translate('bot.commands.ratings.body.more', [
|
|
||||||
`${playersAfter.toLocaleString(interaction.locale)}`,
|
|
||||||
])}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Leaderboard.Root>
|
|
||||||
)}
|
|
||||||
</CommandWrapper>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
autocomplete: autocompleteUsername,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -154,6 +154,7 @@ export function Item({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{score !== undefined && (
|
{score !== undefined && (
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export const FIRST_ARCHIVED_RATING_SEASON = 49;
|
||||||
@@ -1 +0,0 @@
|
|||||||
export const FIRST_ARCHIVED_RATINGS_SEASON = 49;
|
|
||||||
@@ -18,9 +18,9 @@ export const TOOLS: Tool[] = [
|
|||||||
description: 'Blitz tank encyclopedia',
|
description: 'Blitz tank encyclopedia',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'ratings',
|
id: 'rating-leaderboard',
|
||||||
title: 'Ratings',
|
title: 'Rating leaderboard',
|
||||||
description: 'Ratings gamemode leaderboards',
|
description: 'Full rating gamemode leaderboard',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'discord',
|
id: 'discord',
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
import { RatingsInfo } from '../../commands/ratings';
|
import { RatingInfo } from '../../commands/ratingLeaderboard';
|
||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import { context } from '../blitzkrieg/context';
|
import { context } from '../blitzkrieg/context';
|
||||||
import { patientFetch } from '../blitzkrieg/patientFetch';
|
import { patientFetch } from '../blitzkrieg/patientFetch';
|
||||||
import regionToRegionSubdomain from './regionToRegionSubdomain';
|
import regionToRegionSubdomain from './regionToRegionSubdomain';
|
||||||
|
|
||||||
const ratingsInfoCache: Partial<Record<Region, RatingsInfo>> = {};
|
const cache: Partial<Record<Region, RatingInfo>> = {};
|
||||||
|
|
||||||
export default async function getRatingsInfo(region: Region) {
|
export default async function getRatingInfo(region: Region) {
|
||||||
if (ratingsInfoCache[region]) return ratingsInfoCache[region]!;
|
if (cache[region]) return cache[region]!;
|
||||||
|
|
||||||
const response = await patientFetch(
|
const response = await patientFetch(
|
||||||
context === 'website'
|
context === 'website'
|
||||||
? `/api/${region}/ratings/current/info`
|
? `/api/${region}/rating/current/info`
|
||||||
: `https://${regionToRegionSubdomain(
|
: `https://${regionToRegionSubdomain(
|
||||||
region,
|
region,
|
||||||
)}.wotblitz.com/en/api/rating-leaderboards/season/`,
|
)}.wotblitz.com/en/api/rating-leaderboards/season/`,
|
||||||
undefined,
|
undefined,
|
||||||
{ cache: 'no-store' },
|
{ cache: 'no-store' },
|
||||||
);
|
);
|
||||||
const data = (await response.json()) as RatingsInfo;
|
const data = (await response.json()) as RatingInfo;
|
||||||
|
|
||||||
ratingsInfoCache[region] = data;
|
cache[region] = data;
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { LeagueTop, RatingsPlayer } from '../../commands/ratings';
|
import { LeagueTop, RatingPlayer } from '../../commands/ratingLeaderboard';
|
||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import { context } from '../blitzkrieg/context';
|
import { context } from '../blitzkrieg/context';
|
||||||
import { patientFetch } from '../blitzkrieg/patientFetch';
|
import { patientFetch } from '../blitzkrieg/patientFetch';
|
||||||
import regionToRegionSubdomain from './regionToRegionSubdomain';
|
import regionToRegionSubdomain from './regionToRegionSubdomain';
|
||||||
|
|
||||||
export async function getRatingsLeague(
|
export async function getRatingLeague(
|
||||||
region: Region,
|
region: Region,
|
||||||
league: number,
|
league: number,
|
||||||
usePatientFetcher = false,
|
usePatientFetcher = false,
|
||||||
@@ -15,7 +15,7 @@ export async function getRatingsLeague(
|
|||||||
: fetch(url, { cache: 'no-store' });
|
: fetch(url, { cache: 'no-store' });
|
||||||
const response = await fetcher(
|
const response = await fetcher(
|
||||||
context === 'website'
|
context === 'website'
|
||||||
? `/api/${region}/ratings/current/league/${league}`
|
? `/api/${region}/rating/current/league/${league}`
|
||||||
: `https://${regionToRegionSubdomain(
|
: `https://${regionToRegionSubdomain(
|
||||||
region,
|
region,
|
||||||
)}.wotblitz.com/en/api/rating-leaderboards/league/${league}/top/`,
|
)}.wotblitz.com/en/api/rating-leaderboards/league/${league}/top/`,
|
||||||
@@ -23,6 +23,6 @@ export async function getRatingsLeague(
|
|||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
|
||||||
return context === 'website'
|
return context === 'website'
|
||||||
? (json as RatingsPlayer[])
|
? (json as RatingPlayer[])
|
||||||
: (json as LeagueTop).result;
|
: (json as LeagueTop).result;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { RatingsNeighbors } from '../../commands/ratings';
|
import { RatingNeighbors } from '../../commands/ratingLeaderboard';
|
||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import { patientFetch } from '../blitzkrieg/patientFetch';
|
import { patientFetch } from '../blitzkrieg/patientFetch';
|
||||||
import { withCORSProxy } from '../blitzkrieg/withCORSProxy';
|
import { withCORSProxy } from '../blitzkrieg/withCORSProxy';
|
||||||
import regionToRegionSubdomain from './regionToRegionSubdomain';
|
import regionToRegionSubdomain from './regionToRegionSubdomain';
|
||||||
|
|
||||||
export async function getRatingsNeighbors(
|
export async function getRatingNeighbors(
|
||||||
region: Region,
|
region: Region,
|
||||||
id: number,
|
id: number,
|
||||||
count: number,
|
count: number,
|
||||||
@@ -21,7 +21,7 @@ export async function getRatingsNeighbors(
|
|||||||
)}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${count}`,
|
)}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${count}`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const json = (await response.json()) as RatingsNeighbors;
|
const json = (await response.json()) as RatingNeighbors;
|
||||||
|
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import getRatingInfo from './getRatingInfo';
|
||||||
|
|
||||||
|
export async function isOnGoingRatingSeason() {
|
||||||
|
const ratingInfo = await getRatingInfo('com');
|
||||||
|
return ratingInfo.detail === undefined;
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import getRatingsInfo from './getRatingsInfo';
|
|
||||||
|
|
||||||
export async function isOnGoingRatingsSeason() {
|
|
||||||
const ratingsInfo = await getRatingsInfo('com');
|
|
||||||
return ratingsInfo.detail === undefined;
|
|
||||||
}
|
|
||||||
+4
-7
@@ -2,7 +2,7 @@ import { Region } from '../../constants/regions';
|
|||||||
import { context } from '../blitzkrieg/context';
|
import { context } from '../blitzkrieg/context';
|
||||||
import regionToRegionSubdomain from './regionToRegionSubdomain';
|
import regionToRegionSubdomain from './regionToRegionSubdomain';
|
||||||
|
|
||||||
type CurrentRatingsPlayerSearch = Record<
|
type CurrentRatingPlayerSearch = Record<
|
||||||
number,
|
number,
|
||||||
{
|
{
|
||||||
spa_id: number;
|
spa_id: number;
|
||||||
@@ -24,20 +24,17 @@ type CurrentRatingsPlayerSearch = Record<
|
|||||||
)
|
)
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export async function searchCurrentRatingsPlayers(
|
export async function searchCurrentRatingPlayers(region: Region, name: string) {
|
||||||
region: Region,
|
|
||||||
name: string,
|
|
||||||
) {
|
|
||||||
const encodedSearch = encodeURIComponent(name);
|
const encodedSearch = encodeURIComponent(name);
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
context === 'website'
|
context === 'website'
|
||||||
? `/api/${region}/ratings/current/search/${encodedSearch}`
|
? `/api/${region}/rating/current/search/${encodedSearch}`
|
||||||
: `https://${regionToRegionSubdomain(
|
: `https://${regionToRegionSubdomain(
|
||||||
region,
|
region,
|
||||||
)}.wotblitz.com/en/api/rating-leaderboards/search/?prefix=${encodedSearch}`,
|
)}.wotblitz.com/en/api/rating-leaderboards/search/?prefix=${encodedSearch}`,
|
||||||
{ cache: 'no-store' },
|
{ cache: 'no-store' },
|
||||||
);
|
);
|
||||||
const accountList = (await response.json()) as CurrentRatingsPlayerSearch;
|
const accountList = (await response.json()) as CurrentRatingPlayerSearch;
|
||||||
|
|
||||||
return accountList;
|
return accountList;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import getRatingsInfo from '../blitz/getRatingsInfo';
|
import getRatingInfo from '../blitz/getRatingInfo';
|
||||||
|
|
||||||
interface GitHubTrees {
|
interface GitHubTrees {
|
||||||
tree: { path: string; url: string }[];
|
tree: { path: string; url: string }[];
|
||||||
@@ -11,9 +11,9 @@ export async function getArchivedLatestSeasonNumber() {
|
|||||||
return cachedLatestArchivedSeasonNumber;
|
return cachedLatestArchivedSeasonNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ratingsInfo = await getRatingsInfo('com');
|
const ratingInfo = await getRatingInfo('com');
|
||||||
|
|
||||||
if (ratingsInfo.detail) {
|
if (ratingInfo.detail) {
|
||||||
const regionsURL = (
|
const regionsURL = (
|
||||||
(await fetch(
|
(await fetch(
|
||||||
'https://api.github.com/repos/tresabhi/blitzkrieg-assets/git/trees/main',
|
'https://api.github.com/repos/tresabhi/blitzkrieg-assets/git/trees/main',
|
||||||
@@ -24,14 +24,14 @@ export async function getArchivedLatestSeasonNumber() {
|
|||||||
response.json(),
|
response.json(),
|
||||||
)) as GitHubTrees
|
)) as GitHubTrees
|
||||||
).tree.find(({ path }) => path === 'com')!.url;
|
).tree.find(({ path }) => path === 'com')!.url;
|
||||||
const ratingsURL = (
|
const ratingURL = (
|
||||||
(await fetch(comURL).then((response) => response.json())) as {
|
(await fetch(comURL).then((response) => response.json())) as {
|
||||||
tree: [{ url: string }];
|
tree: [{ url: string }];
|
||||||
}
|
}
|
||||||
).tree[0].url;
|
).tree[0].url;
|
||||||
const latestSeason = Math.max(
|
const latestSeason = Math.max(
|
||||||
...(
|
...(
|
||||||
(await fetch(ratingsURL).then((response) => response.json())) as {
|
(await fetch(ratingURL).then((response) => response.json())) as {
|
||||||
tree: { path: string }[];
|
tree: { path: string }[];
|
||||||
}
|
}
|
||||||
).tree.map(({ path }) => Number(path)),
|
).tree.map(({ path }) => Number(path)),
|
||||||
@@ -39,7 +39,7 @@ export async function getArchivedLatestSeasonNumber() {
|
|||||||
|
|
||||||
cachedLatestArchivedSeasonNumber = latestSeason;
|
cachedLatestArchivedSeasonNumber = latestSeason;
|
||||||
} else {
|
} else {
|
||||||
cachedLatestArchivedSeasonNumber = ratingsInfo.current_season - 1;
|
cachedLatestArchivedSeasonNumber = ratingInfo.current_season - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cachedLatestArchivedSeasonNumber;
|
return cachedLatestArchivedSeasonNumber;
|
||||||
|
|||||||
+5
-5
@@ -1,4 +1,4 @@
|
|||||||
import { RatingsInfo } from '../../commands/ratings';
|
import { RatingInfo } from '../../commands/ratingLeaderboard';
|
||||||
import { Region } from '../../constants/regions';
|
import { Region } from '../../constants/regions';
|
||||||
import { asset } from './asset';
|
import { asset } from './asset';
|
||||||
import { superDecompress } from './superDecompress';
|
import { superDecompress } from './superDecompress';
|
||||||
@@ -7,7 +7,7 @@ const cache: Record<
|
|||||||
Region,
|
Region,
|
||||||
Record<
|
Record<
|
||||||
number,
|
number,
|
||||||
RatingsInfo & {
|
RatingInfo & {
|
||||||
detail: undefined;
|
detail: undefined;
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -17,17 +17,17 @@ const cache: Record<
|
|||||||
asia: {},
|
asia: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function getArchivedRatingsInfo(
|
export default async function getArchivedRatingInfo(
|
||||||
region: Region,
|
region: Region,
|
||||||
season: number,
|
season: number,
|
||||||
) {
|
) {
|
||||||
if (!cache[region][season]) {
|
if (!cache[region][season]) {
|
||||||
const info = await fetch(
|
const info = await fetch(
|
||||||
asset(`regions/${region}/ratings/${season}/info.cdon.lz4`),
|
asset(`regions/${region}/rating/${season}/info.cdon.lz4`),
|
||||||
)
|
)
|
||||||
.then((response) => response.arrayBuffer())
|
.then((response) => response.arrayBuffer())
|
||||||
.then((buffer) =>
|
.then((buffer) =>
|
||||||
superDecompress<RatingsInfo & { detail: undefined }>(
|
superDecompress<RatingInfo & { detail: undefined }>(
|
||||||
new Uint8Array(buffer),
|
new Uint8Array(buffer),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
+2
-2
@@ -8,13 +8,13 @@ const cache: Record<Region, Record<number, BkrlDiscriminatedEntries>> = {
|
|||||||
com: {},
|
com: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getArchivedRatingsLeaderboard(
|
export async function getArchivedRatingLeaderboard(
|
||||||
region: Region,
|
region: Region,
|
||||||
season: number,
|
season: number,
|
||||||
) {
|
) {
|
||||||
if (!cache[region][season]) {
|
if (!cache[region][season]) {
|
||||||
const info = await fetch(
|
const info = await fetch(
|
||||||
asset(`regions/${region}/ratings/${season}/latest.bkrl`),
|
asset(`regions/${region}/rating/${season}/latest.bkrl`),
|
||||||
)
|
)
|
||||||
.then((response) => response.arrayBuffer())
|
.then((response) => response.arrayBuffer())
|
||||||
.then((buffer) => new BkrlReadStream(buffer).bkrl());
|
.then((buffer) => new BkrlReadStream(buffer).bkrl());
|
||||||
+2
-2
@@ -8,13 +8,13 @@ const cache: Record<Region, Record<number, BkrlDiscriminatedEntries | null>> = {
|
|||||||
asia: {},
|
asia: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function getArchivedRatingsMidnightLeaderboard(
|
export default async function getArchivedRatingMidnightLeaderboard(
|
||||||
region: Region,
|
region: Region,
|
||||||
season: number,
|
season: number,
|
||||||
) {
|
) {
|
||||||
if (!cache[region][season]) {
|
if (!cache[region][season]) {
|
||||||
const info = await fetch(
|
const info = await fetch(
|
||||||
asset(`regions/${region}/ratings/${season}/midnight.bkrl`),
|
asset(`regions/${region}/rating/${season}/midnight.bkrl`),
|
||||||
)
|
)
|
||||||
.then((response) =>
|
.then((response) =>
|
||||||
response.status === 404 ? null : response.arrayBuffer(),
|
response.status === 404 ? null : response.arrayBuffer(),
|
||||||
@@ -69,7 +69,7 @@ export default async function getStatsInPeriod(
|
|||||||
return get(latest.all) - get(previous.all);
|
return get(latest.all) - get(previous.all);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if there was a change in battles as games in ratings do update last_battle_time
|
// check if there was a change in battles as games in rating do update last_battle_time
|
||||||
if (d((a) => a.battles) > 0) {
|
if (d((a) => a.battles) > 0) {
|
||||||
diff[tankId] = {
|
diff[tankId] = {
|
||||||
battles: d((a) => a.battles),
|
battles: d((a) => a.battles),
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { ownedTanksCommand } from '../../commands/ownedTanks';
|
|||||||
import { permissionsCommand } from '../../commands/permissions';
|
import { permissionsCommand } from '../../commands/permissions';
|
||||||
import { pingCommand } from '../../commands/ping';
|
import { pingCommand } from '../../commands/ping';
|
||||||
import { playerInfoCommand } from '../../commands/playerInfo';
|
import { playerInfoCommand } from '../../commands/playerInfo';
|
||||||
import { ratingsCommand } from '../../commands/ratings';
|
import { ratingLeaderboardCommand } from '../../commands/ratingLeaderboard';
|
||||||
import { researchCommand } from '../../commands/research';
|
import { researchCommand } from '../../commands/research';
|
||||||
import { searchClansCommand } from '../../commands/searchClans';
|
import { searchClansCommand } from '../../commands/searchClans';
|
||||||
import { searchPlayersCommand } from '../../commands/searchPlayers';
|
import { searchPlayersCommand } from '../../commands/searchPlayers';
|
||||||
@@ -93,7 +93,7 @@ export const COMMANDS_RAW: Promise<CommandRegistry>[] = [
|
|||||||
pingCommand,
|
pingCommand,
|
||||||
evolutionCommand,
|
evolutionCommand,
|
||||||
statsCommand,
|
statsCommand,
|
||||||
ratingsCommand,
|
ratingLeaderboardCommand,
|
||||||
todayCommand,
|
todayCommand,
|
||||||
researchCommand,
|
researchCommand,
|
||||||
testCommand,
|
testCommand,
|
||||||
|
|||||||
+6
-6
@@ -414,9 +414,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"ratings": {
|
"rating_leaderboard": {
|
||||||
"$": "ratings",
|
"$": "rating-leaderboard",
|
||||||
"description": "Ratings battles statistics",
|
"description": "Rating battles statistics",
|
||||||
|
|
||||||
"subcommands": {
|
"subcommands": {
|
||||||
"neighbors": {
|
"neighbors": {
|
||||||
@@ -453,10 +453,10 @@
|
|||||||
|
|
||||||
"body": {
|
"body": {
|
||||||
"top_players": "Top %s players",
|
"top_players": "Top %s players",
|
||||||
"no_participation": "# %s didn't play ratings in season %s\n\nThis player did not participate in this season or did not get past calibration.",
|
"no_participation": "# %s didn't play rating in season %s\n\nThis player did not participate in this season or did not get past calibration.",
|
||||||
"no_ongoing_season": "# No ongoing season\n\nThere isn't a currently running ratings season.",
|
"no_ongoing_season": "# No ongoing season\n\nThere isn't a currently running rating season.",
|
||||||
"deleted_player": "Deleted %s",
|
"deleted_player": "Deleted %s",
|
||||||
"neighbors": "Ratings neighbors",
|
"neighbors": "Rating neighbors",
|
||||||
"more": "+ %s more"
|
"more": "+ %s more"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user