feat: simplified /ratings command

This commit is contained in:
TrèsAbhi committed 2024-01-19 16:02:03 -06:00
1 parent ff9bda29cf
commit 890a4d142b
3 files changed
+180 -194

No files matched your search

+166 -193
View File
@@ -2,7 +2,6 @@ import {
APIApplicationCommandOptionChoice, APIApplicationCommandOptionChoice,
SlashCommandBuilder, SlashCommandBuilder,
SlashCommandSubcommandBuilder, SlashCommandSubcommandBuilder,
SlashCommandSubcommandGroupBuilder,
} from 'discord.js'; } from 'discord.js';
import { range } from 'lodash'; import { range } from 'lodash';
import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard'; import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard';
@@ -17,13 +16,13 @@ import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo';
import getRatingsInfo from '../core/blitz/getRatingsInfo'; import getRatingsInfo from '../core/blitz/getRatingsInfo';
import { getRatingsLeague } from '../core/blitz/getRatingsLeague'; import { getRatingsLeague } from '../core/blitz/getRatingsLeague';
import { getRatingsNeighbors } from '../core/blitz/getRatingsNeighbors'; import { getRatingsNeighbors } from '../core/blitz/getRatingsNeighbors';
import { isOnGoingRatingsSeason } from '../core/blitz/isOnGoingRatingsSeason';
import { getArchivedLatestSeasonNumber } from '../core/blitzkrieg/getArchivedLatestSeasonNumber'; import { getArchivedLatestSeasonNumber } from '../core/blitzkrieg/getArchivedLatestSeasonNumber';
import getArchivedRatingsInfo from '../core/blitzkrieg/getArchivedRatingsInfo'; import getArchivedRatingsInfo from '../core/blitzkrieg/getArchivedRatingsInfo';
import { getArchivedRatingsLeaderboard } from '../core/blitzkrieg/getArchivedRatingsLeaderboard'; import { getArchivedRatingsLeaderboard } from '../core/blitzkrieg/getArchivedRatingsLeaderboard';
import getArchivedRatingsMidnightLeaderboard from '../core/blitzkrieg/getArchivedRatingsMidnightLeaderboard'; import getArchivedRatingsMidnightLeaderboard from '../core/blitzkrieg/getArchivedRatingsMidnightLeaderboard';
import { UserError } from '../core/blitzkrieg/userError'; import { UserError } from '../core/blitzkrieg/userError';
import addRegionChoices from '../core/discord/addRegionChoices'; import addRegionChoices from '../core/discord/addRegionChoices';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import autocompleteUsername from '../core/discord/autocompleteUsername'; import autocompleteUsername from '../core/discord/autocompleteUsername';
import embedNegative from '../core/discord/embedNegative'; import embedNegative from '../core/discord/embedNegative';
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
@@ -116,56 +115,6 @@ export interface RatingsNeighbors {
neighbors: RatingsPlayer[]; 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( const noOngoingSeason = embedNegative(
'No ongoing season', 'No ongoing season',
"Wargaming didn't provide any data for this season.", "Wargaming didn't provide any data for this season.",
@@ -174,38 +123,72 @@ const noOngoingSeason = embedNegative(
export const ratingsCommand = new Promise<CommandRegistryRaw>( export const ratingsCommand = new Promise<CommandRegistryRaw>(
async (resolve) => { async (resolve) => {
const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber(); const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber();
const seasonChoices = range( const onGoingSeason = await isOnGoingRatingsSeason();
const seasonNumbers = range(
FIRST_ARCHIVED_RATINGS_SEASON, FIRST_ARCHIVED_RATINGS_SEASON,
latestArchivedSeasonNumber + 1, latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1,
).map(
(number) =>
({
name: `Season ${number}`,
value: `${number}`,
}) satisfies APIApplicationCommandOptionChoice<string>,
); );
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() const command = new SlashCommandBuilder()
.setName('ratings') .setName('ratings')
.setDescription('Ratings battles statistics') .setDescription('Ratings battles statistics')
.addSubcommandGroup((option) => .addSubcommand((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) =>
addSubcommands( addSubcommands(
option option
.setName('current') .setName('neighbors')
.setDescription('The current running Ratings season'), .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; nickname: string;
} }
const subcommandGroup = interaction.options.getSubcommandGroup(true) as
| 'archive'
| 'current';
const subcommand = interaction.options.getSubcommand(true) as const subcommand = interaction.options.getSubcommand(true) as
| 'league' | 'league'
| 'neighbors'; | 'neighbors';
const limit = interaction.options.getInteger('limit') ?? 10; const limit = interaction.options.getInteger('limit') ?? 10;
const seasonOption = interaction.options.getString('season'); 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 titleName: string;
let titleImage: string | undefined; let titleImage: string | undefined;
let titleNameDiscriminator: string | undefined;
let titleDescription: string; let titleDescription: string;
let playersBefore: number; let playersBefore: number;
let playersAfter: number; let playersAfter: number;
@@ -249,69 +233,66 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
interaction.options.getString('league')!, interaction.options.getString('league')!,
); );
const region = interaction.options.getString('region') as Region; const region = interaction.options.getString('region') as Region;
regionRatingsInfo = await (subcommandGroup === 'current' regionRatingsInfo = await (isCurrentSeason
? getRatingsInfo(region) ? getRatingsInfo(region)
: getArchivedRatingsInfo(region, season!)); : getArchivedRatingsInfo(region, season!));
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
const result = const result = isCurrentSeason
subcommandGroup === 'current' ? await getRatingsLeague(region, leagueIndex).then(async (result) =>
? await getRatingsLeague(region, leagueIndex).then( result.slice(0, limit).map(
async (result) => (player) =>
result.slice(0, limit).map( ({
(player) => id: player.spa_id,
({ score: player.score,
id: player.spa_id, position: player.number,
score: player.score, clan: player.clan_tag,
position: player.number, nickname: player.nickname,
clan: player.clan_tag, }) satisfies SimplifiedPlayer,
nickname: player.nickname, ),
}) satisfies SimplifiedPlayer, )
), : await getArchivedRatingsLeaderboard(region, season!).then(
) async (players) => {
: await getArchivedRatingsLeaderboard(region, season!).then( const firstPlayerIndex =
async (players) => { leagueIndex === 0
const firstPlayerIndex = ? 0
leagueIndex === 0 : players.findIndex(
? 0 (player) =>
: players.findIndex( player.score < LEAGUES[leagueIndex - 1].minScore,
(player) => );
player.score < LEAGUES[leagueIndex - 1].minScore, const lastPlayerIndex = firstPlayerIndex + limit - 1;
); const trimmed = players.slice(
const lastPlayerIndex = firstPlayerIndex + limit - 1; firstPlayerIndex,
const trimmed = players.slice( lastPlayerIndex + 1,
firstPlayerIndex, );
lastPlayerIndex + 1, const clanData = await getClanAccountInfo(
); region,
const clanData = await getClanAccountInfo( trimmed.map(({ id }) => id),
region, ['clan'],
trimmed.map(({ id }) => id), );
['clan'],
);
return trimmed.map( return trimmed.map(
(player, index) => (player, index) =>
({ ({
id: player.id, id: player.id,
score: player.score, score: player.score,
position: firstPlayerIndex + index + 1, position: firstPlayerIndex + index + 1,
clan: clanData[player.id]?.clan?.tag, clan: clanData[player.id]?.clan?.tag,
nickname: nickname:
clanData[index]?.account_name ?? clanData[index]?.account_name ??
`Deleted ${player.id}`, `Deleted ${player.id}`,
}) satisfies SimplifiedPlayer, }) satisfies SimplifiedPlayer,
); );
}, },
); );
const leagueInfo = regionRatingsInfo.leagues[leagueIndex]; const leagueInfo = regionRatingsInfo.leagues[leagueIndex];
midnightLeaderboard = midnightLeaderboard = isCurrentSeason
subcommandGroup === 'current' ? await getArchivedRatingsMidnightLeaderboard(
? await getArchivedRatingsMidnightLeaderboard( region,
region, regionRatingsInfo.current_season,
regionRatingsInfo.current_season, )
) : await getArchivedRatingsMidnightLeaderboard(region, season!);
: await getArchivedRatingsMidnightLeaderboard(region, season!);
players = result; players = result;
playersBefore = result ? result[0].position - 1 : 0; playersBefore = result ? result[0].position - 1 : 0;
@@ -326,22 +307,17 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
titleDescription = `Top ${limit} players`; titleDescription = `Top ${limit} players`;
} else { } else {
const { region, id } = await resolvePlayerFromCommand(interaction); const { region, id } = await resolvePlayerFromCommand(interaction);
regionRatingsInfo = regionRatingsInfo = isCurrentSeason
subcommandGroup === 'current' ? await getRatingsInfo(region)
? await getRatingsInfo(region) : await getArchivedRatingsInfo(region, season!);
: await getArchivedRatingsInfo(region, season!);
if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; if (regionRatingsInfo.detail !== undefined) return noOngoingSeason;
const accountInfo = await getAccountInfo(region, id); const accountInfo = await getAccountInfo(region, id);
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan; const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
const neighbors = const neighbors = isCurrentSeason
subcommandGroup === 'current' ? await getRatingsNeighbors(region, id, Math.round(limit / 2)).then(
? await getRatingsNeighbors( (players) =>
region,
id,
Math.round(limit / 2),
).then((players) =>
players.neighbors.map( players.neighbors.map(
(player) => (player) =>
({ ({
@@ -352,54 +328,51 @@ export const ratingsCommand = new Promise<CommandRegistryRaw>(
nickname: player.nickname, nickname: player.nickname,
}) satisfies SimplifiedPlayer, }) satisfies SimplifiedPlayer,
), ),
) )
: await getArchivedRatingsLeaderboard(region, season!).then( : await getArchivedRatingsLeaderboard(region, season!).then(
async (players) => { async (players) => {
const playerIndex = players.findIndex( const playerIndex = players.findIndex(
(player) => player.id === id, (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) { const halfRange = Math.round(limit / 2);
throw new UserError( const trimmed = players.slice(
`${accountInfo.nickname} didn't player ratings in season ${season}`, playerIndex - halfRange,
{ playerIndex + halfRange + 1,
cause: );
'This player did not participate in this season or did not get past calibration.', const clanData = await getClanAccountInfo(
}, region,
); trimmed.map((player) => player.id),
} ['clan'],
);
const halfRange = Math.round(limit / 2); return trimmed.map((player, index) => ({
const trimmed = players.slice( id: player.id,
playerIndex - halfRange, score: player.score,
playerIndex + halfRange + 1, position: playerIndex + index + 1,
); clan: clanData[index]?.clan?.tag,
const clanData = await getClanAccountInfo( nickname: clanData[index]
region, ? clanData[index]!.account_name
trimmed.map((player) => player.id), : `Deleted ${player.id}`,
['clan'], }));
); },
);
return trimmed.map((player, index) => ({ midnightLeaderboard = isCurrentSeason
id: player.id, ? await getArchivedRatingsMidnightLeaderboard(
score: player.score, region,
position: playerIndex + index + 1, regionRatingsInfo.current_season,
clan: clanData[index]?.clan?.tag, )
nickname: clanData[index] : await getArchivedRatingsMidnightLeaderboard(region, season!);
? 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}]`;
players = neighbors; players = neighbors;
titleImage = clan titleImage = clan
+8 -1
View File
@@ -4,7 +4,11 @@ import { context } from '../blitzkrieg/context';
import { patientFetch } from '../blitzkrieg/patientFetch'; import { patientFetch } from '../blitzkrieg/patientFetch';
import regionToRegionSubdomain from './regionToRegionSubdomain'; import regionToRegionSubdomain from './regionToRegionSubdomain';
const ratingsInfoCache: Partial<Record<Region, RatingsInfo>> = {};
export default async function getRatingsInfo(region: Region) { export default async function getRatingsInfo(region: Region) {
if (ratingsInfoCache[region]) return ratingsInfoCache[region]!;
const response = await patientFetch( const response = await patientFetch(
context === 'website' context === 'website'
? `/api/${region}/ratings/current/info` ? `/api/${region}/ratings/current/info`
@@ -14,6 +18,9 @@ export default async function getRatingsInfo(region: Region) {
undefined, undefined,
{ cache: 'no-store' }, { cache: 'no-store' },
); );
const data = (await response.json()) as RatingsInfo;
return (await response.json()) as Promise<RatingsInfo>; ratingsInfoCache[region] = data;
return data;
} }
+6
View File
@@ -0,0 +1,6 @@
import getRatingsInfo from './getRatingsInfo';
export async function isOnGoingRatingsSeason() {
const ratingsInfo = await getRatingsInfo('com');
return ratingsInfo.detail === undefined;
}