Compare commits

...
13 Commits
14 changed files with 127 additions and 89 deletions

No files matched your search

+2
View File
@@ -13,9 +13,11 @@
"Everytime",
"fullstats",
"fuzzysort",
"immer",
"kranvagn",
"Leaderboard",
"leaderboards",
"MURICA",
"Oopsy",
"outdir",
"outfile",
+1 -1
View File
@@ -13,7 +13,7 @@ export default defineConfig({
themeConfig: {
nav: [
{ text: 'Home', link: '/' },
{ text: 'Guide', link: '/guide/about' },
{ text: 'Guide', link: '/guide/introduction' },
],
sidebar: [
+24
View File
@@ -0,0 +1,24 @@
# Blitzkrieg 1.15.1
'MURICA!
## Changes
- "Neighbor" is now spelled in the American way
- Updated documentation website with the latest information
## Fixes
- Fixed the guide link on the documentation website
## Technical features
- Added capability to fetch patiently while also parsing the JSON
## Technical changes
- Removed neighbor relative position caching
## Technical fixes
- `/ratings` now uses the non-caching APIs
+9
View File
@@ -0,0 +1,9 @@
# Blitzkrieg 1.15.2
Infinity reduced to a finite value.
## Fixes
- Added a maximum value to the jump to position button
- Removed neighbor checking for the seeding player
- This fixes the jump to position button not loading at all
-17
View File
@@ -1,17 +0,0 @@
# Installing Blitzkrieg
It's easy as 1, 2, 3!
## Step 1
Join a server that already has Blitzkrieg or [join our official Discord server](https://discord.gg/nDt7AjGJQH).
## Step 2
View the bot's profile and click "Add to Server".
<video src="https://i.imgur.com/Zq0P3m2.mp4" autoplay loop muted width=320 title="how to install"></video>
## Step 3
Review the permissions the bot is asking for and add it to your server!
+13 -1
View File
@@ -1,6 +1,10 @@
# Introduction
Blitzkrieg is a multipurpose bot for anything and everything World of Tanks Blitz.
🎉 Services for everything and anything World of Tanks Blitz
## Bot
Blitzkrieg is available as a Discord bot and can be [installed very easily](/guide/invite).
- Track statistics
- Find inactive players in a clan
@@ -8,3 +12,11 @@ Blitzkrieg is a multipurpose bot for anything and everything World of Tanks Blit
- Create embeds for streams
- Track a player/tanks statistical evolution
- ...and a variety of other similar things
## Website
Provides a bunch of very helpful tools; https://blitz-krieg.vercel.app/
- Session tracker
- Ratings leaderboard
- Way more to come
+2 -2
View File
@@ -12,10 +12,10 @@ hero:
actions:
- theme: brand
text: Add to Discord Server
link: guide/install
link: /guide/invite
- theme: alt
text: Guide
link: /guide/about
link: /guide/introduction
features:
- title: Clean and Efficient UI
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "blitzkrieg",
"version": "1.15.0",
"version": "1.15.2",
"description": "🎉 Tools for everything World of Tanks Blitz",
"main": "src/server.ts",
"type": "module",
+8 -13
View File
@@ -6,7 +6,7 @@ import {
RatingsNeighbors,
RatingsPlayer,
} from '../src/commands/ratings';
import { patientFetch } from '../src/core/blitzkrieg/patientFetch';
import { patientFetchJSON } from '../src/core/blitzkrieg/patientFetchJSON';
import commitMultipleFiles from './commitMultipleFiles.js';
/*
@@ -29,11 +29,9 @@ const server = argv.find((arg) => arg.startsWith('--region='))?.split('=')[1];
if (!server) throw new Error('Region parameter not specified');
const NEIGHBORS = 1024;
const PLAYERS = await patientFetch(
const PLAYERS = await patientFetchJSON<RatingsInfo>(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`,
)
.then((response) => response.json() as Promise<RatingsInfo>)
.then((data) => (data.detail === undefined ? data.count : undefined));
).then((data) => (data.detail === undefined ? data.count : undefined));
if (PLAYERS === undefined) {
throw new Error('No current ratings season running');
@@ -44,9 +42,9 @@ const leaderboard: BlitzkriegRatingsLeaderboard = [];
let registered = 0;
async function branchFromPlayer(id: number) {
const { neighbors } = await patientFetch(
const { neighbors } = await patientFetchJSON<RatingsNeighbors>(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${NEIGHBORS}`,
).then((response) => response.json() as Promise<RatingsNeighbors>);
);
const firstPlayer = neighbors[0];
const lastPlayer = neighbors[neighbors.length - 1];
@@ -75,9 +73,9 @@ async function branchFromPlayer(id: number) {
}
async function branchFromLeague(id: number) {
const { result } = await patientFetch(
const { result } = await patientFetchJSON<{ result: RatingsPlayer[] }>(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/league/${id}/top/`,
).then((response) => response.json() as Promise<{ result: RatingsPlayer[] }>);
);
const firstPlayer = result[0];
const lastPlayer = result[result.length - 1];
@@ -113,11 +111,8 @@ const leaderboardJSON = JSON.stringify(leaderboard);
if (publish) {
const octokit = new Octokit({ auth: env.GH_TOKEN });
const info = await patientFetch(
const info = await patientFetchJSON<RatingsInfo & { detail: undefined }>(
`https://${server}.wotblitz.com/en/api/rating-leaderboards/season/`,
).then(
(response) =>
response.json() as Promise<RatingsInfo & { detail: undefined }>,
);
const normalizedServer = server === 'na' ? 'com' : server;
+6 -16
View File
@@ -137,15 +137,12 @@ export default function Page() {
await Promise.all(
range(0, 5).map(async (league: number) => {
const data = await getRatingsLeague(region, league);
leaguePositionCache.current[region][league] =
data.result[0].number - 1;
leaguePositionCache.current[region][league] = data[0].number - 1;
return data;
}),
)
)
.map(({ result }) => result)
.flat();
).flat();
useLeaderboardCache.setState(
produce((draft: LeaderboardCache) => {
@@ -302,19 +299,12 @@ export default function Page() {
}
}
const seedingPlayerNeighborEntry = Object.entries(
leaderboard[region][season],
).find(([, player]) => id === player);
if (seedingPlayerNeighborEntry === undefined) return null;
const seedingPlayerPosition = parseInt(seedingPlayerNeighborEntry[0]);
const { neighbors } = await getRatingsNeighbors(region, id, size, true);
useLeaderboardCache.setState(
produce((draft: LeaderboardCache) => {
neighbors.forEach((neighbor, neighborIndex) => {
const position = neighborIndex - radius + seedingPlayerPosition;
const position = neighbor.number - 1;
if (!(position in draft[region][season])) {
draft[region][season][position] = neighbor.spa_id;
@@ -349,9 +339,9 @@ export default function Page() {
async function jumpToPosition(position: number) {
if (!ratingsInfo || ratingsInfo?.detail) return;
const targetPosition = Math.max(
0,
Math.min(position, ratingsInfo.count - 1),
const targetPosition = Math.min(
ratingsInfo.count - 1,
Math.max(0, Math.min(position, ratingsInfo.count - 1)),
);
if (season === 0) {
+25 -31
View File
@@ -15,6 +15,8 @@ import { REGION_NAMES_SHORT, 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 regionToRegionSubdomain from '../core/blitz/regionToRegionSubdomain';
import { getArchivedLatestSeasonNumber } from '../core/blitzkrieg/getArchivedLatestSeasonNumber';
import getArchivedRatingsInfo from '../core/blitzkrieg/getArchivedRatingsInfo';
@@ -123,14 +125,14 @@ function addSubcommands(
.addSubcommand((option) =>
addOptions(
option
.setName('neighbours')
.setDescription('Your position and neighbours'),
.setName('neighbors')
.setDescription('Your position and neighbors'),
)
.addStringOption(addUsernameChoices)
.addIntegerOption((option) =>
option
.setName('limit')
.setDescription('How many neighbours to display (default: 10)')
.setDescription('How many neighbors to display (default: 10)')
.setRequired(false)
.setMinValue(5)
.setMaxValue(30),
@@ -228,7 +230,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
| 'current';
const subcommand = interaction.options.getSubcommand(true) as
| 'league'
| 'neighbours';
| 'neighbors';
const limit = interaction.options.getInteger('limit') ?? 10;
const seasonOption = interaction.options.getString('season');
const season = seasonOption ? parseInt(seasonOption) : undefined;
@@ -257,11 +259,8 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
const regionSubdomain = regionToRegionSubdomain(region);
const result =
subcommandGroup === 'current'
? await fetch(
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/league/${leagueIndex}/top/`,
)
.then((response) => response.json() as Promise<LeagueTop>)
.then(async ({ result }) =>
? await getRatingsLeague(region, leagueIndex).then(
async (result) =>
result.slice(0, limit).map(
(player) =>
({
@@ -272,7 +271,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
nickname: player.nickname,
}) satisfies SimplifiedPlayer,
),
)
)
: await getArchivedRatingsLeaderboard(region, season!).then(
async (players) => {
const firstPlayerIndex =
@@ -338,29 +337,24 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
const accountInfo = await getAccountInfo(region, id);
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
const regionSubdomain = regionToRegionSubdomain(region);
const neighbors =
subcommandGroup === 'current'
? await fetch(
`https://${regionSubdomain}.wotblitz.com/en/api/rating-leaderboards/user/${id}/?neighbors=${Math.round(
limit / 2,
)}`,
? 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,
),
)
.then(
(response) => response.json() as Promise<RatingsNeighbors>,
)
.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.findIndex(
@@ -414,7 +408,7 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
titleName = neighbors
? accountInfo.nickname
: `No Ongoing Season - ${REGION_NAMES_SHORT[region]}`;
titleDescription = 'Ratings neighbours';
titleDescription = 'Ratings neighbors';
playersBefore = neighbors ? neighbors[0].position - 1 : 0;
playersAfter =
regionRatingsInfo.count - neighbors[neighbors.length - 1].position;
+9 -4
View File
@@ -1,4 +1,4 @@
import { LeagueTop } from '../../commands/ratings';
import { LeagueTop, RatingsPlayer } from '../../commands/ratings';
import { Region } from '../../constants/regions';
import { context } from '../blitzkrieg/context';
import { patientFetch } from '../blitzkrieg/patientFetch';
@@ -9,7 +9,10 @@ export async function getRatingsLeague(
league: number,
usePatientFetcher = false,
) {
const fetcher = usePatientFetcher ? patientFetch : fetch;
const fetcher = (url: string) =>
usePatientFetcher
? patientFetch(url, undefined, { cache: 'no-store' })
: fetch(url, { cache: 'no-store' });
const response = await fetcher(
context === 'website'
? `/api/${region}/ratings/current/league/${league}`
@@ -17,7 +20,9 @@ export async function getRatingsLeague(
region,
)}.wotblitz.com/en/api/rating-leaderboards/league/${league}/top/`,
);
const json = (await response.json()) as LeagueTop;
const json = await response.json();
return json;
return context === 'website'
? (json as RatingsPlayer[])
: (json as LeagueTop).result;
}
+4 -3
View File
@@ -10,9 +10,10 @@ export async function getRatingsNeighbors(
count: number,
usePatientFetcher = false,
) {
const fetcher = usePatientFetcher
? patientFetch
: (url: string) => fetch(url, { cache: 'no-store' });
const fetcher = (url: string) =>
usePatientFetcher
? patientFetch(url, undefined, { cache: 'no-store' })
: fetch(url, { cache: 'no-store' });
const response = await fetcher(
withCORSProxy(
`https://${regionToRegionSubdomain(
+23
View File
@@ -0,0 +1,23 @@
export async function patientFetchJSON<Type extends object>(
url: string,
timeout = 1000,
options?: Partial<{ cache: 'no-store' }>,
) {
let patientJSON: Type;
while (patientJSON! === undefined) {
try {
patientJSON = (await fetch(url, options).then(({ json }) =>
json(),
)) as Type;
} catch (error) {
console.warn(
`Failed to fetch ${url} as JSON; retrying in ${timeout}ms...`,
error,
);
await new Promise((resolve) => setTimeout(resolve, timeout));
}
}
return patientJSON;
}