From c804a3d56419e5ef955e6e9d75da684659433d69 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 19 Jan 2024 09:53:42 -0600
Subject: [PATCH 01/11] feat: update version number
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index d7d12bb84..59c5e31fd 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "blitzkrieg",
- "version": "1.15.9",
+ "version": "1.15.10",
"description": "🎉 Tools for everything World of Tanks Blitz",
"main": "src/server.ts",
"type": "module",
From 5ca9e29f3d85ab21a4231ad90015a5fd23bff5a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 19 Jan 2024 15:22:53 -0600
Subject: [PATCH 02/11] fix: percentile indicator contrast
---
.../AllStatsOverview/components/WN8Display/index.tsx | 4 ++--
src/components/PercentileIndicator.tsx | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/components/AllStatsOverview/components/WN8Display/index.tsx b/src/components/AllStatsOverview/components/WN8Display/index.tsx
index 6e8fe8aba..3e33af43b 100644
--- a/src/components/AllStatsOverview/components/WN8Display/index.tsx
+++ b/src/components/AllStatsOverview/components/WN8Display/index.tsx
@@ -37,7 +37,7 @@ export function WN8Display({ WN8 }: WN8DisplayProps) {
gap: -16,
}}
>
-
+
-
+
);
}
diff --git a/src/components/PercentileIndicator.tsx b/src/components/PercentileIndicator.tsx
index 548a46039..25fe22e6c 100644
--- a/src/components/PercentileIndicator.tsx
+++ b/src/components/PercentileIndicator.tsx
@@ -1,4 +1,5 @@
import {
+ amber,
blue,
green,
lime,
@@ -8,7 +9,6 @@ import {
red,
slate,
teal,
- yellow,
} from '@radix-ui/colors';
import { Percentile } from '../constants/percentiles';
@@ -20,8 +20,8 @@ export const PERCENTILE_COLORS = {
[Percentile.VeryBad]: slate.slate9,
[Percentile.Bad]: red.red9,
[Percentile.BelowAverage]: orange.orange9,
- [Percentile.Average]: yellow.yellow9,
- [Percentile.AboveAverage]: lime.lime9,
+ [Percentile.Average]: amber.amber10,
+ [Percentile.AboveAverage]: lime.lime8,
[Percentile.Good]: green.green9,
[Percentile.VeryGood]: teal.teal9,
[Percentile.Great]: blue.blue9,
From ff9bda29cf06822535e383d7bac295073905e8d8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 19 Jan 2024 15:28:02 -0600
Subject: [PATCH 03/11] fix: archive league neighbors
---
src/commands/ratings.tsx | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx
index f807ef7ff..197a55c21 100644
--- a/src/commands/ratings.tsx
+++ b/src/commands/ratings.tsx
@@ -384,8 +384,10 @@ export const ratingsCommand = new Promise(
id: player.id,
score: player.score,
position: playerIndex + index + 1,
- clan: clanData[player.id]!.clan?.tag,
- nickname: clanData[player.id]!.account_name,
+ clan: clanData[index]?.clan?.tag,
+ nickname: clanData[index]
+ ? clanData[index]!.account_name
+ : `Deleted ${player.id}`,
}));
},
);
From 890a4d142b3fd8f8bfad02e929e57bd8453b3d9a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 19 Jan 2024 16:02:03 -0600
Subject: [PATCH 04/11] feat: simplified /ratings command
---
src/commands/ratings.tsx | 359 +++++++++++------------
src/core/blitz/getRatingsInfo.ts | 9 +-
src/core/blitz/isOnGoingRatingsSeason.ts | 6 +
3 files changed, 180 insertions(+), 194 deletions(-)
create mode 100644 src/core/blitz/isOnGoingRatingsSeason.ts
diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx
index 197a55c21..68ab00559 100644
--- a/src/commands/ratings.tsx
+++ b/src/commands/ratings.tsx
@@ -2,7 +2,6 @@ import {
APIApplicationCommandOptionChoice,
SlashCommandBuilder,
SlashCommandSubcommandBuilder,
- SlashCommandSubcommandGroupBuilder,
} from 'discord.js';
import { range } from 'lodash';
import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard';
@@ -17,13 +16,13 @@ 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 { UserError } from '../core/blitzkrieg/userError';
import addRegionChoices from '../core/discord/addRegionChoices';
-import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedNegative from '../core/discord/embedNegative';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
@@ -116,56 +115,6 @@ export interface RatingsNeighbors {
neighbors: RatingsPlayer[];
}
-function addSubcommands(
- option: SlashCommandSubcommandGroupBuilder,
- addOptions = (option: SlashCommandSubcommandBuilder) => option,
-) {
- return option
- .addSubcommand((option) =>
- addOptions(
- option
- .setName('neighbors')
- .setDescription('Your position and neighbors'),
- )
- .addStringOption(addUsernameChoices)
- .addIntegerOption((option) =>
- option
- .setName('limit')
- .setDescription('How many neighbors to display (default: 10)')
- .setRequired(false)
- .setMinValue(5)
- .setMaxValue(30),
- ),
- )
- .addSubcommand((option) =>
- addOptions(option.setName('league').setDescription('Top league players'))
- .addStringOption((option) =>
- option
- .setName('league')
- .setDescription('The league to display')
- .addChoices(
- ...LEAGUES.map(
- ({ name }, value) =>
- ({
- name,
- value: `${value}`,
- }) satisfies APIApplicationCommandOptionChoice,
- ),
- )
- .setRequired(true),
- )
- .addStringOption(addRegionChoices)
- .addIntegerOption((option) =>
- option
- .setName('limit')
- .setDescription('How many rows to display (default: 10)')
- .setRequired(false)
- .setMinValue(5)
- .setMaxValue(30),
- ),
- );
-}
-
const noOngoingSeason = embedNegative(
'No ongoing season',
"Wargaming didn't provide any data for this season.",
@@ -174,38 +123,72 @@ const noOngoingSeason = embedNegative(
export const ratingsCommand = new Promise(
async (resolve) => {
const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber();
- const seasonChoices = range(
+ const onGoingSeason = await isOnGoingRatingsSeason();
+ const seasonNumbers = range(
FIRST_ARCHIVED_RATINGS_SEASON,
- latestArchivedSeasonNumber + 1,
- ).map(
- (number) =>
- ({
- name: `Season ${number}`,
- value: `${number}`,
- }) satisfies APIApplicationCommandOptionChoice,
+ latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1,
);
+ function addSubcommands(option: SlashCommandSubcommandBuilder) {
+ return option
+ .addIntegerOption((option) =>
+ option
+ .setName('limit')
+ .setDescription('How many neighbors to display (default: 10)')
+ .setRequired(false)
+ .setMinValue(5)
+ .setMaxValue(30),
+ )
+ .addStringOption((option) =>
+ option
+ .setName('season')
+ .setDescription('Season number')
+ .setRequired(false)
+ .addChoices(
+ ...seasonNumbers
+ .map(
+ (number, index) =>
+ ({
+ name: `${number}${index === seasonNumbers.length - 1 && onGoingSeason ? ' (current)' : ''}`,
+ value: `${number}`,
+ }) satisfies APIApplicationCommandOptionChoice,
+ )
+ .reverse(),
+ ),
+ );
+ }
+
const command = new SlashCommandBuilder()
.setName('ratings')
.setDescription('Ratings battles statistics')
- .addSubcommandGroup((option) =>
- addSubcommands(
- option.setName('archive').setDescription('Any previous season'),
- (option) =>
- option.addStringOption((option) =>
- option
- .setName('season')
- .setDescription('The season number')
- .setChoices(...seasonChoices)
- .setRequired(true),
- ),
- ),
- )
- .addSubcommandGroup((option) =>
+ .addSubcommand((option) =>
addSubcommands(
option
- .setName('current')
- .setDescription('The current running Ratings season'),
+ .setName('neighbors')
+ .setDescription('Shows neighboring players on the leaderboard'),
+ ),
+ )
+ .addSubcommand((option) =>
+ addSubcommands(
+ option
+ .setName('league')
+ .setDescription('Shows top players from a league')
+ .addStringOption(addRegionChoices)
+ .addStringOption((option) =>
+ option
+ .setName('league')
+ .setDescription('The league to display')
+ .addChoices(
+ ...LEAGUES.map(
+ ({ name }, value) =>
+ ({
+ name,
+ value: `${value}`,
+ }) satisfies APIApplicationCommandOptionChoice,
+ ),
+ )
+ .setRequired(true),
+ ),
),
);
@@ -224,18 +207,19 @@ export const ratingsCommand = new Promise(
nickname: string;
}
- const subcommandGroup = interaction.options.getSubcommandGroup(true) as
- | 'archive'
- | 'current';
const subcommand = interaction.options.getSubcommand(true) as
| 'league'
| 'neighbors';
const limit = interaction.options.getInteger('limit') ?? 10;
const seasonOption = interaction.options.getString('season');
- const season = seasonOption ? parseInt(seasonOption) : undefined;
+ const season = seasonOption
+ ? parseInt(seasonOption)
+ : onGoingSeason
+ ? latestArchivedSeasonNumber + 1
+ : latestArchivedSeasonNumber;
+ const isCurrentSeason = season === latestArchivedSeasonNumber + 1;
let titleName: string;
let titleImage: string | undefined;
- let titleNameDiscriminator: string | undefined;
let titleDescription: string;
let playersBefore: number;
let playersAfter: number;
@@ -249,69 +233,66 @@ export const ratingsCommand = new Promise(
interaction.options.getString('league')!,
);
const region = interaction.options.getString('region') as Region;
- regionRatingsInfo = await (subcommandGroup === 'current'
+ regionRatingsInfo = await (isCurrentSeason
? getRatingsInfo(region)
: getArchivedRatingsInfo(region, season!));
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
- const result =
- subcommandGroup === 'current'
- ? 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.findIndex(
- (player) =>
- player.score < LEAGUES[leagueIndex - 1].minScore,
- );
- const lastPlayerIndex = firstPlayerIndex + limit - 1;
- const trimmed = players.slice(
- firstPlayerIndex,
- lastPlayerIndex + 1,
- );
- const clanData = await getClanAccountInfo(
- region,
- trimmed.map(({ id }) => id),
- ['clan'],
- );
+ 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.findIndex(
+ (player) =>
+ player.score < LEAGUES[leagueIndex - 1].minScore,
+ );
+ const lastPlayerIndex = firstPlayerIndex + limit - 1;
+ const trimmed = players.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 ??
- `Deleted ${player.id}`,
- }) satisfies SimplifiedPlayer,
- );
- },
- );
+ 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 ??
+ `Deleted ${player.id}`,
+ }) satisfies SimplifiedPlayer,
+ );
+ },
+ );
const leagueInfo = regionRatingsInfo.leagues[leagueIndex];
- midnightLeaderboard =
- subcommandGroup === 'current'
- ? await getArchivedRatingsMidnightLeaderboard(
- region,
- regionRatingsInfo.current_season,
- )
- : await getArchivedRatingsMidnightLeaderboard(region, season!);
+ midnightLeaderboard = isCurrentSeason
+ ? await getArchivedRatingsMidnightLeaderboard(
+ region,
+ regionRatingsInfo.current_season,
+ )
+ : await getArchivedRatingsMidnightLeaderboard(region, season!);
players = result;
playersBefore = result ? result[0].position - 1 : 0;
@@ -326,22 +307,17 @@ export const ratingsCommand = new Promise(
titleDescription = `Top ${limit} players`;
} else {
const { region, id } = await resolvePlayerFromCommand(interaction);
- regionRatingsInfo =
- subcommandGroup === 'current'
- ? await getRatingsInfo(region)
- : await getArchivedRatingsInfo(region, season!);
+ regionRatingsInfo = isCurrentSeason
+ ? await getRatingsInfo(region)
+ : await getArchivedRatingsInfo(region, season!);
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
const accountInfo = await getAccountInfo(region, id);
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
- const neighbors =
- subcommandGroup === 'current'
- ? await getRatingsNeighbors(
- region,
- id,
- Math.round(limit / 2),
- ).then((players) =>
+ const neighbors = isCurrentSeason
+ ? await getRatingsNeighbors(region, id, Math.round(limit / 2)).then(
+ (players) =>
players.neighbors.map(
(player) =>
({
@@ -352,54 +328,51 @@ export const ratingsCommand = new Promise(
nickname: player.nickname,
}) satisfies SimplifiedPlayer,
),
- )
- : await getArchivedRatingsLeaderboard(region, season!).then(
- async (players) => {
- const playerIndex = players.findIndex(
- (player) => player.id === id,
+ )
+ : await getArchivedRatingsLeaderboard(region, season!).then(
+ async (players) => {
+ const playerIndex = players.findIndex(
+ (player) => player.id === id,
+ );
+
+ if (playerIndex === -1) {
+ throw new UserError(
+ `${accountInfo.nickname} didn't player ratings in season ${season}`,
+ {
+ cause:
+ 'This player did not participate in this season or did not get past calibration.',
+ },
);
+ }
- if (playerIndex === -1) {
- throw new UserError(
- `${accountInfo.nickname} didn't player ratings in season ${season}`,
- {
- cause:
- 'This player did not participate in this season or did not get past calibration.',
- },
- );
- }
+ const halfRange = Math.round(limit / 2);
+ const trimmed = players.slice(
+ playerIndex - halfRange,
+ playerIndex + halfRange + 1,
+ );
+ const clanData = await getClanAccountInfo(
+ region,
+ trimmed.map((player) => player.id),
+ ['clan'],
+ );
- const halfRange = Math.round(limit / 2);
- const trimmed = players.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 =
- subcommandGroup === 'current'
- ? await getArchivedRatingsMidnightLeaderboard(
- region,
- regionRatingsInfo.current_season,
- )
- : await getArchivedRatingsMidnightLeaderboard(region, season!);
-
- if (clan && neighbors) titleNameDiscriminator = `[${clan.tag}]`;
+ 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
diff --git a/src/core/blitz/getRatingsInfo.ts b/src/core/blitz/getRatingsInfo.ts
index 6c77df9f7..ef1616f12 100644
--- a/src/core/blitz/getRatingsInfo.ts
+++ b/src/core/blitz/getRatingsInfo.ts
@@ -4,7 +4,11 @@ import { context } from '../blitzkrieg/context';
import { patientFetch } from '../blitzkrieg/patientFetch';
import regionToRegionSubdomain from './regionToRegionSubdomain';
+const ratingsInfoCache: Partial> = {};
+
export default async function getRatingsInfo(region: Region) {
+ if (ratingsInfoCache[region]) return ratingsInfoCache[region]!;
+
const response = await patientFetch(
context === 'website'
? `/api/${region}/ratings/current/info`
@@ -14,6 +18,9 @@ export default async function getRatingsInfo(region: Region) {
undefined,
{ cache: 'no-store' },
);
+ const data = (await response.json()) as RatingsInfo;
- return (await response.json()) as Promise;
+ ratingsInfoCache[region] = data;
+
+ return data;
}
diff --git a/src/core/blitz/isOnGoingRatingsSeason.ts b/src/core/blitz/isOnGoingRatingsSeason.ts
new file mode 100644
index 000000000..ef6f1e2d5
--- /dev/null
+++ b/src/core/blitz/isOnGoingRatingsSeason.ts
@@ -0,0 +1,6 @@
+import getRatingsInfo from './getRatingsInfo';
+
+export async function isOnGoingRatingsSeason() {
+ const ratingsInfo = await getRatingsInfo('com');
+ return ratingsInfo.detail === undefined;
+}
From 4c746cbb864bd6967ea634aac7f4fb27658c426f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 19 Jan 2024 16:07:15 -0600
Subject: [PATCH 05/11] feat: improved error messages
---
src/commands/ratings.tsx | 7 ++-----
src/core/blitz/getTankStats.ts | 8 ++++----
src/core/blitz/resolveTankId.ts | 14 ++++++++------
src/core/blitzkrieg/userError.ts | 7 ++++++-
src/core/discord/resolveClanFromCommand.ts | 7 ++++---
src/core/discord/resolvePlayerFromCommand.ts | 14 ++++++++------
6 files changed, 32 insertions(+), 25 deletions(-)
diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx
index 68ab00559..e238e2063 100644
--- a/src/commands/ratings.tsx
+++ b/src/commands/ratings.tsx
@@ -337,11 +337,8 @@ export const ratingsCommand = new Promise(
if (playerIndex === -1) {
throw new UserError(
- `${accountInfo.nickname} didn't player ratings in season ${season}`,
- {
- cause:
- 'This player did not participate in this season or did not get past calibration.',
- },
+ `${accountInfo.nickname} didn't play ratings in season ${season}`,
+ 'This player did not participate in this season or did not get past calibration.',
);
}
diff --git a/src/core/blitz/getTankStats.ts b/src/core/blitz/getTankStats.ts
index 4965c43d7..763d720cf 100644
--- a/src/core/blitz/getTankStats.ts
+++ b/src/core/blitz/getTankStats.ts
@@ -10,10 +10,10 @@ export default async function getTankStats(region: Region, id: number) {
);
if (tankStats[id] === null) {
- throw new UserError('No tank stats available', {
- cause:
- "This player doesn't have any stats for tanks available. This may not be the player you are looking for.",
- });
+ throw new UserError(
+ 'No tank stats available',
+ "This player doesn't have any stats available. This may not be the player you are looking for.",
+ );
}
return tankStats[id];
diff --git a/src/core/blitz/resolveTankId.ts b/src/core/blitz/resolveTankId.ts
index c110ee3a2..813f5e51a 100644
--- a/src/core/blitz/resolveTankId.ts
+++ b/src/core/blitz/resolveTankId.ts
@@ -15,9 +15,10 @@ export default async function resolveTankId(tank: string | number) {
});
if (searchResult.length === 0) {
- throw new UserError('Tank not found', {
- cause: `Could not find tank by the name "${tank}".`,
- });
+ throw new UserError(
+ 'Tank not found',
+ `Could not find tank by the name "${tank}".`,
+ );
} else {
return searchResult[0].obj.id;
}
@@ -25,9 +26,10 @@ export default async function resolveTankId(tank: string | number) {
if ((await tankDefinitions)[number]) {
return number;
} else {
- throw new UserError('Tank not found', {
- cause: `Could not find tank by the ID "${number}".`,
- });
+ throw new UserError(
+ 'Tank not found',
+ `Could not find tank by the ID "${number}". Try typing the name of the tank instead of a number.`,
+ );
}
}
}
diff --git a/src/core/blitzkrieg/userError.ts b/src/core/blitzkrieg/userError.ts
index 06b0ee65c..e4eb7cc27 100644
--- a/src/core/blitzkrieg/userError.ts
+++ b/src/core/blitzkrieg/userError.ts
@@ -1,4 +1,9 @@
/**
* Discriminator for user caused errors to not report in logs
*/
-export class UserError extends Error {}
+export class UserError {
+ constructor(
+ public message: string,
+ public cause: string,
+ ) {}
+}
diff --git a/src/core/discord/resolveClanFromCommand.ts b/src/core/discord/resolveClanFromCommand.ts
index a43bb78fc..f332ca22e 100644
--- a/src/core/discord/resolveClanFromCommand.ts
+++ b/src/core/discord/resolveClanFromCommand.ts
@@ -18,9 +18,10 @@ export default async function resolveClanFromCommand(
if (accounts[0]) {
return { region: accounts[0].region, id: accounts[0].clan_id };
} else {
- throw new UserError('Could not find clan', {
- cause: `I couldn't find clan \`${clan}\`. Try selecting a username from the search result.`,
- });
+ throw new UserError(
+ 'Could not find clan',
+ `\`${clan}\` was not found. Try selecting a clan from the search result.`,
+ );
}
}
}
diff --git a/src/core/discord/resolvePlayerFromCommand.ts b/src/core/discord/resolvePlayerFromCommand.ts
index a766f2161..21ca22347 100644
--- a/src/core/discord/resolvePlayerFromCommand.ts
+++ b/src/core/discord/resolvePlayerFromCommand.ts
@@ -34,11 +34,12 @@ export default async function resolvePlayerFromCommand(
id: accounts[0].account_id,
} satisfies ResolvedPlayer;
} else {
- throw new UserError('Could not find user', {
- cause: `I couldn't find user "${markdownEscape(
+ throw new UserError(
+ 'Could not find user',
+ `I couldn't find user "${markdownEscape(
commandUsername,
)}". Try picking an user from the search result, typing in a valid username, or using the \`/link\` command.`,
- });
+ );
}
}
} else {
@@ -47,9 +48,10 @@ export default async function resolvePlayerFromCommand(
if (account) {
return { region: account.region, id: account.blitz };
} else {
- throw new UserError('Use the `/link` command', {
- cause: 'Link your Blitz and Discord accounts to get started.',
- });
+ throw new UserError(
+ "You're account isn't linked with Blitzkrieg yet",
+ 'Use the `/link` command to get started.', // TODO: add shortcut button too
+ );
}
}
}
From ae07b8de4cd2eaeb5bed5de2a630a751fc278f06 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 19 Jan 2024 20:47:46 -0600
Subject: [PATCH 06/11] feat: center align text when no image
---
src/components/Tanks/components/Item.tsx | 1 -
src/components/TitleBar.tsx | 20 +++++++++++++++++---
src/core/discord/resolvePlayerFromCommand.ts | 2 +-
3 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/src/components/Tanks/components/Item.tsx b/src/components/Tanks/components/Item.tsx
index 4363e0a80..4bf8766d1 100644
--- a/src/components/Tanks/components/Item.tsx
+++ b/src/components/Tanks/components/Item.tsx
@@ -18,7 +18,6 @@ export const TANK_TYPES = [
'heavy',
] as const;
-// TODO: remove hyphen for underscore
export const TREE_TYPES = ['researchable', 'premium', 'collector'] as const;
export type TankType = (typeof TANK_TYPES)[number];
diff --git a/src/components/TitleBar.tsx b/src/components/TitleBar.tsx
index b3e4da675..7449da129 100644
--- a/src/components/TitleBar.tsx
+++ b/src/components/TitleBar.tsx
@@ -36,6 +36,8 @@ export default function TitleBar({ name, image, description }: TitleBarProps) {
style={{
display: 'flex',
gap: 4,
+ alignItems: 'center',
+ justifyContent: 'center',
}}
>
-
- {description}
-
+
+
+ {description}
+
+
diff --git a/src/core/discord/resolvePlayerFromCommand.ts b/src/core/discord/resolvePlayerFromCommand.ts
index 21ca22347..fa6d156d1 100644
--- a/src/core/discord/resolvePlayerFromCommand.ts
+++ b/src/core/discord/resolvePlayerFromCommand.ts
@@ -50,7 +50,7 @@ export default async function resolvePlayerFromCommand(
} else {
throw new UserError(
"You're account isn't linked with Blitzkrieg yet",
- 'Use the `/link` command to get started.', // TODO: add shortcut button too
+ 'Use the `/link` command to get started.',
);
}
}
From 0480445bae93281e5afcfabe6dac033918b9f517 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 19 Jan 2024 20:53:27 -0600
Subject: [PATCH 07/11] feat: easier /link command
---
src/commands/link.tsx | 294 ++++++++++++++++++++----------------------
1 file changed, 140 insertions(+), 154 deletions(-)
diff --git a/src/commands/link.tsx b/src/commands/link.tsx
index 9db82aaae..1e44da607 100644
--- a/src/commands/link.tsx
+++ b/src/commands/link.tsx
@@ -1,25 +1,21 @@
import { teal } from '@radix-ui/colors';
-import {
- AttachmentBuilder,
- GuildMemberRoleManager,
- SlashCommandBuilder,
-} from 'discord.js';
+import { GuildMemberRoleManager, SlashCommandBuilder } from 'discord.js';
import markdownEscape from 'markdown-escape';
import discord from '../../discord.json' assert { type: 'json' };
import { Glow } from '../components/AllStatsOverview/components/WN8Display/components/Glow';
import Wrapper from '../components/Wrapper';
-import { REGION_NAMES_SHORT, Region } from '../constants/regions';
+import { REGION_NAMES_SHORT } from '../constants/regions';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import { linkBlitzAndDiscord } from '../core/blitzkrieg/discordBlitz';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
+import embedInfo from '../core/discord/embedInfo';
import embedNegative from '../core/discord/embedNegative';
+import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
import { CommandRegistry } from '../events/interactionCreate';
import { theme } from '../stitches.config';
-const serverAndIdPattern = /(com|eu|asia)\/[0-9]+/;
-
export const verifyCommand: CommandRegistry = {
inProduction: true,
inPublic: true,
@@ -30,193 +26,183 @@ export const verifyCommand: CommandRegistry = {
.addStringOption((option) => addUsernameChoices(option).setRequired(true)),
async handler(interaction) {
- const username = interaction.options.getString('username', true);
+ const { id, region } = await resolvePlayerFromCommand(interaction);
+ const discordId = parseInt(interaction.user.id);
+ const accountInfo = await getAccountInfo(region, id);
+ const clanAccountInfo = await getClanAccountInfo(region, id, ['clan']);
- if (serverAndIdPattern.test(username)) {
- const [region, idString] = username.split('/');
- const id = parseInt(idString);
- const discordId = parseInt(interaction.user.id);
- const accountInfo = await getAccountInfo(region as Region, id);
- const clanAccountInfo = await getClanAccountInfo(region as Region, id, [
- 'clan',
- ]);
+ await linkBlitzAndDiscord(discordId, region, id);
- await linkBlitzAndDiscord(discordId, region as Region, id);
-
- if (interaction.guildId === discord.sklld_guild_id) {
- if (!interaction.guild?.members.me?.permissions.has('ManageRoles')) {
- return embedNegative(
- `${markdownEscape(interaction.user.username)} failed to verify`,
- "I don't have the permission to change your manage roles.",
- );
- }
-
- await (interaction.member!.roles as GuildMemberRoleManager).remove(
- discord.sklld_verify_role,
- );
- await (interaction.member!.roles as GuildMemberRoleManager).add(
- discord.sklld_peasant_role,
+ if (interaction.guildId === discord.sklld_guild_id) {
+ if (!interaction.guild?.members.me?.permissions.has('ManageRoles')) {
+ return embedNegative(
+ `${markdownEscape(interaction.user.username)} failed to verify`,
+ "I don't have the permission to change your manage roles.",
);
}
- return (
-
+ await (interaction.member!.roles as GuildMemberRoleManager).remove(
+ discord.sklld_verify_role,
+ );
+ await (interaction.member!.roles as GuildMemberRoleManager).add(
+ discord.sklld_peasant_role,
+ );
+ }
+
+ return [
+
+
+
+
-
+
+

+
+
+
+ {interaction.user.displayName}
+
+
+ @{interaction.user.username}
+
+
+
-
+ />
+ Accounts linked
+
+
+
+ {clanAccountInfo?.clan ? (

-
-
-
- {interaction.user.displayName}
-
-
- @{interaction.user.username}
-
-
-
-
+ ) : null}
-

-
Accounts linked
-
-
-
- {clanAccountInfo?.clan ? (
-

- ) : null}
-
-
- {accountInfo.nickname}
-
-
- {clanAccountInfo?.clan
- ? `[${clanAccountInfo?.clan?.tag}] - `
- : ''}
- {REGION_NAMES_SHORT[region as Region]}
-
-
+ {accountInfo.nickname}
+
+
+ {clanAccountInfo?.clan
+ ? `[${clanAccountInfo?.clan?.tag}] - `
+ : ''}
+ {REGION_NAMES_SHORT[region]}
+
-
-
-
- );
- } else {
- return [
- embedNegative(
- 'Please select from the options',
- 'Follow the procedure like the video above. The search results may take a while to show up.',
- ),
- new AttachmentBuilder('https://i.imgur.com/2p6GFgC.gif'),
- ];
- }
+
+
+
+ ,
+
+ embedInfo(
+ 'Incorrect account?',
+ 'Use the command again and select a username from the search results.',
+ ),
+ ];
},
autocomplete: autocompleteUsername,
From 23a23e36bb79ba2699334d43fce5c49ef1eed65b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 19 Jan 2024 21:32:46 -0600
Subject: [PATCH 08/11] feat: /today alias for /breakdown today
---
.vscode/settings.json | 1 +
src/commands/breakdown.tsx | 14 +-
src/commands/debug.ts | 2 +-
src/commands/evolution.tsx | 4 +-
src/commands/fullStats.tsx | 8 +-
src/commands/ratings.tsx | 4 +-
src/commands/stats.tsx | 82 ++++++------
src/commands/today.ts | 71 ++++++++++
src/core/discord/addFilterOptions.ts | 126 ++++++++----------
src/core/discord/addPeriodicFilterOptions.ts | 25 ++++
src/core/discord/getCustomPeriodParams.ts | 7 +-
src/core/discord/resolvePeriodFromCommand.ts | 5 +-
.../handlers/chatInputCommand.ts | 3 +-
src/events/interactionCreate/index.ts | 53 ++++----
14 files changed, 252 insertions(+), 153 deletions(-)
create mode 100644 src/commands/today.ts
create mode 100644 src/core/discord/addPeriodicFilterOptions.ts
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 4482684a6..91a3cb534 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -39,6 +39,7 @@
"ownedtanks",
"playerachievements",
"playerinfo",
+ "Promisable",
"ratingsleaderboard",
"ratingsplot",
"researchable",
diff --git a/src/commands/breakdown.tsx b/src/commands/breakdown.tsx
index fe05523db..fc6308cc9 100644
--- a/src/commands/breakdown.tsx
+++ b/src/commands/breakdown.tsx
@@ -13,7 +13,7 @@ import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
import { tankAverages } from '../core/blitzstars/tankAverages';
-import addFilterOptions from '../core/discord/addFilterOptions';
+import addPeriodicFilterOptions from '../core/discord/addPeriodicFilterOptions';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteTanks from '../core/discord/autocompleteTanks';
import autocompleteUsername from '../core/discord/autocompleteUsername';
@@ -33,12 +33,12 @@ import resolvePlayerFromCommand, {
import calculateWN8 from '../core/statistics/calculateWN8';
import { StatFilters, filterStats } from '../core/statistics/filterStats';
import getWN8Percentile from '../core/statistics/getWN8Percentile';
-import { CommandRegistryRaw } from '../events/interactionCreate';
+import { CommandRegistryPromisable } from '../events/interactionCreate';
const ROWS_PER_PAGE = 8;
const MAX_PAGES = 10;
-async function render(
+export async function renderBreakdown(
{ region, id }: ResolvedPlayer,
{ start, end, name }: ResolvedPeriod,
filters: StatFilters,
@@ -290,9 +290,9 @@ async function render(
}
}
-export const breakdownCommand = new Promise(
+export const breakdownCommand = new Promise(
async (resolve) => {
- const command = await addFilterOptions(
+ const command = await addPeriodicFilterOptions(
new SlashCommandBuilder()
.setName('breakdown')
.setDescription("A period's breakdown by tanks played"),
@@ -316,7 +316,7 @@ export const breakdownCommand = new Promise(
});
return [
- ...(await render(player, period, filters)),
+ ...(await renderBreakdown(player, period, filters)),
buttonPrimary(path, 'Refresh'),
await getBlitzStarsLinkButton(player.region, player.id),
];
@@ -332,7 +332,7 @@ export const breakdownCommand = new Promise(
const period = resolvePeriodFromButton(player.region, interaction);
const filters = getFiltersFromButton(interaction);
- return await render(player, period, filters);
+ return await renderBreakdown(player, period, filters);
},
});
},
diff --git a/src/commands/debug.ts b/src/commands/debug.ts
index 70ac62ad9..ef768d4d6 100644
--- a/src/commands/debug.ts
+++ b/src/commands/debug.ts
@@ -13,7 +13,7 @@ export const debugCommand: CommandRegistry = {
.setName('debug')
.setDescription('Debug information about the bot'),
- async handler(interaction) {
+ async handler() {
const currentTime = new Date().getTime();
const uptime = currentTime - executionStart;
const list = [
diff --git a/src/commands/evolution.tsx b/src/commands/evolution.tsx
index b495076e4..5b8cbd213 100644
--- a/src/commands/evolution.tsx
+++ b/src/commands/evolution.tsx
@@ -29,7 +29,7 @@ import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
import resolvePlayerFromCommand, {
ResolvedPlayer,
} from '../core/discord/resolvePlayerFromCommand';
-import { CommandRegistryRaw } from '../events/interactionCreate';
+import { CommandRegistryPromisable } from '../events/interactionCreate';
type EvolutionStatType = 'player' | 'tank';
@@ -107,7 +107,7 @@ async function render(
);
}
-export const evolutionCommand = new Promise(
+export const evolutionCommand = new Promise(
async (resolve) => {
let playerGroup: SlashCommandSubcommandGroupBuilder;
let tankGroup: SlashCommandSubcommandGroupBuilder;
diff --git a/src/commands/fullStats.tsx b/src/commands/fullStats.tsx
index f9262602a..01ca868a8 100644
--- a/src/commands/fullStats.tsx
+++ b/src/commands/fullStats.tsx
@@ -10,7 +10,7 @@ import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL';
import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
-import addFilterOptions from '../core/discord/addFilterOptions';
+import addPeriodicFilterOptions from '../core/discord/addPeriodicFilterOptions';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteTanks from '../core/discord/autocompleteTanks';
import autocompleteUsername from '../core/discord/autocompleteUsername';
@@ -29,7 +29,7 @@ import resolvePlayerFromCommand, {
} from '../core/discord/resolvePlayerFromCommand';
import { StatFilters, filterStats } from '../core/statistics/filterStats';
import { getTierWeights } from '../core/statistics/getTierWeights';
-import { CommandRegistryRaw } from '../events/interactionCreate';
+import { CommandRegistryPromisable } from '../events/interactionCreate';
async function render(
{ region, id }: ResolvedPlayer,
@@ -64,9 +64,9 @@ async function render(
);
}
-export const fullStatsCommand = new Promise(
+export const fullStatsCommand = new Promise(
async (resolve) => {
- const command = await addFilterOptions(
+ const command = await addPeriodicFilterOptions(
new SlashCommandBuilder()
.setName('full-stats')
.setDescription('Full in-game statistics'),
diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx
index e238e2063..9e344bdf9 100644
--- a/src/commands/ratings.tsx
+++ b/src/commands/ratings.tsx
@@ -26,7 +26,7 @@ import addRegionChoices from '../core/discord/addRegionChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedNegative from '../core/discord/embedNegative';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
-import { CommandRegistryRaw } from '../events/interactionCreate';
+import { CommandRegistryPromisable } from '../events/interactionCreate';
export interface RatingsPlayer {
spa_id: number;
@@ -120,7 +120,7 @@ const noOngoingSeason = embedNegative(
"Wargaming didn't provide any data for this season.",
);
-export const ratingsCommand = new Promise(
+export const ratingsCommand = new Promise(
async (resolve) => {
const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber();
const onGoingSeason = await isOnGoingRatingsSeason();
diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx
index 3f2e8cf60..bf6146259 100644
--- a/src/commands/stats.tsx
+++ b/src/commands/stats.tsx
@@ -9,7 +9,7 @@ import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL';
import { filtersToDescription } from '../core/blitzkrieg/filtersToDescription';
import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod';
-import addFilterOptions from '../core/discord/addFilterOptions';
+import addPeriodicFilterOptions from '../core/discord/addPeriodicFilterOptions';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteTanks from '../core/discord/autocompleteTanks';
import autocompleteUsername from '../core/discord/autocompleteUsername';
@@ -27,7 +27,7 @@ import resolvePlayerFromCommand, {
ResolvedPlayer,
} from '../core/discord/resolvePlayerFromCommand';
import { StatFilters, filterStats } from '../core/statistics/filterStats';
-import { CommandRegistryRaw } from '../events/interactionCreate';
+import { CommandRegistryPromisable } from '../events/interactionCreate';
async function render(
{ region, id }: ResolvedPlayer,
@@ -57,48 +57,50 @@ async function render(
);
}
-export const statsCommand = new Promise(async (resolve) => {
- const command = await addFilterOptions(
- new SlashCommandBuilder()
- .setName('stats')
- .setDescription('Regular battles statistics'),
- (option) => option.addStringOption(addUsernameChoices),
- );
+export const statsCommand = new Promise(
+ async (resolve) => {
+ const command = await addPeriodicFilterOptions(
+ new SlashCommandBuilder()
+ .setName('stats')
+ .setDescription('Regular battles statistics'),
+ (option) => option.addStringOption(addUsernameChoices),
+ );
- resolve({
- inProduction: true,
- inPublic: true,
+ resolve({
+ inProduction: true,
+ inPublic: true,
- command,
+ command,
- async handler(interaction) {
- const player = await resolvePlayerFromCommand(interaction);
- const period = resolvePeriodFromCommand(player.region, interaction);
- const filters = await getFiltersFromCommand(interaction);
- const path = commandToURL(interaction, {
- ...player,
- ...getCustomPeriodParams(interaction),
- ...filters,
- });
+ async handler(interaction) {
+ const player = await resolvePlayerFromCommand(interaction);
+ const period = resolvePeriodFromCommand(player.region, interaction);
+ const filters = await getFiltersFromCommand(interaction);
+ const path = commandToURL(interaction, {
+ ...player,
+ ...getCustomPeriodParams(interaction),
+ ...filters,
+ });
- return Promise.all([
- render(player, period, filters),
- buttonRefresh(interaction, path),
- getBlitzStarsLinkButton(player.region, player.id),
- ]);
- },
+ return Promise.all([
+ render(player, period, filters),
+ buttonRefresh(interaction, path),
+ getBlitzStarsLinkButton(player.region, player.id),
+ ]);
+ },
- autocomplete: (interaction) => {
- autocompleteUsername(interaction);
- autocompleteTanks(interaction);
- },
+ autocomplete: (interaction) => {
+ autocompleteUsername(interaction);
+ autocompleteTanks(interaction);
+ },
- async button(interaction) {
- const player = await resolvePlayerFromButton(interaction);
- const period = resolvePeriodFromButton(player.region, interaction);
- const filters = getFiltersFromButton(interaction);
+ async button(interaction) {
+ const player = await resolvePlayerFromButton(interaction);
+ const period = resolvePeriodFromButton(player.region, interaction);
+ const filters = getFiltersFromButton(interaction);
- return await render(player, period, filters);
- },
- });
-});
+ return await render(player, period, filters);
+ },
+ });
+ },
+);
diff --git a/src/commands/today.ts b/src/commands/today.ts
new file mode 100644
index 000000000..81e82405b
--- /dev/null
+++ b/src/commands/today.ts
@@ -0,0 +1,71 @@
+import { SlashCommandBuilder } from 'discord.js';
+import { encyclopediaInfo } from '../core/blitz/encyclopediaInfo';
+import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton';
+import { addFilterOptions } from '../core/discord/addFilterOptions';
+import addUsernameChoices from '../core/discord/addUsernameChoices';
+import autocompleteTanks from '../core/discord/autocompleteTanks';
+import autocompleteUsername from '../core/discord/autocompleteUsername';
+import buttonPrimary from '../core/discord/buttonPrimary';
+import commandToURL from '../core/discord/commandToURL';
+import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams';
+import { getFiltersFromButton } from '../core/discord/getFiltersFromButton';
+import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand';
+import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton';
+import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand';
+import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton';
+import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
+import { CommandRegistryPromisable } from '../events/interactionCreate';
+import { renderBreakdown } from './breakdown';
+
+export const todayCommand = new Promise(
+ async (resolve) => {
+ const awaitedEncyclopediaInfo = await encyclopediaInfo;
+ const command = addFilterOptions(
+ new SlashCommandBuilder()
+ .setName('today')
+ .setDescription("Today's played tanks and statistics"),
+ awaitedEncyclopediaInfo,
+ ).addStringOption(addUsernameChoices);
+
+ resolve({
+ inProduction: true,
+ inPublic: true,
+
+ command,
+
+ async handler(interaction) {
+ const player = await resolvePlayerFromCommand(interaction);
+ const period = resolvePeriodFromCommand(
+ player.region,
+ interaction,
+ 'today',
+ );
+ const filters = await getFiltersFromCommand(interaction);
+ const path = commandToURL(interaction, {
+ ...player,
+ ...getCustomPeriodParams(interaction, true),
+ ...filters,
+ });
+
+ return [
+ ...(await renderBreakdown(player, period, filters)),
+ buttonPrimary(path, 'Refresh'),
+ await getBlitzStarsLinkButton(player.region, player.id),
+ ];
+ },
+
+ autocomplete: (interaction) => {
+ autocompleteUsername(interaction);
+ autocompleteTanks(interaction);
+ },
+
+ async button(interaction) {
+ const player = await resolvePlayerFromButton(interaction);
+ const period = resolvePeriodFromButton(player.region, interaction);
+ const filters = getFiltersFromButton(interaction);
+
+ return await renderBreakdown(player, period, filters);
+ },
+ });
+ },
+);
diff --git a/src/core/discord/addFilterOptions.ts b/src/core/discord/addFilterOptions.ts
index 2c45a612d..1f00e9942 100644
--- a/src/core/discord/addFilterOptions.ts
+++ b/src/core/discord/addFilterOptions.ts
@@ -1,76 +1,60 @@
-import {
- SlashCommandBuilder,
- SlashCommandSubcommandBuilder,
- SlashCommandSubcommandGroupBuilder,
-} from 'discord.js';
+import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from 'discord.js';
import { TREE_TYPE_NAMES, TreeTypeString } from '../../components/Tanks';
-import { encyclopediaInfo } from '../blitz/encyclopediaInfo';
-import addPeriodSubCommands from './addPeriodSubCommands';
+import { EncyclopediaInfo } from '../blitz/encyclopediaInfo';
import addTankChoices from './addTankChoices';
import addTierChoices from './addTierChoices';
-export default async function addFilterOptions<
- OptionType extends SlashCommandBuilder | SlashCommandSubcommandGroupBuilder,
->(
- option: OptionType,
- extra: (
- option: SlashCommandSubcommandBuilder,
- ) => SlashCommandSubcommandBuilder = (option) => option,
-): Promise {
- const awaitedEncyclopediaInfo = await encyclopediaInfo;
-
- addPeriodSubCommands(option, (option) => {
- extra(option);
-
- return option
- .addStringOption((option) =>
- option
- .setName('nation')
- .setDescription('Nation')
- .addChoices(
- ...Object.entries(awaitedEncyclopediaInfo.vehicle_nations).map(
- ([type, name]) => ({ name, value: type }),
- ),
- )
- .setRequired(false),
- )
- .addStringOption((option) => addTierChoices(option).setRequired(false))
- .addStringOption((option) =>
- option
- .setName('tank-type')
- .setDescription('Tank type')
- .addChoices(
- ...Object.entries(awaitedEncyclopediaInfo.vehicle_types).map(
- ([type, name]) => ({
- name: name.replace(' Tank', ''),
- value: type,
- }),
- ),
- )
- .setRequired(false),
- )
- .addStringOption((option) =>
- option
- .setName('tree-type')
- .setDescription('Tech tree type')
- .addChoices(
- {
- name: TREE_TYPE_NAMES['researchable'],
- value: 'researchable' satisfies TreeTypeString,
- },
- {
- name: TREE_TYPE_NAMES.premium,
- value: 'premium' satisfies TreeTypeString,
- },
- {
- name: TREE_TYPE_NAMES.collector,
- value: 'collector' satisfies TreeTypeString,
- },
- )
- .setRequired(false),
- )
- .addStringOption((option) => addTankChoices(option).setRequired(false));
- });
-
- return option;
+export function addFilterOptions<
+ Option extends SlashCommandBuilder | SlashCommandSubcommandBuilder,
+>(option: Option, encyclopediaInfo: EncyclopediaInfo) {
+ return option
+ .addStringOption((option) =>
+ option
+ .setName('nation')
+ .setDescription('Nation')
+ .addChoices(
+ ...Object.entries(encyclopediaInfo.vehicle_nations).map(
+ ([type, name]) => ({ name, value: type }),
+ ),
+ )
+ .setRequired(false),
+ )
+ .addStringOption((option) => addTierChoices(option).setRequired(false))
+ .addStringOption((option) =>
+ option
+ .setName('tank-type')
+ .setDescription('Tank type')
+ .addChoices(
+ ...Object.entries(encyclopediaInfo.vehicle_types).map(
+ ([type, name]) => ({
+ name: name.replace(' Tank', ''),
+ value: type,
+ }),
+ ),
+ )
+ .setRequired(false),
+ )
+ .addStringOption((option) =>
+ option
+ .setName('tree-type')
+ .setDescription('Tech tree type')
+ .addChoices(
+ {
+ name: TREE_TYPE_NAMES['researchable'],
+ value: 'researchable' satisfies TreeTypeString,
+ },
+ {
+ name: TREE_TYPE_NAMES.premium,
+ value: 'premium' satisfies TreeTypeString,
+ },
+ {
+ name: TREE_TYPE_NAMES.collector,
+ value: 'collector' satisfies TreeTypeString,
+ },
+ )
+ .setRequired(false),
+ )
+ .addStringOption((option) =>
+ addTankChoices(option).setRequired(false),
+ ) as Option;
}
diff --git a/src/core/discord/addPeriodicFilterOptions.ts b/src/core/discord/addPeriodicFilterOptions.ts
new file mode 100644
index 000000000..7840258d3
--- /dev/null
+++ b/src/core/discord/addPeriodicFilterOptions.ts
@@ -0,0 +1,25 @@
+import {
+ SlashCommandBuilder,
+ SlashCommandSubcommandBuilder,
+ SlashCommandSubcommandGroupBuilder,
+} from 'discord.js';
+import { encyclopediaInfo } from '../blitz/encyclopediaInfo';
+import { addFilterOptions } from './addFilterOptions';
+import addPeriodSubCommands from './addPeriodSubCommands';
+
+export default async function addPeriodicFilterOptions<
+ OptionType extends SlashCommandBuilder | SlashCommandSubcommandGroupBuilder,
+>(
+ option: OptionType,
+ extra: (
+ option: SlashCommandSubcommandBuilder,
+ ) => SlashCommandSubcommandBuilder = (option) => option,
+): Promise {
+ const awaitedEncyclopediaInfo = await encyclopediaInfo;
+
+ addPeriodSubCommands(option, (option) => {
+ return addFilterOptions(extra(option), awaitedEncyclopediaInfo);
+ });
+
+ return option;
+}
diff --git a/src/core/discord/getCustomPeriodParams.ts b/src/core/discord/getCustomPeriodParams.ts
index 55b7b8aa0..1b35e96e8 100644
--- a/src/core/discord/getCustomPeriodParams.ts
+++ b/src/core/discord/getCustomPeriodParams.ts
@@ -3,12 +3,17 @@ import { PeriodType } from './addPeriodSubCommands';
export function getCustomPeriodParams(
interaction: ChatInputCommandInteraction,
+ forcedNonCustomPeriod = false,
) {
- if ((interaction.options.getSubcommand() as PeriodType) === 'custom')
+ if (
+ !forcedNonCustomPeriod &&
+ (interaction.options.getSubcommand() as PeriodType) === 'custom'
+ ) {
return {
start: interaction.options.getInteger('start', true),
end: interaction.options.getInteger('end', true),
};
+ }
return undefined;
}
diff --git a/src/core/discord/resolvePeriodFromCommand.ts b/src/core/discord/resolvePeriodFromCommand.ts
index a09da237d..fc09987aa 100644
--- a/src/core/discord/resolvePeriodFromCommand.ts
+++ b/src/core/discord/resolvePeriodFromCommand.ts
@@ -20,11 +20,14 @@ export interface ResolvedPeriod {
export default function resolvePeriodFromCommand(
region: Region,
interaction: ChatInputCommandInteraction,
+ forcedPeriod?: PeriodType,
) {
let name: string;
let start: number;
let end: number;
- const periodSubcommand = interaction.options.getSubcommand() as PeriodType;
+ const periodSubcommand = forcedPeriod
+ ? forcedPeriod
+ : (interaction.options.getSubcommand() as PeriodType);
if (periodSubcommand === 'custom') {
const startOption = interaction.options.getInteger('start', true);
diff --git a/src/events/interactionCreate/handlers/chatInputCommand.ts b/src/events/interactionCreate/handlers/chatInputCommand.ts
index adb323b07..7d2699725 100644
--- a/src/events/interactionCreate/handlers/chatInputCommand.ts
+++ b/src/events/interactionCreate/handlers/chatInputCommand.ts
@@ -19,7 +19,8 @@ import { psa } from '../../../core/discord/psa';
export default async function handleChatInputCommand(
interaction: ChatInputCommandInteraction,
) {
- const registry = (await commands)[interaction.commandName];
+ const awaitedCommands = await commands;
+ const registry = awaitedCommands[interaction.commandName];
await interaction.deferReply();
diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts
index 0b7aeadcb..3e7393199 100644
--- a/src/events/interactionCreate/index.ts
+++ b/src/events/interactionCreate/index.ts
@@ -31,6 +31,7 @@ import { searchClansCommand } from '../../commands/searchClans';
import { searchPlayersCommand } from '../../commands/searchPlayers';
import { searchTanksCommand } from '../../commands/searchTanks';
import { statsCommand } from '../../commands/stats';
+import { todayCommand } from '../../commands/today';
import getClientId from '../../core/blitzkrieg/getClientId';
import isDev from '../../core/blitzkrieg/isDev';
import { secrets } from '../../core/blitzkrieg/secrets';
@@ -52,34 +53,39 @@ export type InteractionReturnable =
| InteractionIterableReturnable
| Promise;
-export interface Registry {
- inProduction: boolean;
-}
-
-export type CommandRegistryRaw = Registry & {
- inPublic: boolean;
- inPreview?: boolean;
-
+type CommandRegistryBase = {
command:
| SlashCommandBuilder
| SlashCommandSubcommandsOnlyBuilder
| Omit;
+};
+type CommandRegistryDefinitionBase = {
+ inProduction: boolean;
+ inPublic: boolean;
+ inPreview?: boolean;
autocomplete?: (interaction: AutocompleteInteraction) => void;
button?: (interaction: ButtonInteraction) => InteractionReturnable;
-} & (
- | {
- handlesInteraction: true;
- handler: (interaction: ChatInputCommandInteraction) => void;
- }
- | {
- handlesInteraction?: false;
- handler: (
- interaction: ChatInputCommandInteraction,
- ) => InteractionReturnable;
- }
- );
-export type CommandRegistry = CommandRegistryRaw | Promise;
+};
+type CommandRegistryDefinitionHandlesInteraction = {
+ handlesInteraction: true;
+ handler: (interaction: ChatInputCommandInteraction) => void;
+};
+type CommandRegistryDefinitionDoesHandleInteraction = {
+ handlesInteraction?: false;
+ handler: (
+ interaction: ChatInputCommandInteraction,
+ ) => InteractionReturnable;
+};
+export type CommandRegistryPromisable = CommandRegistryBase &
+ (CommandRegistryDefinitionBase &
+ (
+ | CommandRegistryDefinitionHandlesInteraction
+ | CommandRegistryDefinitionDoesHandleInteraction
+ ));
+export type CommandRegistry =
+ | CommandRegistryPromisable
+ | Promise;
const rest = new REST().setToken(secrets.DISCORD_TOKEN);
@@ -101,10 +107,11 @@ export const COMMANDS_RAW: CommandRegistry[] = [
evolutionCommand,
statsCommand,
ratingsCommand,
+ todayCommand,
];
export const commands = Promise.all(COMMANDS_RAW).then((rawCommands) =>
- rawCommands.reduce>(
+ rawCommands.reduce>(
(accumulator, registry) => {
if (isDev()) registry.command.setDefaultMemberPermissions(0);
@@ -123,7 +130,7 @@ export const publicCommands: RESTPostAPIChatInputApplicationCommandsJSONBody[] =
[];
commands.then((awaitedCommands) => {
- Object.entries(awaitedCommands).forEach(([, registry]) => {
+ Object.values(awaitedCommands).forEach((registry) => {
const json = registry.command.toJSON();
if (registry.inPublic) {
From 8cf50126021f62dd6d239e22c7bc415377d06185 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 19 Jan 2024 21:35:46 -0600
Subject: [PATCH 09/11] feat: full region names in /link
---
src/commands/link.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/commands/link.tsx b/src/commands/link.tsx
index 1e44da607..46110c6ed 100644
--- a/src/commands/link.tsx
+++ b/src/commands/link.tsx
@@ -4,7 +4,7 @@ import markdownEscape from 'markdown-escape';
import discord from '../../discord.json' assert { type: 'json' };
import { Glow } from '../components/AllStatsOverview/components/WN8Display/components/Glow';
import Wrapper from '../components/Wrapper';
-import { REGION_NAMES_SHORT } from '../constants/regions';
+import { REGION_NAMES } from '../constants/regions';
import { getAccountInfo } from '../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import { linkBlitzAndDiscord } from '../core/blitzkrieg/discordBlitz';
@@ -188,7 +188,7 @@ export const verifyCommand: CommandRegistry = {
{clanAccountInfo?.clan
? `[${clanAccountInfo?.clan?.tag}] - `
: ''}
- {REGION_NAMES_SHORT[region]}
+ {REGION_NAMES[region]}
From 8f4e33fd5a2f2fff256c185cfa0274f498784e28 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 19 Jan 2024 21:45:23 -0600
Subject: [PATCH 10/11] feat: changelogs
---
docs/changelogs/1.15.10.md | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 docs/changelogs/1.15.10.md
diff --git a/docs/changelogs/1.15.10.md b/docs/changelogs/1.15.10.md
new file mode 100644
index 000000000..9d2f3753a
--- /dev/null
+++ b/docs/changelogs/1.15.10.md
@@ -0,0 +1,22 @@
+# Blitzkrieg 1.15.10
+
+The 10th patch for the `1.15` update is here, breaking the record of the most patches for a single update!
+
+## Features
+
+- Added `/today`
+ - This is a direct alias for `/breakdown today`
+- Drastically simplified ratings command
+ - `/ratings current` and `/ratings archive` have been now merged
+ - Where the data is fetched from is now decided directly from the chosen season number
+- Simplified many error messages
+- Title bar description text is now centered when there is no image
+ - It always felt awkward when there was no image on the left but the left still aligned to the right
+- Removed `/link`'s requirement to select a username explicitly from the search results
+- Full names for regions are now used in the `/link` command
+ - So "NA" turns into "North America"
+
+## Fixes
+
+- Fixed contrast issues with WN8 percentile colors behind white text
+- Archived ratings neighbors now functions once again
From c91ab29a234f67e051df19dd1836d08e547d4751 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Fri, 19 Jan 2024 21:46:36 -0600
Subject: [PATCH 11/11] feat: disabled tankopedia
---
docs/changelogs/1.15.10.md | 1 +
src/constants/tools.ts | 1 +
2 files changed, 2 insertions(+)
diff --git a/docs/changelogs/1.15.10.md b/docs/changelogs/1.15.10.md
index 9d2f3753a..231d0f2f2 100644
--- a/docs/changelogs/1.15.10.md
+++ b/docs/changelogs/1.15.10.md
@@ -20,3 +20,4 @@ The 10th patch for the `1.15` update is here, breaking the record of the most pa
- Fixed contrast issues with WN8 percentile colors behind white text
- Archived ratings neighbors now functions once again
+- Disabled tankopedia; this feature isn't finished yet and the changes were accidentally leaked
diff --git a/src/constants/tools.ts b/src/constants/tools.ts
index 4211c50a9..a531edd8e 100644
--- a/src/constants/tools.ts
+++ b/src/constants/tools.ts
@@ -20,6 +20,7 @@ export const TOOLS: Tool[] = [
id: 'tankopedia',
title: 'Tankopedia',
description: 'Blitz tank encyclopedia',
+ disabled: true,
},
{
id: 'inactive',