mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
feat: simplified /ratings command
This commit is contained in:
1 parent
ff9bda29cf
commit
890a4d142b
3 files changed
+180
-194
No files matched your search
+166
-193
@@ -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<string>,
|
||||
),
|
||||
)
|
||||
.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<CommandRegistryRaw>(
|
||||
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<string>,
|
||||
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<string>,
|
||||
)
|
||||
.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<string>,
|
||||
),
|
||||
)
|
||||
.setRequired(true),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -224,18 +207,19 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
|
||||
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<CommandRegistryRaw>(
|
||||
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<CommandRegistryRaw>(
|
||||
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<CommandRegistryRaw>(
|
||||
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
|
||||
|
||||
@@ -4,7 +4,11 @@ import { context } from '../blitzkrieg/context';
|
||||
import { patientFetch } from '../blitzkrieg/patientFetch';
|
||||
import regionToRegionSubdomain from './regionToRegionSubdomain';
|
||||
|
||||
const ratingsInfoCache: Partial<Record<Region, RatingsInfo>> = {};
|
||||
|
||||
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<RatingsInfo>;
|
||||
ratingsInfoCache[region] = data;
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import getRatingsInfo from './getRatingsInfo';
|
||||
|
||||
export async function isOnGoingRatingsSeason() {
|
||||
const ratingsInfo = await getRatingsInfo('com');
|
||||
return ratingsInfo.detail === undefined;
|
||||
}
|
||||
Reference in new issue
Block a user