diff --git a/.vscode/settings.json b/.vscode/settings.json index 57e6e0dab..de2d6290e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,9 @@ "Abhi", "accountinfo", "bepaint", + "blitzstars", "cleandist", + "customperiod", "fuzzysort", "kranvagn", "outfile", diff --git a/src/commands/eligible.ts b/src/commands/eligible.ts index 8dc80c208..d4bd25ca8 100644 --- a/src/commands/eligible.ts +++ b/src/commands/eligible.ts @@ -60,7 +60,6 @@ export default { if (clan === 'SKLLD') { const tankStatsOverTime = await getTankStatsOverTime( - interaction, server, id, getPeriodicStart('30'), diff --git a/src/commands/ownedtanks.tsx b/src/commands/ownedtanks.tsx index 15d4deb09..7edceec12 100644 --- a/src/commands/ownedtanks.tsx +++ b/src/commands/ownedtanks.tsx @@ -73,7 +73,7 @@ export default { const accountInfo = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`, ); - const tankStats = await getTankStats(interaction, server, id); + const tankStats = await getTankStats(server, id); const tanks = tankStats .map((tankData) => tankopedia[tankData.tank_id]) .filter((tank) => tank?.tier === tier); diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx index 6c541451a..141c0c8af 100644 --- a/src/commands/stats.tsx +++ b/src/commands/stats.tsx @@ -14,15 +14,17 @@ import sumStats from '../core/blitz/sumStats.js'; import { tankopedia } from '../core/blitz/tankopedia.js'; import getPeriodNow from '../core/blitzstars/getPeriodNow.js'; import getPeriodicStart from '../core/blitzstars/getPeriodStart.js'; +import getPeriodStartFromDaysAgo from '../core/blitzstars/getPeriodStartFromDaysAgo.js'; import getTankStatsOverTime from '../core/blitzstars/getTankStatsOverTime.js'; import { tankAverages } from '../core/blitzstars/tankAverages.js'; import cmdName from '../core/interaction/cmdName.js'; import fullBlitzStarsStats from '../core/interaction/fullBlitzStarsStats.js'; import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js'; -import addStatPeriodChoices, { +import addCustomPeriodOption from '../core/options/addCustomPeriodOption.js'; +import addPeriodChoices, { StatPeriod, statPeriodNames, -} from '../core/options/addStatPeriodChoices.js'; +} from '../core/options/addPeriodChoices.js'; import addUsernameOption from '../core/options/addUsernameOption.js'; import { WARGAMING_APPLICATION_ID } from '../core/process/args.js'; import { CommandRegistry } from '../events/interactionCreate.js'; @@ -35,14 +37,25 @@ import { PlayerClanData } from '../types/playerClanData.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() .setName(cmdName('stats')) .setDescription("Gets the user's in-game statistics") - .addStringOption(addStatPeriodChoices) - .addStringOption(addUsernameOption), + .addSubcommand((option) => + option + .setName('period') + .setDescription('Pick from a predetermined periods') + .addStringOption(addPeriodChoices) + .addStringOption(addUsernameOption), + ) + .addSubcommand((option) => + addCustomPeriodOption(option) + .setName('customperiod') + .setDescription('Custom periods') + .addStringOption(addUsernameOption), + ), async execute(interaction) { const period = interaction.options.getString('period') as StatPeriod; @@ -53,12 +66,25 @@ export default { const clanData = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`, ); - const tankStats = await getTankStats(interaction, server, id); let stats: AllStats; let supplementaryStats: SupplementaryStats | undefined; let hasWN8AccumulatedAtAll = false; + const isCustomPeriod = + interaction.options.getSubcommand() === 'customperiod'; + let periodName: string; + const startRaw = interaction.options.getInteger('start')!; + const endRaw = interaction.options.getInteger('end')!; + const start = Math.max(startRaw, endRaw); + const end = Math.min(startRaw, endRaw); + + if (isCustomPeriod) { + periodName = `${start} to ${end} days ago`; + } else { + periodName = statPeriodNames[period]; + } if (period === 'career') { + const tankStats = await getTankStats(server, id); stats = accountInfo[id].statistics.all; const totalBattles = tankStats.reduce( (accumulator, { all }) => accumulator + all.battles, @@ -87,11 +113,12 @@ export default { }; } else { const tankStatsOverTime = await getTankStatsOverTime( - interaction, server, id, - getPeriodicStart(period), - getPeriodNow(), + isCustomPeriod + ? getPeriodStartFromDaysAgo(start) + : getPeriodicStart(period), + isCustomPeriod ? getPeriodStartFromDaysAgo(end) : getPeriodNow(), ); const entries = Object.entries(tankStatsOverTime); const totalBattles = entries.reduce( @@ -141,9 +168,9 @@ export default { ? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clanData[id]?.clan?.emblem_set_id}.png` : undefined } - description={`${ - statPeriodNames[period] - } • ${new Date().toDateString()} • ${BLITZ_SERVERS[server]}`} + description={`${periodName} • ${new Date().toDateString()} • ${ + BLITZ_SERVERS[server] + }`} /> {stats.battles === 0 && } diff --git a/src/commands/tankstats.tsx b/src/commands/tankstats.tsx index 6585a2fca..3c863e4ea 100644 --- a/src/commands/tankstats.tsx +++ b/src/commands/tankstats.tsx @@ -16,6 +16,7 @@ import resolveTankName from '../core/blitz/resolveTankName.js'; import { tankopedia } from '../core/blitz/tankopedia.js'; import getPeriodNow from '../core/blitzstars/getPeriodNow.js'; import getPeriodicStart from '../core/blitzstars/getPeriodStart.js'; +import getPeriodStartFromDaysAgo from '../core/blitzstars/getPeriodStartFromDaysAgo.js'; import getTankStatsOverTime, { emptyAllStats, } from '../core/blitzstars/getTankStatsOverTime.js'; @@ -23,10 +24,11 @@ import { tankAverages } from '../core/blitzstars/tankAverages.js'; import cmdName from '../core/interaction/cmdName.js'; import fullBlitzStarsStats from '../core/interaction/fullBlitzStarsStats.js'; import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js'; -import addStatPeriodChoices, { +import addCustomPeriodOption from '../core/options/addCustomPeriodOption.js'; +import addPeriodChoices, { StatPeriod, statPeriodNames, -} from '../core/options/addStatPeriodChoices.js'; +} from '../core/options/addPeriodChoices.js'; import addTankChoices from '../core/options/addTankChoices.js'; import addUsernameOption from '../core/options/addUsernameOption.js'; import { WARGAMING_APPLICATION_ID } from '../core/process/args.js'; @@ -35,15 +37,27 @@ import { AccountInfo, AllStats } from '../types/accountInfo.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() .setName(cmdName('tankstats')) .setDescription('Stats for a tank over a period') - .addStringOption(addTankChoices) - .addStringOption(addStatPeriodChoices) - .addStringOption(addUsernameOption), + .addSubcommand((option) => + option + .setName('period') + .setDescription('Pick from a predetermined periods') + .addStringOption(addTankChoices) + .addStringOption(addPeriodChoices) + .addStringOption(addUsernameOption), + ) + .addSubcommand((option) => + addCustomPeriodOption(option) + .setName('customperiod') + .addStringOption(addTankChoices) + .setDescription('Custom periods') + .addStringOption(addUsernameOption), + ), async execute(interaction) { const tank = await resolveTankId( @@ -56,9 +70,22 @@ export default { const accountInfo = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`, ); - const tankStats = await getTankStats(interaction, server, id); + const tankStats = await getTankStats(server, id); let stats: AllStats; const tankId = Number(tank); + const isCustomPeriod = + interaction.options.getSubcommand() === 'customperiod'; + let periodName: string; + const startRaw = interaction.options.getInteger('start')!; + const endRaw = interaction.options.getInteger('end')!; + const start = Math.max(startRaw, endRaw); + const end = Math.min(startRaw, endRaw); + + if (isCustomPeriod) { + periodName = `${start} to ${end} days ago`; + } else { + periodName = statPeriodNames[period]; + } if (period === 'career') { stats = @@ -66,11 +93,12 @@ export default { emptyAllStats; } else { const tankStatsOverTime = await getTankStatsOverTime( - interaction, server, id, - getPeriodicStart(period), - getPeriodNow(), + isCustomPeriod + ? getPeriodStartFromDaysAgo(start) + : getPeriodicStart(period), + isCustomPeriod ? getPeriodStartFromDaysAgo(end) : getPeriodNow(), ); stats = tankStatsOverTime[tankId] ?? emptyAllStats; @@ -82,9 +110,9 @@ export default { name={accountInfo[id].nickname} nameDiscriminator={`(${resolveTankName(tankId)})`} image={tankopedia[tankId].images.normal} - description={`${ - statPeriodNames[period] - } • ${new Date().toDateString()} • ${BLITZ_SERVERS[server]}`} + description={`${periodName} • ${new Date().toDateString()} • ${ + BLITZ_SERVERS[server] + }`} /> {stats.battles === 0 && } diff --git a/src/commands/today.tsx b/src/commands/today.tsx index 681b4ee1d..da78dccaf 100644 --- a/src/commands/today.tsx +++ b/src/commands/today.tsx @@ -38,7 +38,6 @@ export default { async execute(interaction) { const { id, server } = await getBlitzAccount(interaction); const tankStatsOverTime = await getTankStatsOverTime( - interaction, server, id, last5AM().getTime() / 1000, @@ -50,7 +49,7 @@ export default { const clanData = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`, ); - const careerTankStatsRaw = await getTankStats(interaction, server, id); + const careerTankStatsRaw = await getTankStats(server, id); const careerStats: Record = { 0: accountInfo[id].statistics.all, }; diff --git a/src/core/blitz/getTankStats.ts b/src/core/blitz/getTankStats.ts index 334dfbc23..66d1091d5 100644 --- a/src/core/blitz/getTankStats.ts +++ b/src/core/blitz/getTankStats.ts @@ -1,15 +1,10 @@ -import { CacheType, ChatInputCommandInteraction } from 'discord.js'; import { BLITZ_SERVERS, BlitzServer } from '../../constants/servers.js'; import { TanksStats } from '../../types/tanksStats.js'; import { WARGAMING_APPLICATION_ID } from '../process/args.js'; import errorWithCause from '../process/errorWithCause.js'; import getWargamingResponse from './getWargamingResponse.js'; -export default async function getTankStats( - interaction: ChatInputCommandInteraction, - server: BlitzServer, - id: number, -) { +export default async function getTankStats(server: BlitzServer, id: number) { const tankStats = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`, ); diff --git a/src/core/blitzstars/getPeriodStart.ts b/src/core/blitzstars/getPeriodStart.ts index 916bcd9f7..845d94ba9 100644 --- a/src/core/blitzstars/getPeriodStart.ts +++ b/src/core/blitzstars/getPeriodStart.ts @@ -1,4 +1,5 @@ -import { StatPeriod } from '../options/addStatPeriodChoices.js'; +import { StatPeriod } from '../options/addPeriodChoices.js'; +import getPeriodStartFromDaysAgo from './getPeriodStartFromDaysAgo.js'; export default function getPeriodicStart(period: StatPeriod) { if (period === 'career') { @@ -12,12 +13,6 @@ export default function getPeriodicStart(period: StatPeriod) { return now.getTime() / 1000; } else { - const daysAgo = Number(period) - 1; - const periodStart = new Date(); - - periodStart.setDate(periodStart.getDate() - daysAgo); - periodStart.setHours(5, 0, 0, 0); - - return periodStart.getTime() / 1000; + return getPeriodStartFromDaysAgo(Number(period)); } } diff --git a/src/core/blitzstars/getPeriodStartFromDaysAgo.ts b/src/core/blitzstars/getPeriodStartFromDaysAgo.ts new file mode 100644 index 000000000..909f87735 --- /dev/null +++ b/src/core/blitzstars/getPeriodStartFromDaysAgo.ts @@ -0,0 +1,9 @@ +export default function getPeriodStartFromDaysAgo(period: number) { + const daysAgo = Number(period) - 1; + const periodStart = new Date(); + + periodStart.setDate(periodStart.getDate() - daysAgo); + periodStart.setHours(5, 0, 0, 0); + + return periodStart.getTime() / 1000; +} diff --git a/src/core/blitzstars/getTankStatsOverTime.ts b/src/core/blitzstars/getTankStatsOverTime.ts index 08d30e592..20793764c 100644 --- a/src/core/blitzstars/getTankStatsOverTime.ts +++ b/src/core/blitzstars/getTankStatsOverTime.ts @@ -1,4 +1,3 @@ -import { CacheType, ChatInputCommandInteraction } from 'discord.js'; import { BlitzServer } from '../../constants/servers.js'; import { AllStats } from '../../types/accountInfo.js'; import getTankStats from '../blitz/getTankStats.js'; @@ -44,13 +43,12 @@ export const emptyTankHistoryNode: TankHistoryNode = { export type TankHistory = TankHistoryNode[]; export default async function getTankStatsOverTime( - interaction: ChatInputCommandInteraction, server: BlitzServer, id: number, start: number, end: number, ) { - const latestTankStatsRaw = await getTankStats(interaction, server, id); + const latestTankStatsRaw = await getTankStats(server, id); const tankHistoriesResponse = await fetch( `https://www.blitzstars.com/api/tankhistories/for/${id}/`, ); diff --git a/src/core/options/addCustomPeriodOption.ts b/src/core/options/addCustomPeriodOption.ts new file mode 100644 index 000000000..4ec04c6b8 --- /dev/null +++ b/src/core/options/addCustomPeriodOption.ts @@ -0,0 +1,21 @@ +import { SlashCommandSubcommandBuilder } from 'discord.js'; + +export default function addCustomPeriodOption( + option: SlashCommandSubcommandBuilder, +) { + return option + .addIntegerOption((option) => + option + .setName('start') + .setDescription('Start of period in days ago from today') + .setMinValue(0) + .setRequired(true), + ) + .addIntegerOption((option) => + option + .setName('end') + .setDescription('End of period in days ago from today') + .setMinValue(0) + .setRequired(true), + ); +} diff --git a/src/core/options/addStatPeriodChoices.ts b/src/core/options/addPeriodChoices.ts similarity index 90% rename from src/core/options/addStatPeriodChoices.ts rename to src/core/options/addPeriodChoices.ts index b2a36c3f4..a724276d5 100644 --- a/src/core/options/addStatPeriodChoices.ts +++ b/src/core/options/addPeriodChoices.ts @@ -8,7 +8,7 @@ export const statPeriodNames: Record = { 90: '90-day statistics', career: 'Career statistics', }; -export default function addStatPeriodChoices(option: SlashCommandStringOption) { +export default function addPeriodChoices(option: SlashCommandStringOption) { return option .setName('period') .setDescription('The period to get stats for') diff --git a/src/core/options/addUsernameOption.ts b/src/core/options/addUsernameOption.ts index d9f323d77..c5da50df8 100644 --- a/src/core/options/addUsernameOption.ts +++ b/src/core/options/addUsernameOption.ts @@ -3,6 +3,8 @@ import { SlashCommandStringOption } from 'discord.js'; export default function addUsernameOption(option: SlashCommandStringOption) { return option .setName('username') - .setDescription('The username you use in Blitz') + .setDescription( + 'The username you use in Blitz (inferred from Discord nickname by default)', + ) .setAutocomplete(true); } diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 295dc05f4..43585b7d8 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -12,6 +12,7 @@ import { RESTPostAPIChatInputApplicationCommandsJSONBody, Routes, SlashCommandBuilder, + SlashCommandSubcommandsOnlyBuilder, } from 'discord.js'; import discord from '../../discord.json' assert { type: 'json' }; import debug from '../commands/debug.js'; @@ -46,7 +47,7 @@ export type CommandRegistry = { inProduction: boolean; inPublic: boolean; - command: Omit; + command: SlashCommandBuilder | SlashCommandSubcommandsOnlyBuilder; autocomplete?: (interaction: AutocompleteInteraction) => void; } & ( | {