diff --git a/src/commands/eligible.ts b/src/commands/eligible.ts index 334b2fb2d..142c7de45 100644 --- a/src/commands/eligible.ts +++ b/src/commands/eligible.ts @@ -3,13 +3,14 @@ import markdownEscape from 'markdown-escape'; import { CommandRegistry } from '../behaviors/interactionCreate.js'; import { NEGATIVE_COLOR, POSITIVE_COLOR } from '../constants/colors.js'; import { BlitzServer } from '../constants/servers.js'; -import getBlitzAccount from '../core/blitz/getBlitzAccount.js'; +import usernameAutocomplete from '../core/autocomplete/username.js'; +import validateUsername from '../core/blitz/validateUsername.js'; import blitzStarsLinks from '../core/blitzstars/blitzStarsLinks.js'; import getBlitzStarsAccount from '../core/blitzstars/getBlitzStarsAccount.js'; import poweredByBlitzStars from '../core/blitzstars/poweredByBlitzStars.js'; import cmdName from '../core/interaction/cmdName.js'; -import addIGNOption from '../core/options/addIGNOption.js'; import addServerChoices from '../core/options/addServerChoices.js'; +import addUsernameOption from '../core/options/addUsernameOption.js'; const CLANS = { sklld: { id: 71559, name: 'Skilled' }, @@ -35,13 +36,13 @@ export default { .setRequired(true), ) .addStringOption(addServerChoices) - .addStringOption(addIGNOption), + .addStringOption(addUsernameOption), async execute(interaction) { const clan = interaction.options.getString('clan') as keyof typeof CLANS; - const name = interaction.options.getString('name')!; + const name = interaction.options.getString('username')!; const server = interaction.options.getString('server') as BlitzServer; - const blitzAccount = await getBlitzAccount(interaction, name, server); + const blitzAccount = await validateUsername(interaction, name, server); if (!blitzAccount) return; @@ -143,4 +144,6 @@ export default { `Showing ${blitzStarsAccount.nickname}'s eligibility for ${CLANS[clan].name}`, ); }, + + autocomplete: usernameAutocomplete, } satisfies CommandRegistry; diff --git a/src/commands/inactive.ts b/src/commands/inactive.ts index 5437da189..11edbc442 100644 --- a/src/commands/inactive.ts +++ b/src/commands/inactive.ts @@ -25,7 +25,7 @@ export default { .addStringOption(addServerChoices) .addStringOption((option) => option - .setName('name') + .setName('clan') .setDescription('The clan name or tag you are checking') .setRequired(true), ) @@ -39,10 +39,10 @@ export default { ), async execute(interaction) { - const name = interaction.options.getString('name')!; + const clanName = interaction.options.getString('clan')!; const server = interaction.options.getString('server') as BlitzServer; - const clan = (await getClan(interaction, name, server)) as Clan; + const clan = (await getClan(interaction, clanName, server)) as Clan; if (!clan) return; const threshold = diff --git a/src/commands/searchplayers.ts b/src/commands/searchplayers.ts index 2950fbce0..5f4a53d79 100644 --- a/src/commands/searchplayers.ts +++ b/src/commands/searchplayers.ts @@ -4,10 +4,10 @@ import { CommandRegistry } from '../behaviors/interactionCreate.js'; import { SKILLED_COLOR } from '../constants/colors.js'; import getWargamingResponse from '../core/blitz/getWargamingResponse.js'; import cmdName from '../core/interaction/cmdName.js'; -import addIGNOption from '../core/options/addIGNOption.js'; import addServerChoices from '../core/options/addServerChoices.js'; +import addUsernameOption from '../core/options/addUsernameOption.js'; import { args } from '../core/process/args.js'; -import { Players } from '../types/players.js'; +import { AccountList } from '../types/players.js'; export default { inDevelopment: true, @@ -18,7 +18,9 @@ export default { .setName(cmdName('searchplayers')) .setDescription('Search players in a Blitz server') .addStringOption(addServerChoices) - .addStringOption(addIGNOption) + .addStringOption((option) => + addUsernameOption(option).setAutocomplete(false), + ) .addIntegerOption((option) => option .setName('limit') @@ -29,9 +31,9 @@ export default { async execute(interaction) { const server = interaction.options.getString('server')!; - const name = interaction.options.getString('name')!; + const name = interaction.options.getString('username')!; const limit = interaction.options.getInteger('limit') ?? 25; - const players = await getWargamingResponse( + const players = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/account/list/?application_id=${args['wargaming-application-id']}&search=${name}&limit=${limit}`, ); if (!players) return; diff --git a/src/commands/stats.ts b/src/commands/stats.ts index 9b93c1e14..ec13d2825 100644 --- a/src/commands/stats.ts +++ b/src/commands/stats.ts @@ -3,28 +3,22 @@ import markdownEscape from 'markdown-escape'; import { CommandRegistry } from '../behaviors/interactionCreate.js'; import { SKILLED_COLOR } from '../constants/colors.js'; import { BlitzServer } from '../constants/servers.js'; -import getBlitzAccount from '../core/blitz/getBlitzAccount.js'; +import usernameAutocomplete from '../core/autocomplete/username.js'; import getWargamingResponse from '../core/blitz/getWargamingResponse.js'; +import validateUsername from '../core/blitz/validateUsername.js'; import blitzStarsLinks from '../core/blitzstars/blitzStarsLinks.js'; import getBlitzStarsAccount from '../core/blitzstars/getBlitzStarsAccount.js'; import poweredByBlitzStars from '../core/blitzstars/poweredByBlitzStars.js'; import cleanTable from '../core/interaction/cleanTable.js'; import cmdName from '../core/interaction/cmdName.js'; -import addIGNOption from '../core/options/addIGNOption.js'; import addServerChoices from '../core/options/addServerChoices.js'; +import addUsernameOption from '../core/options/addUsernameOption.js'; import { args } from '../core/process/args.js'; import { AccountInfo, AllStats } from '../types/accountInfo.js'; import { BlitzStartsComputedPeriodicStatistics } from '../types/statistics.js'; type Period = 'today' | '30' | '90' | 'career'; -const daysInPeriod: Record = { - today: 1 * 24 * 60 * 60, - 30: 30 * 24 * 60 * 60, - 90: 90 * 24 * 60 * 60, - career: Infinity, -}; - export default { inProduction: true, inDevelopment: true, @@ -46,13 +40,13 @@ export default { .setRequired(true), ) .addStringOption(addServerChoices) - .addStringOption(addIGNOption), + .addStringOption(addUsernameOption), async execute(interaction) { const period = interaction.options.getString('period') as Period; - const name = interaction.options.getString('name')!; + const username = interaction.options.getString('username')!; const server = interaction.options.getString('server') as BlitzServer; - const blitzAccount = await getBlitzAccount(interaction, name, server); + const blitzAccount = await validateUsername(interaction, username, server); if (!blitzAccount) return; let stats: BlitzStartsComputedPeriodicStatistics; const accountInfo = await getWargamingResponse( @@ -210,4 +204,6 @@ export default { console.log(`Showing stats for ${blitzAccount.nickname}`); }, + + autocomplete: usernameAutocomplete, } satisfies CommandRegistry; diff --git a/src/commands/tanks.ts b/src/commands/tanks.ts index 6fdd6f5c2..b681a5aa2 100644 --- a/src/commands/tanks.ts +++ b/src/commands/tanks.ts @@ -3,12 +3,13 @@ import markdownEscape from 'markdown-escape'; import { CommandRegistry } from '../behaviors/interactionCreate.js'; import { SKILLED_COLOR } from '../constants/colors.js'; import { BlitzServer } from '../constants/servers.js'; -import getBlitzAccount from '../core/blitz/getBlitzAccount.js'; +import usernameAutocomplete from '../core/autocomplete/username.js'; import getWargamingResponse from '../core/blitz/getWargamingResponse.js'; +import validateUsername from '../core/blitz/validateUsername.js'; import { TANK_TYPE_EMOJIS, tankopedia } from '../core/blitzstars/tankopedia.js'; import cmdName from '../core/interaction/cmdName.js'; -import addIGNOption from '../core/options/addIGNOption.js'; import addServerChoices from '../core/options/addServerChoices.js'; +import addUsernameOption from '../core/options/addUsernameOption.js'; import { args } from '../core/process/args.js'; import { TanksStats } from '../types/tanksStats.js'; import resolveTankName from '../utilities/resolveTankName.js'; @@ -51,13 +52,13 @@ export default { .setRequired(true), ) .addStringOption(addServerChoices) - .addStringOption(addIGNOption), + .addStringOption(addUsernameOption), async execute(interaction) { const tier = interaction.options.getInteger('tier')!; - const name = interaction.options.getString('name')!; + const name = interaction.options.getString('username')!; const server = interaction.options.getString('server') as BlitzServer; - const account = await getBlitzAccount(interaction, name, server); + const account = await validateUsername(interaction, name, server); if (!account) return; const tankStats = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${args['wargaming-application-id']}&account_id=${account.account_id}`, @@ -103,4 +104,6 @@ export default { console.log(`Displaying ${account.nickname}'s owned tanks in tier ${tier}`); }, + + autocomplete: usernameAutocomplete, } satisfies CommandRegistry; diff --git a/src/commands/tankstats.ts b/src/commands/tankstats.ts index 668b4bc86..b137c2201 100644 --- a/src/commands/tankstats.ts +++ b/src/commands/tankstats.ts @@ -3,7 +3,8 @@ import markdownEscape from 'markdown-escape'; import { CommandRegistry } from '../behaviors/interactionCreate.js'; import { BlitzServer } from '../constants/servers.js'; import tanksAutocomplete from '../core/autocomplete/tanks.js'; -import getBlitzAccount from '../core/blitz/getBlitzAccount.js'; +import usernameAutocomplete from '../core/autocomplete/username.js'; +import validateUsername from '../core/blitz/validateUsername.js'; import { tankopedia } from '../core/blitzstars/tankopedia.js'; import getPeriodicStats, { Period, @@ -12,9 +13,9 @@ import cleanTable from '../core/interaction/cleanTable.js'; import cmdName from '../core/interaction/cmdName.js'; import errorEmbed from '../core/interaction/errorEmbed.js'; import sklldEmbed from '../core/interaction/sklldEmbed.js'; -import addIGNOption from '../core/options/addIGNOption.js'; import addServerChoices from '../core/options/addServerChoices.js'; import addTankChoices from '../core/options/addTankChoices.js'; +import addUsernameOption from '../core/options/addUsernameOption.js'; import resolveTankName from '../utilities/resolveTankName.js'; type Periods = '30' | '60' | '90'; @@ -40,14 +41,14 @@ export default { .setRequired(true), ) .addStringOption(addServerChoices) - .addStringOption(addIGNOption), + .addStringOption(addUsernameOption), async execute(interaction) { const server = interaction.options.getString('server') as BlitzServer; - const name = interaction.options.getString('name')!; + const name = interaction.options.getString('username')!; const tankId = interaction.options.getString('tank')!; const period = interaction.options.getString('period') as Periods; - const blitzAccount = await getBlitzAccount(interaction, name, server); + const blitzAccount = await validateUsername(interaction, name, server); if (!blitzAccount) return; const tank = tankopedia.data[tankId as unknown as number]; @@ -164,5 +165,8 @@ export default { } }, - autocomplete: tanksAutocomplete, + autocomplete: (interaction) => { + tanksAutocomplete(interaction); + usernameAutocomplete(interaction); + }, } satisfies CommandRegistry; diff --git a/src/commands/verify.ts b/src/commands/verify.ts index 7a3cb41b9..31802fb70 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -4,11 +4,12 @@ import discord from '../../discord.json' assert { type: 'json' }; import { CommandRegistry } from '../behaviors/interactionCreate.js'; import { NEGATIVE_COLOR, POSITIVE_COLOR } from '../constants/colors.js'; import { BlitzServer } from '../constants/servers.js'; -import getBlitzAccount from '../core/blitz/getBlitzAccount.js'; +import usernameAutocomplete from '../core/autocomplete/username.js'; import getWargamingResponse from '../core/blitz/getWargamingResponse.js'; +import validateUsername from '../core/blitz/validateUsername.js'; import cmdName from '../core/interaction/cmdName.js'; -import addIGNOption from '../core/options/addIGNOption.js'; import addServerChoices from '../core/options/addServerChoices.js'; +import addUsernameOption from '../core/options/addUsernameOption.js'; import { args } from '../core/process/args.js'; import { PlayerClanData } from '../types/playerClanData.js'; @@ -21,12 +22,12 @@ export default { .setName(cmdName('verify')) .setDescription("Set's the user's username to their in-game name") .addStringOption(addServerChoices) - .addStringOption(addIGNOption), + .addStringOption(addUsernameOption), async execute(interaction) { - const name = interaction.options.getString('name')!; + const name = interaction.options.getString('username')!; const server = interaction.options.getString('server') as BlitzServer; - const account = await getBlitzAccount(interaction, name, server); + const account = await validateUsername(interaction, name, server); if (!account) return; const clanData = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${args['wargaming-application-id']}&account_id=${account.account_id}&extra=clan`, @@ -128,4 +129,6 @@ export default { } } }, + + autocomplete: usernameAutocomplete, } satisfies CommandRegistry; diff --git a/src/core/autocomplete/tanks.ts b/src/core/autocomplete/tanks.ts index 355a5b185..2fdd33d7d 100644 --- a/src/core/autocomplete/tanks.ts +++ b/src/core/autocomplete/tanks.ts @@ -10,11 +10,12 @@ import { tanks } from '../blitzstars/tankopedia.js'; export default async function tanksAutocomplete( interaction: AutocompleteInteraction, ) { - const focusedValue = interaction.options.getFocused(); + const focusedOption = interaction.options.getFocused(true); + if (focusedOption.name !== 'tank') return; await interaction.respond( - focusedValue - ? go(focusedValue, tanks, { keys: ['name'], limit: 10 }).map( + focusedOption.value + ? go(focusedOption.value, tanks, { keys: ['name'], limit: 10 }).map( (item) => ({ name: item.obj.name ? item.obj.name : resolveTankName(item.obj), @@ -23,4 +24,6 @@ export default async function tanksAutocomplete( ) : [], ); + + console.log(`Tanks autocomplete for ${focusedOption.value}`); } diff --git a/src/core/autocomplete/username.ts b/src/core/autocomplete/username.ts new file mode 100644 index 000000000..03501dcaa --- /dev/null +++ b/src/core/autocomplete/username.ts @@ -0,0 +1,39 @@ +import { + ApplicationCommandOptionChoiceData, + AutocompleteInteraction, + CacheType, +} from 'discord.js'; +import { BlitzServer } from '../../constants/servers.js'; +import { AccountList } from '../../types/players.js'; +import getWargamingResponse from '../blitz/getWargamingResponse.js'; +import { args } from '../process/args.js'; + +export default async function usernameAutocomplete( + interaction: AutocompleteInteraction, +) { + const focusedOption = interaction.options.getFocused(true); + if (focusedOption.name !== 'username') return; + const server = interaction.options.getString('server') as BlitzServer; + + const players = await getWargamingResponse( + `https://api.wotblitz.${ + server ?? 'com' + }/wotb/account/list/?application_id=${ + args['wargaming-application-id'] + }&search=${focusedOption.value}&limit=10`, + ); + + await interaction.respond( + players + ? players.map( + (player) => + ({ + name: player.nickname, + value: `${player.account_id}`, + } satisfies ApplicationCommandOptionChoiceData), + ) + : [], + ); + + console.log(`Username autocomplete for ${focusedOption.value}`); +} diff --git a/src/core/blitz/getBlitzAccount.ts b/src/core/blitz/getBlitzAccount.ts deleted file mode 100644 index c5c2f7597..000000000 --- a/src/core/blitz/getBlitzAccount.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { - CacheType, - ChatInputCommandInteraction, - EmbedBuilder, -} from 'discord.js'; -import markdownEscape from 'markdown-escape'; -import { NEGATIVE_COLOR } from '../../constants/colors.js'; -import { BLITZ_SERVERS, BlitzServer } from '../../constants/servers.js'; -import { Players } from '../../types/players.js'; -import { args } from '../process/args.js'; -import getWargamingResponse from './getWargamingResponse.js'; - -export default async function getBlitzAccount( - interaction: ChatInputCommandInteraction, - name: string, - server: BlitzServer, -) { - const serverName = BLITZ_SERVERS[server]; - - const players = await getWargamingResponse( - `https://api.wotblitz.${server}/wotb/account/list/?application_id=${args['wargaming-application-id']}&search=${name}`, - ); - - if ( - players.length > 0 && - players[0].nickname.toLowerCase() === name.toLowerCase() - ) { - return players[0]; - } else { - // no exact match - await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setColor(NEGATIVE_COLOR) - .setTitle(`Account not found`) - .setDescription( - `I couldn't find "${markdownEscape( - name, - )}" in the ${serverName} server. I found ${ - players ? (players.length < 100 ? players.length : 'over 100') : 0 - } similarly spelled account${players?.length !== 1 ? 's' : ''}. ${ - players && players.length > 0 - ? `Did you mean "${markdownEscape(players[0].nickname)}"? ` - : '' - }Use the \`/verify\` command to fix any issues or re-run the command and don't make typos.`, - ), - ], - }); - - console.log(`Account not found for ${name} in ${serverName} server.`); - - return null; - } -} diff --git a/src/core/blitz/validateUsername.ts b/src/core/blitz/validateUsername.ts new file mode 100644 index 000000000..6e56cd7a1 --- /dev/null +++ b/src/core/blitz/validateUsername.ts @@ -0,0 +1,65 @@ +import { CacheType, ChatInputCommandInteraction } from 'discord.js'; +import { BLITZ_SERVERS, BlitzServer } from '../../constants/servers.js'; +import { AccountInfo } from '../../types/accountInfo.js'; +import { Account, AccountList } from '../../types/players.js'; +import errorEmbed from '../interaction/errorEmbed.js'; +import { args } from '../process/args.js'; +import getWargamingResponse from './getWargamingResponse.js'; + +export default async function validateUsername( + interaction: ChatInputCommandInteraction, + username: string, + server: BlitzServer, +) { + const serverName = BLITZ_SERVERS[server]; + + if (Number.isNaN(Number(username))) { + // string like "tresabhi" + const accountList = await getWargamingResponse( + `https://api.wotblitz.${server}/wotb/account/list/?application_id=${args['wargaming-application-id']}&limit=1&search=${username}`, + ); + + if ( + accountList?.[0]?.nickname.toLowerCase().trim() === + username.toLowerCase().trim() + ) { + return accountList[0]; + } + + await interaction.editReply({ + embeds: [ + errorEmbed( + 'Could not find user', + `I couldn't find user "${username}" in the ${serverName} server. Did you select a username from the search result?`, + ), + ], + }); + + return null; + } else { + // number like 1041988373 + const accountInfo = await getWargamingResponse( + `https://api.wotblitz.${server}/wotb/account/info/?application_id=${args['wargaming-application-id']}&account_id=${username}`, + ); + + if (accountInfo) { + const account = accountInfo[username as unknown as number]; + + return { + account_id: account.account_id, + nickname: account.nickname, + } satisfies Account; + } + + await interaction.editReply({ + embeds: [ + errorEmbed( + 'Could not find user', + `I couldn't find the user by id \`${username}\` in the ${serverName} server. Did you select a username from the search result?`, + ), + ], + }); + + return null; + } +} diff --git a/src/core/options/addIGNOption.ts b/src/core/options/addUsernameOption.ts similarity index 53% rename from src/core/options/addIGNOption.ts rename to src/core/options/addUsernameOption.ts index 99937265f..8c0a45911 100644 --- a/src/core/options/addIGNOption.ts +++ b/src/core/options/addUsernameOption.ts @@ -1,8 +1,9 @@ import { SlashCommandStringOption } from 'discord.js'; -export default function addIGNOption(option: SlashCommandStringOption) { +export default function addUsernameOption(option: SlashCommandStringOption) { return option - .setName('name') + .setName('username') .setDescription('The username you use in Blitz') + .setAutocomplete(true) .setRequired(true); } diff --git a/src/types/players.ts b/src/types/players.ts index abbcaa2b8..74bf0f73e 100644 --- a/src/types/players.ts +++ b/src/types/players.ts @@ -1,6 +1,6 @@ -export interface Player { +export interface Account { nickname: string; account_id: number; } -export type Players = Player[]; +export type AccountList = Account[];