diff --git a/src/commands/evolution.tsx b/src/commands/evolution.tsx index 5b4b8b1db..f7e65edf6 100644 --- a/src/commands/evolution.tsx +++ b/src/commands/evolution.tsx @@ -1,17 +1,26 @@ import { SlashCommandBuilder } from 'discord.js'; +import { CYCLIC_API } from '../constants/cyclic.js'; +import getWargamingResponse from '../core/blitz/getWargamingResponse.js'; import resolveTankId from '../core/blitz/resolveTankId.js'; import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups.js'; import autocompleteTanks from '../core/discord/autocompleteTanks.js'; import autocompleteUsername from '../core/discord/autocompleteUsername.js'; +import interactionToURL from '../core/discord/interactionToURL.js'; +import linkButton from '../core/discord/linkButton.js'; +import primaryButton from '../core/discord/primaryButton.js'; +import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton.js'; import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand.js'; +import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton.js'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js'; +import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js'; import { CommandRegistry } from '../events/interactionCreate/index.js'; import evolution from '../renderers/evolution.js'; import { StatType } from '../renderers/statsfull.js'; +import { AccountInfo } from '../types/accountInfo.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: addStatTypeSubCommandGroups( @@ -27,25 +36,51 @@ export default { const player = await resolvePlayerFromCommand(interaction); const period = resolvePeriodFromCommand(interaction); const tankIdRaw = interaction.options.getString('tank')!; - const tankId = commandGroup === 'tank' ? resolveTankId(tankIdRaw) : 0; + const tankId = commandGroup === 'tank' ? resolveTankId(tankIdRaw) : null; + const start = interaction.options.getInteger('start'); + const end = interaction.options.getInteger('end'); + const { nickname } = ( + await getWargamingResponse( + `https://api.wotblitz.${player.server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`, + ) + )[player.id]; + const path = interactionToURL(interaction, { + ...player, + tankId, + start, + end, + }); - return await evolution(commandGroup, period, player, tankId); - - // TODO: implement this - // new ButtonBuilder() - // .setStyle(ButtonStyle.Link) - // .setLabel('View on BlitzStars') - // .setURL( - // commandGroup === 'tank' - // ? `https://www.blitzstars.com/player/com/${ - // accountInfo[id].nickname - // }/tank/${tankId!}` - // : `https://www.blitzstars.com/player/com/${accountInfo[id].nickname}`, - // ); + return [ + await evolution(commandGroup, period, player, tankId), + primaryButton(path, 'Refresh'), + linkButton(`${CYCLIC_API}/${path}`, 'Embed'), + linkButton( + `https://www.blitzstars.com/player/${player.server}/${nickname}${ + commandGroup === 'tank' ? `/tank/${tankId!}` : '' + }`, + 'BlitzStars', + ), + ]; }, autocomplete: (interaction) => { autocompleteUsername(interaction); autocompleteTanks(interaction); }, + + async button(interaction) { + const url = new URL(`${CYCLIC_API}/${interaction.customId}`); + const path = url.pathname.split('/').filter(Boolean); + const commandGroup = path[1] as StatType; + const period = resolvePeriodFromButton(interaction); + const player = await resolvePlayerFromButton(interaction); + + return await evolution( + commandGroup, + period, + player, + parseInt(url.searchParams.get('tankId')!), + ); + }, } satisfies CommandRegistry; diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx index 2bff4e4e7..4f29fbda2 100644 --- a/src/commands/stats.tsx +++ b/src/commands/stats.tsx @@ -1,19 +1,22 @@ import { SlashCommandBuilder } from 'discord.js'; import { CYCLIC_API } from '../constants/cyclic.js'; +import getWargamingResponse from '../core/blitz/getWargamingResponse.js'; import resolveTankId from '../core/blitz/resolveTankId.js'; -import { Period } from '../core/discord/addPeriodSubCommands.js'; import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups.js'; import autocompleteTanks from '../core/discord/autocompleteTanks.js'; import autocompleteUsername from '../core/discord/autocompleteUsername.js'; +import interactionToURL from '../core/discord/interactionToURL.js'; import linkButton from '../core/discord/linkButton.js'; import primaryButton from '../core/discord/primaryButton.js'; import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton.js'; import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand.js'; import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton.js'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js'; +import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js'; import { CommandRegistry } from '../events/interactionCreate/index.js'; import stats from '../renderers/stats.js'; import { StatType } from '../renderers/statsfull.js'; +import { AccountInfo } from '../types/accountInfo.js'; export default { inProduction: true, @@ -30,23 +33,32 @@ export default { const commandGroup = interaction.options.getSubcommandGroup( true, ) as StatType; - const subcommand = interaction.options.getSubcommand() as Period; const player = await resolvePlayerFromCommand(interaction); const period = resolvePeriodFromCommand(interaction); const tankIdRaw = interaction.options.getString('tank')!; - const tankId = commandGroup === 'tank' ? resolveTankId(tankIdRaw) : 0; + const tankId = commandGroup === 'tank' ? resolveTankId(tankIdRaw) : null; const start = interaction.options.getInteger('start'); const end = interaction.options.getInteger('end'); - const path = `stats/${commandGroup}/${subcommand}?server=${ - player.server - }&id=${player.id}&tankId=${tankId}&start=${start ?? 0}&end=${end ?? 0}`; - - // interactionToURL(interaction); + const path = interactionToURL(interaction, { + ...player, + tankId, + start, + end, + }); + const { nickname } = ( + await getWargamingResponse( + `https://api.wotblitz.${player.server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`, + ) + )[player.id]; return [ await stats(commandGroup, period, player, tankId), primaryButton(path, 'Refresh'), - linkButton(path, 'Embed'), + linkButton(`${CYCLIC_API}/${path}`, 'Embed'), + linkButton( + `https://www.blitzstars.com/player/${player.server}/${nickname}`, + 'BlitzStars', + ), ]; }, diff --git a/src/commands/statsfull.tsx b/src/commands/statsfull.tsx index 92a1341c2..2e8a23e4b 100644 --- a/src/commands/statsfull.tsx +++ b/src/commands/statsfull.tsx @@ -1,7 +1,7 @@ import { SlashCommandBuilder } from 'discord.js'; import { CYCLIC_API } from '../constants/cyclic.js'; +import getWargamingResponse from '../core/blitz/getWargamingResponse.js'; import resolveTankId from '../core/blitz/resolveTankId.js'; -import { Period } from '../core/discord/addPeriodSubCommands.js'; import addStatTypeSubCommandGroups from '../core/discord/addStatTypeSubCommandGroups.js'; import autocompleteTanks from '../core/discord/autocompleteTanks.js'; import autocompleteUsername from '../core/discord/autocompleteUsername.js'; @@ -12,8 +12,10 @@ import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton.js' import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand.js'; import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton.js'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js'; +import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js'; import { CommandRegistry } from '../events/interactionCreate/index.js'; import statsfull, { StatType } from '../renderers/statsfull.js'; +import { AccountInfo } from '../types/accountInfo.js'; export default { inProduction: true, @@ -30,23 +32,32 @@ export default { const commandGroup = interaction.options.getSubcommandGroup( true, ) as StatType; - const subcommand = interaction.options.getSubcommand() as Period; const player = await resolvePlayerFromCommand(interaction); const period = resolvePeriodFromCommand(interaction); const tankIdRaw = interaction.options.getString('tank')!; const tankId = commandGroup === 'tank' ? resolveTankId(tankIdRaw) : 0; const start = interaction.options.getInteger('start'); const end = interaction.options.getInteger('end'); - const path = `statsfull/${commandGroup}/${subcommand}?server=${ - player.server - }&id=${player.id}&tankId=${tankId}&start=${start ?? 0}&end=${end ?? 0}`; - - interactionToURL(interaction); + const path = interactionToURL(interaction, { + ...player, + tankId, + start, + end, + }); + const { nickname } = ( + await getWargamingResponse( + `https://api.wotblitz.${player.server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`, + ) + )[player.id]; return [ await statsfull(commandGroup, period, player, tankId), primaryButton(path, 'Refresh'), - linkButton(path, 'Embed'), + linkButton(`${CYCLIC_API}/${path}`, 'Embed'), + linkButton( + `https://www.blitzstars.com/player/${player.server}/${nickname}`, + 'BlitzStars', + ), ]; }, diff --git a/src/commands/today.tsx b/src/commands/today.tsx index e7f29d0e0..324c92b77 100644 --- a/src/commands/today.tsx +++ b/src/commands/today.tsx @@ -1,13 +1,21 @@ import { SlashCommandBuilder } from 'discord.js'; +import { CYCLIC_API } from '../constants/cyclic.js'; +import getWargamingResponse from '../core/blitz/getWargamingResponse.js'; import addUsernameChoices from '../core/discord/addUsernameChoices.js'; import autocompleteUsername from '../core/discord/autocompleteUsername.js'; +import interactionToURL from '../core/discord/interactionToURL.js'; +import linkButton from '../core/discord/linkButton.js'; +import primaryButton from '../core/discord/primaryButton.js'; +import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton.js'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand.js'; +import { WARGAMING_APPLICATION_ID } from '../core/node/arguments.js'; import { CommandRegistry } from '../events/interactionCreate/index.js'; import today from '../renderers/today.js'; +import { AccountInfo } from '../types/accountInfo.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() @@ -17,9 +25,29 @@ export default { async handler(interaction) { const player = await resolvePlayerFromCommand(interaction); + const { nickname } = ( + await getWargamingResponse( + `https://api.wotblitz.${player.server}/wotb/account/info/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${player.id}`, + ) + )[player.id]; + const path = interactionToURL(interaction, player); - return await today(player); + return [ + await today(player), + primaryButton(path, 'Refresh'), + linkButton(`${CYCLIC_API}/${path}`, 'Embed'), + linkButton( + `https://www.blitzstars.com/player/${player.server}/${nickname}`, + 'BlitzStars', + ), + ]; }, autocomplete: autocompleteUsername, + + async button(interaction) { + const player = await resolvePlayerFromButton(interaction); + + return await today(player); + }, } satisfies CommandRegistry; diff --git a/src/core/discord/interactionToURL.ts b/src/core/discord/interactionToURL.ts index 1dc5041a4..25e903f5d 100644 --- a/src/core/discord/interactionToURL.ts +++ b/src/core/discord/interactionToURL.ts @@ -1,20 +1,33 @@ -import { CacheType, ChatInputCommandInteraction } from 'discord.js'; +import { + CacheType, + ChatInputCommandInteraction, + CommandInteractionOption, +} from 'discord.js'; + +export interface Parameters { + [key: string]: CommandInteractionOption['value'] | undefined | null; +} export default function interactionToURL( interaction: ChatInputCommandInteraction, + parameters?: Parameters, ) { const pathname = [ interaction.commandName, - interaction.options.getSubcommandGroup(), - interaction.options.getSubcommand(), + interaction.options.getSubcommandGroup(false), + interaction.options.getSubcommand(false), ] .filter(Boolean) .join('/'); - const urlSearchParams = new URLSearchParams(); + const params = [ + ...interaction.options.data.map( + (option) => [option.name, option.value] as const, + ), + ...Object.entries(parameters ?? {}), + ] + .filter(([, value]) => value !== undefined && value !== null) + .map(([key, value]) => `${key}=${value}`) + .join('&'); - interaction.options.data.forEach((option) => { - if (option.value) urlSearchParams.set(option.name, `${option.value}`); - }); - - console.log(`${urlSearchParams}`); + return `${pathname}${params.length > 0 ? `?${params}` : ''}`; } diff --git a/src/core/discord/linkButton.ts b/src/core/discord/linkButton.ts index 1215518a2..c553b009b 100644 --- a/src/core/discord/linkButton.ts +++ b/src/core/discord/linkButton.ts @@ -3,16 +3,15 @@ import { ButtonStyle, ComponentEmojiResolvable, } from 'discord.js'; -import { CYCLIC_API } from '../../constants/cyclic.js'; export default function linkButton( - id: string, + url: string, label: string, emoji?: ComponentEmojiResolvable, ) { const button = new ButtonBuilder() .setStyle(ButtonStyle.Link) - .setURL(`${CYCLIC_API}/${id}`) + .setURL(url) .setLabel(label); if (emoji) button.setEmoji(emoji); diff --git a/src/events/interactionCreate/handlers/button.tsx b/src/events/interactionCreate/handlers/button.tsx index 82a33f013..4bf3d0251 100644 --- a/src/events/interactionCreate/handlers/button.tsx +++ b/src/events/interactionCreate/handlers/button.tsx @@ -1,11 +1,13 @@ import { ButtonInteraction, CacheType } from 'discord.js'; +import { CYCLIC_API } from '../../../constants/cyclic.js'; import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable.js'; import { commands } from '../index.js'; export default async function handleButton( interaction: ButtonInteraction, ) { - const commandName = interaction.customId.split('/')[0]; + const url = new URL(`${CYCLIC_API}/${interaction.customId}`); + const commandName = url.pathname.split('/')[1]; const button = commands[commandName]?.button; if (!button) { diff --git a/src/renderers/evolution.tsx b/src/renderers/evolution.tsx index e89f218b5..1cce51f92 100644 --- a/src/renderers/evolution.tsx +++ b/src/renderers/evolution.tsx @@ -21,7 +21,7 @@ export default async function evolution( type: Type, { start, end, evolutionName }: ResolvedPeriod, { server, id }: ResolvedPlayer, - tankId: Type extends 'tank' ? number : never, + tankId: Type extends 'tank' ? number : null, ) { let nameDiscriminator: string | undefined; let image: string | undefined; @@ -38,8 +38,8 @@ export default async function evolution( ? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png` : undefined; } else { - nameDiscriminator = `(${resolveTankName(tankId)})`; - image = tankopedia[tankId].images.normal; + nameDiscriminator = `(${resolveTankName(tankId!)})`; + image = tankopedia[tankId!].images.normal; } let histories: Histories; @@ -53,7 +53,7 @@ export default async function evolution( }); } else { histories = await getTankHistories(server, id, { - tankId, + tankId: tankId!, start, end, includeLatestHistories: true, diff --git a/src/renderers/stats.tsx b/src/renderers/stats.tsx index c09056f69..c2d6401fa 100644 --- a/src/renderers/stats.tsx +++ b/src/renderers/stats.tsx @@ -28,7 +28,7 @@ export default async function stats( type: Type, { start, end, statsName }: ResolvedPeriod, { server, id }: ResolvedPlayer, - tankId: Type extends 'tank' ? number : never, + tankId: Type extends 'tank' ? number : null, ) { let nameDiscriminator: string | undefined; let image: string | undefined; @@ -45,8 +45,8 @@ export default async function stats( ? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png` : undefined; } else if (type === 'tank') { - nameDiscriminator = `(${resolveTankName(tankId)})`; - image = tankopedia[tankId].images.normal; + nameDiscriminator = `(${resolveTankName(tankId!)})`; + image = tankopedia[tankId!].images.normal; } const tankStats = await getTankStatsDiffed(server, id, start, end); @@ -112,11 +112,11 @@ export default async function stats( {}, ); } else { - stats = tankStats[tankId]; + stats = tankStats[tankId!]; supplementaryStats = { - WN8: calculateWN8(tankAverages[tankId].all, tankStats[tankId]), - tier: tankopedia[tankId].tier, + WN8: calculateWN8(tankAverages[tankId!].all, tankStats[tankId!]), + tier: tankopedia[tankId!].tier, }; }