diff --git a/docs/changelogs/1.14.3.md b/docs/changelogs/1.14.3.md
new file mode 100644
index 000000000..26a30b3e1
--- /dev/null
+++ b/docs/changelogs/1.14.3.md
@@ -0,0 +1,24 @@
+# Blitzkrieg 1.14.3
+
+Pull your brush out and start polishing.
+
+## Changes
+
+
+
+- Rows in `/breakdown` are now chunked into `8` per image
+ - This is true unless the number of pages exceeds `10` in which case the rows are even distributed across all `10` pages
+ - `10` is the maximum number of images that Discord allows
+- Short region names are used in username autocomplete instead of their full names
+ - Example: "North America" is "NA" now
+
+## Fixes
+
+- Removed reference to username inference in the username option description
+- Removed an unnecessary type `asd` that did absolutely nothing used nowhere
+
+## Technical changes
+
+- Image orders are now preserved even if processed in parallel and all completed out of order
+- `fetchBlitz` is now throttled to `10` requests per second
+ - Request overflows are managed by queueing
diff --git a/scripts/build.ts b/scripts/build.ts
index d94cdc7d7..9a6efefcc 100644
--- a/scripts/build.ts
+++ b/scripts/build.ts
@@ -27,7 +27,7 @@ readdir('src/workers').then((workers) => {
bundle: true,
sourcemap: true,
- minifyIdentifiers: false, // cause goofy errors
+ minifyIdentifiers: false, // causes goofy errors
minifySyntax: true,
minifyWhitespace: true,
diff --git a/src/app/embeds/session/page.tsx b/src/app/embeds/session/page.tsx
index 728f84710..0b75c1617 100644
--- a/src/app/embeds/session/page.tsx
+++ b/src/app/embeds/session/page.tsx
@@ -5,7 +5,7 @@ import { useEffect, useState } from 'react';
import * as Breakdown from '../../../components/Breakdown';
import { TreeTypeEnum } from '../../../components/Tanks';
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
-import fetchBlitz from '../../../core/blitz/fetchWargaming';
+import fetchBlitz from '../../../core/blitz/fetchBlitz';
import { tankAverages } from '../../../core/blitzstars/tankAverages';
import {
TankopediaEntry,
diff --git a/src/commands/breakdown.tsx b/src/commands/breakdown.tsx
index 477b158f4..bf17273f8 100644
--- a/src/commands/breakdown.tsx
+++ b/src/commands/breakdown.tsx
@@ -1,4 +1,5 @@
import { SlashCommandBuilder } from 'discord.js';
+import { chunk } from 'lodash';
import * as Breakdown from '../components/Breakdown';
import NoData, { NoDataType } from '../components/NoData';
import TitleBar from '../components/TitleBar';
@@ -35,6 +36,9 @@ import { StatFilters, filterStats } from '../core/statistics/filterStats';
import getWN8Percentile from '../core/statistics/getWN8Percentile';
import { CommandRegistryRaw } from '../events/interactionCreate';
+const ROWS_PER_PAGE = 8;
+const MAX_PAGES = 10;
+
async function render(
{ region, id }: ResolvedPlayer,
{ start, end, name }: ResolvedPeriod,
@@ -132,38 +136,80 @@ async function render(
0,
) / careerBattles;
- return (
-
-
+ const children: JSX.Element[] = [];
- {filteredOrder.length === 0 && (
-
- )}
- {filteredOrder.length > 0 && (
-
+ if (filteredOrder.length > 0) {
+ children.push(
+ ,
+ );
+
+ await Promise.all(
+ filteredOrder.map(async (id, index) => {
+ const tankopediaEntry = awaitedTankopedia[id];
+ const current = orderedCurrentStats[index];
+ const career = orderedCareerStats[index];
+ const currentWN8 = orderedCurrentWN8[index];
+ const careerWN8 = orderedCareerWN8[index];
+
+ children.push(
+ />,
+ );
+ }),
+ );
+ }
- {await Promise.all(
- filteredOrder.map(async (id, index) => {
- const tankopediaEntry = awaitedTankopedia[id];
- const current = orderedCurrentStats[index];
- const career = orderedCareerStats[index];
- const currentWN8 = orderedCurrentWN8[index];
- const careerWN8 = orderedCareerWN8[index];
-
- return (
-
- );
- }),
- )}
-
- )}
-
+ const pages = chunk(
+ children,
+ Math.max(Math.ceil(children.length / MAX_PAGES), ROWS_PER_PAGE),
);
+
+ if (filteredOrder.length > 0) {
+ return pages
+ .map((page, index) => (
+
+ {index === 0 && (
+
+ )}
+
+ {page}
+
+ ))
+ .reverse();
+ } else {
+ return [
+
+
+
+ {filteredOrder.length === 0 && (
+
+ )}
+ ,
+ ];
+ }
}
export const breakdownCommand = new Promise(
@@ -286,11 +316,11 @@ export const breakdownCommand = new Promise(
...filters,
});
- return Promise.all([
- render(player, period, filters),
+ return [
+ ...(await render(player, period, filters)),
buttonPrimary(path, 'Refresh'),
- getBlitzStarsLinkButton(player.region, player.id),
- ]);
+ await getBlitzStarsLinkButton(player.region, player.id),
+ ];
},
autocomplete: (interaction) => {
diff --git a/src/commands/searchClans.ts b/src/commands/searchClans.ts
index 8eb4bc83d..9b4860799 100644
--- a/src/commands/searchClans.ts
+++ b/src/commands/searchClans.ts
@@ -2,7 +2,7 @@ import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
import { REGION_NAMES, Region } from '../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
-import fetchBlitz from '../core/blitz/fetchWargaming';
+import fetchBlitz from '../core/blitz/fetchBlitz';
import addClanChoices from '../core/discord/addClanChoices';
import addRegionChoices from '../core/discord/addRegionChoices';
import embedInfo from '../core/discord/embedInfo';
diff --git a/src/commands/searchPlayers.ts b/src/commands/searchPlayers.ts
index 9a376d9c7..3d6037bcc 100644
--- a/src/commands/searchPlayers.ts
+++ b/src/commands/searchPlayers.ts
@@ -2,7 +2,7 @@ import { SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
import { REGION_NAMES, Region } from '../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
-import fetchBlitz from '../core/blitz/fetchWargaming';
+import fetchBlitz from '../core/blitz/fetchBlitz';
import {
AccountList,
usernamePattern,
diff --git a/src/constants/regions.ts b/src/constants/regions.ts
index 218394efe..ee4b04781 100644
--- a/src/constants/regions.ts
+++ b/src/constants/regions.ts
@@ -11,7 +11,7 @@ export const REGION_NAMES: Record = {
export const REGION_NAMES_SHORT: Record = {
com: 'NA',
eu: 'EU',
- asia: 'AS',
+ asia: 'ASIA',
};
export const REGION_SUBDOMAIN_NAMES: Record = {
diff --git a/src/core/blitz/fetchBlitz.ts b/src/core/blitz/fetchBlitz.ts
new file mode 100644
index 000000000..64f1ae26d
--- /dev/null
+++ b/src/core/blitz/fetchBlitz.ts
@@ -0,0 +1,52 @@
+export type BlitzResponse =
+ | {
+ status: 'error';
+ error: { field: string; message: string; code: 402; value: null };
+ }
+ | {
+ status: 'ok';
+ data: Data;
+ };
+
+const MAX_CALLS_PER_SECOND = 10;
+
+const queue: { url: string; resolve: (data: object) => void }[] = [];
+let inProgress = 0;
+
+async function manageQueue() {
+ if (queue.length > 0 && inProgress < MAX_CALLS_PER_SECOND) {
+ inProgress++;
+
+ setTimeout(() => {
+ inProgress--;
+ manageQueue();
+ }, 1000);
+
+ const { url, resolve } = queue.shift()!;
+ const data = (await fetch(url)
+ .then((response) => response.json())
+ .catch((error) => ({ status: 'error', error }))) as BlitzResponse<
+ typeof resolve
+ >;
+
+ if (data.status === 'ok') {
+ resolve(data.data);
+ } else {
+ throw new Error(`Wargaming response error status:"${data.status}"`, {
+ cause: `Message: "${data.error.message}"\nURL: "${url}"`,
+ });
+ }
+ }
+}
+
+export default function fetchBlitz(url: string) {
+ return new Promise((resolve) => {
+ queue.push({
+ url,
+ resolve(data) {
+ resolve(data as Data);
+ },
+ });
+ manageQueue();
+ });
+}
diff --git a/src/core/blitz/fetchWargaming.ts b/src/core/blitz/fetchWargaming.ts
deleted file mode 100644
index a17c98d5c..000000000
--- a/src/core/blitz/fetchWargaming.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-export type BlitzResponse =
- | {
- status: 'error';
- error: { field: string; message: string; code: 402; value: null };
- }
- | {
- status: 'ok';
- data: Data;
- };
-
-export default async function fetchBlitz(url: string) {
- const data = (await fetch(url).then((response) =>
- response.json(),
- )) as BlitzResponse;
-
- if (data.status === 'ok') {
- return data.data;
- } else {
- throw new Error(`Wargaming response error status:"${data.status}"`, {
- cause: `Message: "${data.error.message}"\nURL: "${url}"`,
- });
- }
-}
diff --git a/src/core/blitz/getAccountAchievements.ts b/src/core/blitz/getAccountAchievements.ts
index edb21ca14..98b7c9cfd 100644
--- a/src/core/blitz/getAccountAchievements.ts
+++ b/src/core/blitz/getAccountAchievements.ts
@@ -1,6 +1,6 @@
import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
-import fetchBlitz from './fetchWargaming';
+import fetchBlitz from './fetchBlitz';
import { normalizeIds } from './normalizeIds';
interface IndividualAccountAchievements {
diff --git a/src/core/blitz/getAccountInfo.ts b/src/core/blitz/getAccountInfo.ts
index 5852ea073..efb795c41 100644
--- a/src/core/blitz/getAccountInfo.ts
+++ b/src/core/blitz/getAccountInfo.ts
@@ -1,6 +1,6 @@
import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
-import fetchBlitz from './fetchWargaming';
+import fetchBlitz from './fetchBlitz';
import { normalizeIds } from './normalizeIds';
export interface AllStats {
diff --git a/src/core/blitz/getClanAccountInfo.ts b/src/core/blitz/getClanAccountInfo.ts
index 6c36fc15f..ffb334ca2 100644
--- a/src/core/blitz/getClanAccountInfo.ts
+++ b/src/core/blitz/getClanAccountInfo.ts
@@ -1,6 +1,6 @@
import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
-import fetchBlitz from './fetchWargaming';
+import fetchBlitz from './fetchBlitz';
import { normalizeExtra } from './normalizeExtra';
import { normalizeIds } from './normalizeIds';
diff --git a/src/core/blitz/getClanInfo.ts b/src/core/blitz/getClanInfo.ts
index 702ebea84..85cfa154f 100644
--- a/src/core/blitz/getClanInfo.ts
+++ b/src/core/blitz/getClanInfo.ts
@@ -1,6 +1,6 @@
import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
-import fetchBlitz from './fetchWargaming';
+import fetchBlitz from './fetchBlitz';
import { normalizeIds } from './normalizeIds';
export interface IndividualClanInfo {
diff --git a/src/core/blitz/getTankStats.ts b/src/core/blitz/getTankStats.ts
index acb9fcc39..4965c43d7 100644
--- a/src/core/blitz/getTankStats.ts
+++ b/src/core/blitz/getTankStats.ts
@@ -2,7 +2,7 @@ import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import { TanksStats } from '../../types/tanksStats';
import { UserError } from '../blitzkrieg/userError';
-import fetchBlitz from './fetchWargaming';
+import fetchBlitz from './fetchBlitz';
export default async function getTankStats(region: Region, id: number) {
const tankStats = await fetchBlitz(
diff --git a/src/core/blitz/searchClansAcrossRegions.ts b/src/core/blitz/searchClansAcrossRegions.ts
index 1e62aa981..98b4cfabb 100644
--- a/src/core/blitz/searchClansAcrossRegions.ts
+++ b/src/core/blitz/searchClansAcrossRegions.ts
@@ -1,7 +1,7 @@
import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
import { Clan, ClanList } from '../../types/clanList';
-import fetchBlitz from './fetchWargaming';
+import fetchBlitz from './fetchBlitz';
export type ClanListWithRegion = (Clan & {
region: 'com' | 'eu' | 'asia';
diff --git a/src/core/blitz/searchPlayersAcrossRegions.ts b/src/core/blitz/searchPlayersAcrossRegions.ts
index b24315ee1..e8a12a17a 100644
--- a/src/core/blitz/searchPlayersAcrossRegions.ts
+++ b/src/core/blitz/searchPlayersAcrossRegions.ts
@@ -1,6 +1,6 @@
import { Region } from '../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
-import fetchBlitz from './fetchWargaming';
+import fetchBlitz from './fetchBlitz';
interface Account {
nickname: string;
diff --git a/src/core/blitzstars/tankopedia.ts b/src/core/blitzstars/tankopedia.ts
index 7be335929..a21182b0e 100644
--- a/src/core/blitzstars/tankopedia.ts
+++ b/src/core/blitzstars/tankopedia.ts
@@ -1,7 +1,6 @@
import { deburr } from 'lodash';
-import { TreeTypeString } from '../../components/Tanks';
import { WARGAMING_APPLICATION_ID } from '../../constants/wargamingApplicationID';
-import fetchBlitz from '../blitz/fetchWargaming';
+import fetchBlitz from '../blitz/fetchBlitz';
import { context } from '../blitzkrieg/context';
export interface TankopediaEntry {
@@ -16,7 +15,6 @@ export interface TankopediaEntry {
type: string;
description: string;
}
-type asd = TreeTypeString;
export interface Tankopedia {
[id: number]: TankopediaEntry | undefined;
}
diff --git a/src/core/discord/addUsernameChoices.ts b/src/core/discord/addUsernameChoices.ts
index cc089f368..77b19936d 100644
--- a/src/core/discord/addUsernameChoices.ts
+++ b/src/core/discord/addUsernameChoices.ts
@@ -3,8 +3,6 @@ import { SlashCommandStringOption } from 'discord.js';
export default function addUsernameChoices(option: SlashCommandStringOption) {
return option
.setName('username')
- .setDescription(
- 'The username you use in Blitz (inferred from Discord nickname by default)',
- )
+ .setDescription('The username you use in Blitz')
.setAutocomplete(true);
}
diff --git a/src/core/discord/autocompleteUsername.ts b/src/core/discord/autocompleteUsername.ts
index 8d5428883..5160a2024 100644
--- a/src/core/discord/autocompleteUsername.ts
+++ b/src/core/discord/autocompleteUsername.ts
@@ -3,7 +3,7 @@ import {
AutocompleteInteraction,
CacheType,
} from 'discord.js';
-import { REGION_NAMES } from '../../constants/regions';
+import { REGION_NAMES_SHORT } from '../../constants/regions';
import searchPlayersAcrossRegions from '../blitz/searchPlayersAcrossRegions';
export default async function autocompleteUsername(
@@ -19,7 +19,9 @@ export default async function autocompleteUsername(
? players.map(
(player) =>
({
- name: `${player.nickname} (${REGION_NAMES[player.region]})`,
+ name: `${player.nickname} (${
+ REGION_NAMES_SHORT[player.region]
+ })`,
value: `${player.region}/${player.account_id}`,
}) satisfies ApplicationCommandOptionChoiceData,
)
diff --git a/src/core/discord/normalizeInteractionReturnable.ts b/src/core/discord/normalizeInteractionReturnable.ts
index cdb0e2208..5642f5564 100644
--- a/src/core/discord/normalizeInteractionReturnable.ts
+++ b/src/core/discord/normalizeInteractionReturnable.ts
@@ -11,6 +11,7 @@ import jsxToPngThreaded from '../blitzkrieg/jsxToPngThreaded';
export default async function normalizeInteractionReturnable(
returnable: InteractionReturnable,
) {
+ const images: [number, Buffer][] = [];
const reply: InteractionEditReplyOptions = {};
const awaitedReturnable = await returnable;
@@ -19,7 +20,7 @@ export default async function normalizeInteractionReturnable(
: [awaitedReturnable];
await Promise.all(
- normalizedReturnable.map(async (item) => {
+ normalizedReturnable.map(async (item, index) => {
if (typeof item === 'string') {
reply.content = item;
} else if (item instanceof EmbedBuilder) {
@@ -38,11 +39,16 @@ export default async function normalizeInteractionReturnable(
return;
} else {
const image = await jsxToPngThreaded(item);
- if (!reply.files) reply.files = [];
- reply.files.push(image);
+ images.push([index, image]);
}
}),
);
+ if (images.length > 0) {
+ images.sort(([a], [b]) => b - a);
+ if (!reply.files) reply.files = [];
+ images.forEach(([, image]) => reply.files?.push(image));
+ }
+
return reply;
}
diff --git a/src/stores/session.ts b/src/stores/session.ts
index 9c125f933..fd3786189 100644
--- a/src/stores/session.ts
+++ b/src/stores/session.ts
@@ -4,7 +4,7 @@ import { devtools, persist } from 'zustand/middleware';
import { CustomColumnDisplay } from '../app/tools/session/components/CustomColumn';
import { Region } from '../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID';
-import fetchBlitz from '../core/blitz/fetchWargaming';
+import fetchBlitz from '../core/blitz/fetchBlitz';
import { NormalizedTankStats, TanksStats } from '../types/tanksStats';
type Session = (