diff --git a/src/behaviors/interactionCreate.ts b/src/behaviors/interactionCreate.ts index c1a15b1d8..bc91bf41c 100644 --- a/src/behaviors/interactionCreate.ts +++ b/src/behaviors/interactionCreate.ts @@ -77,6 +77,7 @@ export default async function interactionCreate( } try { + await interaction.deferReply(); command.execute(interaction); } catch (error) { console.error(error); @@ -86,9 +87,8 @@ export default async function interactionCreate( ephemeral: true, }); } else { - await interaction.reply({ + await interaction.editReply({ content: 'There was an error while executing this command!', - ephemeral: true, }); } } diff --git a/src/commands/debug.ts b/src/commands/debug.ts index dede137d5..84b71db09 100644 --- a/src/commands/debug.ts +++ b/src/commands/debug.ts @@ -11,7 +11,7 @@ const executionStart = new Date().getTime(); export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() @@ -22,7 +22,7 @@ export default { const currentTime = new Date().getTime(); const uptime = currentTime - executionStart; - await interaction.reply({ + await interaction.editReply({ embeds: [ new EmbedBuilder() .setTitle(`${client.user?.username} debug information`) diff --git a/src/commands/eligible.ts b/src/commands/eligible.ts index 75f031b72..378044914 100644 --- a/src/commands/eligible.ts +++ b/src/commands/eligible.ts @@ -43,106 +43,107 @@ export default { if (!extractedUser) return; const { name, server } = extractedUser; - const command = `eligible ${server} ${name} ${clan}`; - getBlitzAccount(interaction, command, name, server, async (account) => { - getBlitzStarsAccount( - interaction, - command, - account.account_id, - name, - async (data) => { - const issues: string[] = []; + const blitzAccount = await getBlitzAccount(interaction, name, server); + if (!blitzAccount) return; - if (clan === 'sklld') { - if (data.period30d.all.battles < 150) { - issues.push( - `**Battles**: ${data.period30d.all.battles} (${ - data.period30d.all.battles - 150 - })`, - ); - } - if (data.period30d.avg_tier < 8) { - issues.push( - `**Tier**: ${data.period30d.avg_tier.toFixed(2)} (${( - data.period30d.avg_tier - 8 - ).toFixed(2)})`, - ); - } - if (data.period30d.special.winrate < 60) { - issues.push( - `**Winrate**: ${data.period30d.special.winrate.toFixed(2)}% (${( - data.period30d.special.winrate - 60 - ).toFixed(2)}%)`, - ); - } - if (data.period30d.wn8 < 2100) { - issues.push( - `**WN8**: ${data.period30d.wn8.toFixed(0)} (${( - data.period30d.wn8 - 2100 - ).toFixed(0)})`, - ); - } - if (data.period30d.special.dpb < 2400) { - issues.push( - `**Damage per battle**: ${data.period30d.special.dpb.toFixed( - 0, - )} (${(data.period30d.special.dpb - 2400).toFixed(0)})`, - ); - } - } else { - if (data.statistics.all.battles < 1500) { - issues.push( - `**Battles**: ${data.statistics.all.battles} (${ - data.statistics.all.battles - 1500 - })`, - ); - } - if (data.statistics.special.winrate < 53) { - issues.push( - `**Winrate**: ${data.statistics.special.winrate.toFixed( - 2, - )}% (${(data.statistics.special.winrate - 53).toFixed(2)}%)`, - ); - } - } + const blitzStarsAccount = await getBlitzStarsAccount( + interaction, + blitzAccount.account_id, + name, + ); + if (!blitzStarsAccount) return; - const eligible = issues.length === 0; + const issues: string[] = []; - await interaction.editReply({ - embeds: [ - poweredByBlitzStars( - new EmbedBuilder() - .setTitle( - `${markdownEscape(data.nickname)} is ${ - eligible ? '' : 'not ' - }eligible for ${CLANS[clan].name}`, - ) - .setColor(eligible ? POSITIVE_COLOR : NEGATIVE_COLOR) - .setDescription( - `${ - eligible - ? `${markdownEscape( - data.nickname, - )} meets all the requirements set for ${ - CLANS[clan].name - }.` - : `${markdownEscape( - data.nickname, - )}'s shortcomings for ${CLANS[clan].name} (${ - clan === 'sklld' ? '30 days stats' : 'career stats' - }):\n\n${issues.join('\n')}` - }\n\n${blitzLinks(data)}`, - ), - ), - ], - }); + if (clan === 'sklld') { + if (blitzStarsAccount.period30d.all.battles < 150) { + issues.push( + `**Battles**: ${blitzStarsAccount.period30d.all.battles} (${ + blitzStarsAccount.period30d.all.battles - 150 + })`, + ); + } + if (blitzStarsAccount.period30d.avg_tier < 8) { + issues.push( + `**Tier**: ${blitzStarsAccount.period30d.avg_tier.toFixed(2)} (${( + blitzStarsAccount.period30d.avg_tier - 8 + ).toFixed(2)})`, + ); + } + if (blitzStarsAccount.period30d.special.winrate < 60) { + issues.push( + `**Winrate**: ${blitzStarsAccount.period30d.special.winrate.toFixed( + 2, + )}% (${(blitzStarsAccount.period30d.special.winrate - 60).toFixed( + 2, + )}%)`, + ); + } + if (blitzStarsAccount.period30d.wn8 < 2100) { + issues.push( + `**WN8**: ${blitzStarsAccount.period30d.wn8.toFixed(0)} (${( + blitzStarsAccount.period30d.wn8 - 2100 + ).toFixed(0)})`, + ); + } + if (blitzStarsAccount.period30d.special.dpb < 2400) { + issues.push( + `**Damage per battle**: ${blitzStarsAccount.period30d.special.dpb.toFixed( + 0, + )} (${(blitzStarsAccount.period30d.special.dpb - 2400).toFixed(0)})`, + ); + } + } else { + if (blitzStarsAccount.statistics.all.battles < 1500) { + issues.push( + `**Battles**: ${blitzStarsAccount.statistics.all.battles} (${ + blitzStarsAccount.statistics.all.battles - 1500 + })`, + ); + } + if (blitzStarsAccount.statistics.special.winrate < 53) { + issues.push( + `**Winrate**: ${blitzStarsAccount.statistics.special.winrate.toFixed( + 2, + )}% (${(blitzStarsAccount.statistics.special.winrate - 53).toFixed( + 2, + )}%)`, + ); + } + } - console.log( - `Showing ${data.nickname}'s eligibility for ${CLANS[clan].name}`, - ); - }, - ); + const eligible = issues.length === 0; + + await interaction.editReply({ + embeds: [ + poweredByBlitzStars( + new EmbedBuilder() + .setTitle( + `${markdownEscape(blitzStarsAccount.nickname)} is ${ + eligible ? '' : 'not ' + }eligible for ${CLANS[clan].name}`, + ) + .setColor(eligible ? POSITIVE_COLOR : NEGATIVE_COLOR) + .setDescription( + `${ + eligible + ? `${markdownEscape( + blitzStarsAccount.nickname, + )} meets all the requirements set for ${CLANS[clan].name}.` + : `${markdownEscape( + blitzStarsAccount.nickname, + )}'s shortcomings for ${CLANS[clan].name} (${ + clan === 'sklld' ? '30 days stats' : 'career stats' + }):\n\n${issues.join('\n')}` + }\n\n${blitzLinks(blitzStarsAccount)}`, + ), + ), + ], }); + + console.log( + `Showing ${blitzStarsAccount.nickname}'s eligibility for ${CLANS[clan].name}`, + ); }, } satisfies CommandRegistry; diff --git a/src/commands/help.ts b/src/commands/help.ts index 5d1d8ac56..15865ba12 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -10,7 +10,7 @@ import { client } from '../index.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() @@ -18,7 +18,7 @@ export default { .setDescription('All the help you need about the bot'), async execute(interaction) { - await interaction.reply({ + await interaction.editReply({ embeds: [ new EmbedBuilder() .setTitle(`${client.user?.username} help`) diff --git a/src/commands/inactive.ts b/src/commands/inactive.ts index ad5425a1b..c9ce90d18 100644 --- a/src/commands/inactive.ts +++ b/src/commands/inactive.ts @@ -3,8 +3,9 @@ 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 { ClanInfo } from '../types/clanInfo.js'; import { AccountInfo } from '../types/accountInfo.js'; +import { ClanInfo } from '../types/clanInfo.js'; +import { Clan } from '../types/clanList.js'; import addServerChoices from '../utilities/addServerChoices.js'; import { args } from '../utilities/args.js'; import getClan from '../utilities/getClan.js'; @@ -14,13 +15,13 @@ const DEFAULT_THRESHOLD = 7; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() .setName('inactive') .setDescription('Lists all inactive players') - .addStringOption(addServerChoices) + .addStringOption((option) => addServerChoices(option).setRequired(true)) .addStringOption((option) => option .setName('name') @@ -39,73 +40,64 @@ export default { async execute(interaction) { const name = interaction.options.getString('name')!; const server = interaction.options.getString('server') as BlitzServer; - const command = `inactive ${server} ${name}`; - getClan(interaction, command, name, server, async (clan) => { - const threshold = - interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD; - const time = new Date().getTime() / 1000; + const clan = (await getClan(interaction, name, server)) as Clan; + if (!clan) return; - getWargamingResponse( - `https://api.wotblitz.${server}/wotb/clans/info/?application_id=${args['wargaming-application-id']}&clan_id=${clan.clan_id}`, - interaction, - command, - async (clanInfo) => { - const memberIds = clanInfo[clan.clan_id].members_ids; + const threshold = + interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD; + const time = new Date().getTime() / 1000; - getWargamingResponse( - `https://api.wotblitz.${server}/wotb/account/info/?application_id=${ - args['wargaming-application-id'] - }&account_id=${memberIds.join(',')}`, - interaction, - command, - async (playerPersonalData) => { - const inactiveInfo = memberIds - .map((memberId) => { - const member = playerPersonalData[memberId]; - const inactiveDays = - (time - member.last_battle_time) / 60 / 60 / 24; + const clanInfo = await getWargamingResponse( + `https://api.wotblitz.${server}/wotb/clans/info/?application_id=${args['wargaming-application-id']}&clan_id=${clan.clan_id}`, + ); - return [member.nickname, inactiveDays] as const; - }) - .filter(([, inactiveDays]) => inactiveDays >= threshold) - .sort((a, b) => b[1] - a[1]) - .map(([nickname, inactiveDays]) => { - const days = Math.floor(inactiveDays); - return `**${markdownEscape(nickname)}**: ${days} day${ - days === 1 ? '' : 's' - }`; - }); - const hasInactiveMembers = inactiveInfo.length > 0; + const memberIds = clanInfo[clan.clan_id].members_ids; - await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setColor( - hasInactiveMembers ? NEGATIVE_COLOR : POSITIVE_COLOR, - ) - .setTitle( - hasInactiveMembers - ? `${markdownEscape(clan.name)}'s inactive members` - : `No inactive members in ${markdownEscape(clan.name)}`, - ) - .setDescription( - hasInactiveMembers - ? `Players that exceed the threshold of ${threshold} days:\n\n${inactiveInfo.join( - '\n', - )}` - : `No players exceed the threshold of ${threshold} days.`, - ), - ], - }); + const accountInfo = await getWargamingResponse( + `https://api.wotblitz.${server}/wotb/account/info/?application_id=${ + args['wargaming-application-id'] + }&account_id=${memberIds.join(',')}`, + ); - console.log( - `Displaying inactive members in ${clan.name} for ${threshold} days`, - ); - }, - ); - }, - ); + const inactiveInfo = memberIds + .map((memberId) => { + const member = accountInfo[memberId]; + const inactiveDays = (time - member.last_battle_time) / 60 / 60 / 24; + + return [member.nickname, inactiveDays] as const; + }) + .filter(([, inactiveDays]) => inactiveDays >= threshold) + .sort((a, b) => b[1] - a[1]) + .map(([nickname, inactiveDays]) => { + const days = Math.floor(inactiveDays); + return `**${markdownEscape(nickname)}**: ${days} day${ + days === 1 ? '' : 's' + }`; + }); + const hasInactiveMembers = inactiveInfo.length > 0; + + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setColor(hasInactiveMembers ? NEGATIVE_COLOR : POSITIVE_COLOR) + .setTitle( + hasInactiveMembers + ? `${markdownEscape(clan.name)}'s inactive members` + : `No inactive members in ${markdownEscape(clan.name)}`, + ) + .setDescription( + hasInactiveMembers + ? `Players that exceed the threshold of ${threshold} days:\n\n${inactiveInfo.join( + '\n', + )}` + : `No players exceed the threshold of ${threshold} days.`, + ), + ], }); + + console.log( + `Displaying inactive members in ${clan.name} for ${threshold} days`, + ); }, } satisfies CommandRegistry; diff --git a/src/commands/searchplayers.ts b/src/commands/searchplayers.ts index 4c4abad6e..4e9e33a72 100644 --- a/src/commands/searchplayers.ts +++ b/src/commands/searchplayers.ts @@ -9,15 +9,15 @@ import { args } from '../utilities/args.js'; import getWargamingResponse from '../utilities/getWargamingResponse.js'; export default { - inDevelopment: false, + inDevelopment: true, inProduction: true, inPublic: true, command: new SlashCommandBuilder() .setName('searchplayers') .setDescription('Search players in a Blitz server') - .addStringOption(addServerChoices) - .addStringOption(addIGNOption) + .addStringOption((option) => addServerChoices(option).setRequired(true)) + .addStringOption((option) => addIGNOption(option).setRequired(true)) .addIntegerOption((option) => option .setName('limit') @@ -26,34 +26,30 @@ export default { .setMaxValue(100), ), - execute(interaction) { + async execute(interaction) { const server = interaction.options.getString('server')!; const name = interaction.options.getString('name')!; const limit = interaction.options.getInteger('limit') ?? 25; - const command = `searchplayers ${server} ${name} ${limit}`; - getWargamingResponse( + const players = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/account/list/?application_id=${args['wargaming-application-id']}&search=${name}&limit=${limit}`, - interaction, - command, - (players) => { - interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setColor(SKILLED_COLOR) - .setTitle(`Player search results for "${markdownEscape(name)}"`) - .setDescription( - `\`\`\`${ - players.length === 0 - ? 'No players found.' - : players - .map((player) => markdownEscape(player.nickname)) - .join('\n') - }\`\`\``, - ), - ], - }); - }, ); + + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setColor(SKILLED_COLOR) + .setTitle(`Player search results for "${markdownEscape(name)}"`) + .setDescription( + `\`\`\`${ + players.length === 0 + ? 'No players found.' + : players + .map((player) => markdownEscape(player.nickname)) + .join('\n') + }\`\`\``, + ), + ], + }); }, } satisfies CommandRegistry; diff --git a/src/commands/stats.ts b/src/commands/stats.ts index ad5257458..b9efacf17 100644 --- a/src/commands/stats.ts +++ b/src/commands/stats.ts @@ -3,7 +3,7 @@ import markdownEscape from 'markdown-escape'; import { CommandRegistry } from '../behaviors/interactionCreate.js'; import { SKILLED_COLOR } from '../constants/colors.js'; import { AccountInfo, AllStats } from '../types/accountInfo.js'; -import { PeriodStatistics } from '../types/statistics.js'; +import { PeriodStatistics, PlayerStatistics } from '../types/statistics.js'; import addIGNOption from '../utilities/addIGNOption.js'; import addServerChoices from '../utilities/addServerChoices.js'; import { args } from '../utilities/args.js'; @@ -18,7 +18,7 @@ type Period = 'today' | '30' | '90' | 'career'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() @@ -48,185 +48,156 @@ export default { const { name, server } = extractedUser; const command = `stats ${server} ${name} ${period}`; - getBlitzAccount( + const blitzAccount = await getBlitzAccount(interaction, name, server); + if (!blitzAccount) return; + + const blitzStarsAccount = (await getBlitzStarsAccount( interaction, - command, + blitzAccount.account_id, name, - server, - async (blitzAccount) => { - getBlitzStarsAccount( - interaction, - command, - blitzAccount.account_id, - name, - async (blitzStarsAccount) => { - async function reply(stats: PeriodStatistics) { - await interaction.editReply({ - embeds: [ - poweredByBlitzStars( - new EmbedBuilder() - .setColor(SKILLED_COLOR) - .setTitle( - `${markdownEscape(blitzStarsAccount.nickname)}'s ${ - period === 'career' ? period : `${period} day` - } stats`, - ) - .setDescription( - `${ - period === 'today' - ? '**⚠ Some stats are not available**\n\n' - : '' - }${[ - ['Winrate', `${stats.special.winrate.toFixed(2)}%`], - [ - 'Survival', - `${stats.special.survivalRate.toFixed(2)}%`, - ], - [ - 'Accuracy', - `${( - (stats.all.hits / stats.all.shots) * - 100 - ).toFixed(2)}%`, - ], - ['WN8', stats.wn8.toFixed(0)], - ['WN7', stats.wn7.toFixed(0)], - [], - ['Battles', stats.all.battles], - ['Wins', stats.all.wins], - ['Losses', stats.all.losses], - ['Average tier', stats.avg_tier.toFixed(2)], - [], - [ - 'Shots per battle', - (stats.all.shots / stats.all.battles).toFixed(2), - ], - ['Hits per battle', stats.special.hpb.toFixed(2)], - ['Damage per battle', stats.special.dpb.toFixed(0)], - ['Kills per battle', stats.special.kpb.toFixed(2)], - ['Spots per battle', stats.special.spb.toFixed(2)], - [ - 'XP per battle', - (stats.all.xp / stats.all.battles).toFixed(0), - ], - [], - [ - 'Damage ratio', - stats.special.damageRatio.toFixed(2), - ], - [ - 'Kills to death ratio', - stats.special.kdr.toFixed(2), - ], - ] - .filter((array) => array[1] !== `${-Infinity}`) - .map((array) => - array.length === 0 - ? '' - : `**${array[0]}**: ${array[1]}`, - ) - .join('\n')}\n\n${blitzLinks(blitzStarsAccount)}`, - ), - ), - ], - }); + )) as PlayerStatistics; + if (!blitzStarsAccount) return; - console.log( - `Showing ${blitzStarsAccount.nickname}'s ${period} stats`, - ); - } + async function reply(stats: PeriodStatistics) { + await interaction.editReply({ + embeds: [ + poweredByBlitzStars( + new EmbedBuilder() + .setColor(SKILLED_COLOR) + .setTitle( + `${markdownEscape(blitzStarsAccount.nickname)}'s ${ + period === 'career' ? period : `${period} day` + } stats`, + ) + .setDescription( + `${ + period === 'today' + ? '**⚠ "Today" stats are in development. Some stats are not available/wrong.**\n\n' + : '' + }${[ + ['Winrate', `${stats.special.winrate.toFixed(2)}%`], + ['Survival', `${stats.special.survivalRate.toFixed(2)}%`], + [ + 'Accuracy', + `${((stats.all.hits / stats.all.shots) * 100).toFixed(2)}%`, + ], + ['WN8', stats.wn8.toFixed(0)], + ['WN7', stats.wn7.toFixed(0)], + [], + ['Battles', stats.all.battles], + ['Wins', stats.all.wins], + ['Losses', stats.all.losses], + ['Average tier', stats.avg_tier.toFixed(2)], + [], + [ + 'Shots per battle', + (stats.all.shots / stats.all.battles).toFixed(2), + ], + ['Hits per battle', stats.special.hpb.toFixed(2)], + ['Damage per battle', stats.special.dpb.toFixed(0)], + ['Kills per battle', stats.special.kpb.toFixed(2)], + ['Spots per battle', stats.special.spb.toFixed(2)], + [ + 'XP per battle', + (stats.all.xp / stats.all.battles).toFixed(0), + ], + [], + ['Damage ratio', stats.special.damageRatio.toFixed(2)], + ['Kills to death ratio', stats.special.kdr.toFixed(2)], + ] + .filter((array) => array[1] !== `${-Infinity}`) + .map((array) => + array.length === 0 ? '' : `**${array[0]}**: ${array[1]}`, + ) + .join('\n')}\n\n${blitzLinks(blitzStarsAccount)}`, + ), + ), + ], + }); - let stats: PeriodStatistics | null = null; + console.log(`Showing ${blitzStarsAccount.nickname}'s ${period} stats`); + } - if (period === 'career') { - stats = blitzStarsAccount.statistics; - } else if (period === 'today') { - getWargamingResponse( - `https://api.wotblitz.${server}/wotb/account/info/?application_id=${args['wargaming-application-id']}&account_id=${blitzAccount.account_id}`, - interaction, - command, - async (accountInfo) => { - const a1 = blitzStarsAccount.statistics; - const a2 = accountInfo[blitzAccount.account_id].statistics; + let stats: PeriodStatistics | null = null; - const battles = a2.all.battles - a1.all.battles; + if (period === 'career') { + stats = blitzStarsAccount.statistics; + } else if (period === 'today') { + const accountInfo = await getWargamingResponse( + `https://api.wotblitz.${server}/wotb/account/info/?application_id=${args['wargaming-application-id']}&account_id=${blitzAccount.account_id}`, + ); - function pb(value: (allStats: AllStats) => number) { - return (value(a2.all) - value(a1.all)) / battles; - } - function diff(value: (allStats: AllStats) => number) { - return value(a2.all) - value(a1.all); - } - function span( - value1: (allStats: AllStats) => number, - value2: (allStats: AllStats) => number, - ) { - return ( - (value1(a2.all) - value1(a1.all)) / - (value2(a2.all) - value2(a1.all)) - ); - } + const a1 = blitzStarsAccount.statistics; + const a2 = accountInfo[blitzAccount.account_id].statistics; - stats = { - all: { - battles, - capture_points: pb((a) => a.capture_points), - damage_dealt: pb((a) => a.damage_dealt), - damage_received: pb((a) => a.damage_received), - dropped_capture_points: diff( - (a) => a.dropped_capture_points, - ), - frags: pb((a) => a.frags), - frags8p: pb((a) => a.frags8p), - hits: diff((a) => a.hits), - losses: diff((a) => a.losses), - max_frags: pb((a) => a.max_frags), - max_frags_tank_id: pb((a) => a.max_frags_tank_id), - max_xp: pb((a) => a.max_xp), - max_xp_tank_id: pb((a) => a.max_xp_tank_id), - shots: diff((a) => a.shots), - spotted: pb((a) => a.spotted), - survived_battles: pb((a) => a.survived_battles), - win_and_survived: pb((a) => a.win_and_survived), - wins: diff((a) => a.wins), - xp: diff((a) => a.xp), - }, - special: { - winrate: pb((a) => a.wins) * 100, - dpb: pb((a) => a.damage_dealt), - survivalRate: pb((a) => a.survived_battles) * 100, - damageRatio: span( - (a) => a.damage_dealt, - (a) => a.damage_received, - ), - hitRate: pb((a) => a.hits / a.shots), - hpb: pb((a) => a.hits), - kdr: - diff((a) => a.frags) / - diff((a) => a.battles - a.survived_battles), - kpb: pb((a) => a.frags), - spb: pb((a) => a.spotted), - }, + const battles = a2.all.battles - a1.all.battles; - avg_tier: -Infinity, - pwp: -Infinity, - wn7: -Infinity, - wn8: -Infinity, - }; - - reply(stats); - }, - ); - } else { - stats = blitzStarsAccount[`period${period}d`]; - } - - if (stats) { - reply(stats); - } - }, + function pb(value: (allStats: AllStats) => number) { + return (value(a2.all) - value(a1.all)) / battles; + } + function diff(value: (allStats: AllStats) => number) { + return value(a2.all) - value(a1.all); + } + function span( + value1: (allStats: AllStats) => number, + value2: (allStats: AllStats) => number, + ) { + return ( + (value1(a2.all) - value1(a1.all)) / (value2(a2.all) - value2(a1.all)) ); - }, - ); + } + + stats = { + all: { + battles, + capture_points: pb((a) => a.capture_points), + damage_dealt: pb((a) => a.damage_dealt), + damage_received: pb((a) => a.damage_received), + dropped_capture_points: diff((a) => a.dropped_capture_points), + frags: pb((a) => a.frags), + frags8p: pb((a) => a.frags8p), + hits: diff((a) => a.hits), + losses: diff((a) => a.losses), + max_frags: pb((a) => a.max_frags), + max_frags_tank_id: pb((a) => a.max_frags_tank_id), + max_xp: pb((a) => a.max_xp), + max_xp_tank_id: pb((a) => a.max_xp_tank_id), + shots: diff((a) => a.shots), + spotted: pb((a) => a.spotted), + survived_battles: pb((a) => a.survived_battles), + win_and_survived: pb((a) => a.win_and_survived), + wins: diff((a) => a.wins), + xp: diff((a) => a.xp), + }, + special: { + winrate: pb((a) => a.wins) * 100, + dpb: pb((a) => a.damage_dealt), + survivalRate: pb((a) => a.survived_battles) * 100, + damageRatio: span( + (a) => a.damage_dealt, + (a) => a.damage_received, + ), + hitRate: pb((a) => a.hits / a.shots), + hpb: pb((a) => a.hits), + kdr: + diff((a) => a.frags) / diff((a) => a.battles - a.survived_battles), + kpb: pb((a) => a.frags), + spb: pb((a) => a.spotted), + }, + + avg_tier: -Infinity, + pwp: -Infinity, + wn7: -Infinity, + wn8: -Infinity, + }; + + reply(stats); + } else { + stats = blitzStarsAccount[`period${period}d`]; + } + + if (stats) { + reply(stats); + } }, } satisfies CommandRegistry; diff --git a/src/commands/tanks.ts b/src/commands/tanks.ts index 4eb97c7ac..ca02b947d 100644 --- a/src/commands/tanks.ts +++ b/src/commands/tanks.ts @@ -34,7 +34,7 @@ const COMP_TANKS = [ export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() @@ -53,64 +53,55 @@ export default { async execute(interaction) { const tier = interaction.options.getInteger('tier')!; - - await interaction.deferReply(); - const extractedUser = await extractUser(interaction); if (!extractedUser) return; const { name, server } = extractedUser; - const command = `tanks ${server} ${name} ${tier}`; - getBlitzAccount(interaction, command, name, server, async (account) => { - getWargamingResponse( - `https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${args['wargaming-application-id']}&account_id=${account.account_id}`, - interaction, - command, - async (tankStats) => { - const tanks = tankStats[account.account_id] - .map((tankData) => tankopedia.data[tankData.tank_id]) - .filter((tank) => tank.tier === tier); + const account = await getBlitzAccount(interaction, name, server); + if (!account) return; - await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle( - `${markdownEscape(account.nickname)}'s owned ${ - tier === null ? '' : `tier ${tier} ` - }tanks`, - ) - .setDescription( - `${ - tanks.length === 0 - ? `No tanks found in tier ${tier}` - : tanks - .map( - (tank) => - `${TANK_TYPE_EMOJIS[tank.type]} ${markdownEscape( - tank.name, - )} ${tank.is_premium ? '⭐' : ''}${ - COMP_TANKS.includes(tank.tank_id) ? 'šŸ†' : '' - }`, - ) - .join('\n') - }\n\n**Legend**\n⭐ = Premium/Collector\n${ - tier === 10 ? 'šŸ† = Comp Tank\n' : '' - }${TANK_TYPE_EMOJIS.heavyTank} = Heavy\n${ - TANK_TYPE_EMOJIS.mediumTank - } = Medium\n${TANK_TYPE_EMOJIS.lightTank} = Light\n${ - TANK_TYPE_EMOJIS['AT-SPG'] - } = Tank Destroyer`, - ) - .setColor(SKILLED_COLOR), - ], - }); + const tankStats = await getWargamingResponse( + `https://api.wotblitz.${server}/wotb/tanks/stats/?application_id=${args['wargaming-application-id']}&account_id=${account.account_id}`, + ); + const tanks = tankStats[account.account_id] + .map((tankData) => tankopedia.data[tankData.tank_id]) + .filter((tank) => tank.tier === tier); - console.log( - `Displaying ${account.nickname}'s owned tanks in tier ${tier}`, - ); - }, - ); + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setTitle( + `${markdownEscape(account.nickname)}'s owned ${ + tier === null ? '' : `tier ${tier} ` + }tanks`, + ) + .setDescription( + `${ + tanks.length === 0 + ? `No tanks found in tier ${tier}` + : tanks + .map( + (tank) => + `${TANK_TYPE_EMOJIS[tank.type]} ${markdownEscape( + tank.name, + )} ${tank.is_premium ? '⭐' : ''}${ + COMP_TANKS.includes(tank.tank_id) ? 'šŸ†' : '' + }`, + ) + .join('\n') + }\n\n**Legend**\n⭐ = Premium/Collector\n${ + tier === 10 ? 'šŸ† = Comp Tank\n' : '' + }${TANK_TYPE_EMOJIS.heavyTank} = Heavy\n${ + TANK_TYPE_EMOJIS.mediumTank + } = Medium\n${TANK_TYPE_EMOJIS.lightTank} = Light\n${ + TANK_TYPE_EMOJIS['AT-SPG'] + } = Tank Destroyer`, + ) + .setColor(SKILLED_COLOR), + ], }); + + console.log(`Displaying ${account.nickname}'s owned tanks in tier ${tier}`); }, } satisfies CommandRegistry; diff --git a/src/commands/verify.ts b/src/commands/verify.ts index c77e10df2..f1175eaf7 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -13,7 +13,7 @@ import getWargamingResponse from '../utilities/getWargamingResponse.js'; export default { inProduction: true, - inDevelopment: false, + inDevelopment: true, inPublic: true, command: new SlashCommandBuilder() @@ -22,131 +22,114 @@ export default { .addStringOption((option) => addServerChoices(option).setRequired(true)) .addStringOption((Option) => addIGNOption(Option).setRequired(true)), - execute(interaction) { + async execute(interaction) { let name = interaction.options.getString('name')!; const server = interaction.options.getString('server') as BlitzServer; - const command = `verify ${server} ${name}`; + const account = await getBlitzAccount(interaction, name, server); - getBlitzAccount(interaction, command, name, server, async (account) => { - name = account.nickname; + if (!account) return; - getWargamingResponse( - `https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${args['wargaming-application-id']}&account_id=${account.account_id}&extra=clan`, - interaction, - command, - async (clanData) => { - const clan = clanData[account.account_id].clan; - const clanTag = clan === null ? '' : ` [${clan!.tag}]`; + name = account.nickname; - if ( - !interaction.guild?.members.me?.permissions.has('ManageNicknames') - ) { - interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setColor(NEGATIVE_COLOR) - .setTitle( - `${markdownEscape( - interaction.user.username, - )} failed to verify`, - ) - .setDescription( - "I don't have the permission to change your nickname.", - ), - ], - }); + const clanData = await getWargamingResponse( + `https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${args['wargaming-application-id']}&account_id=${account.account_id}&extra=clan`, + ); + const clan = clanData[account.account_id]?.clan; + const clanTag = clan ? ` [${clan!.tag}]` : ''; - console.warn( - `${interaction.user.username} failed to verify because of no nickname permission.`, - ); + if (!interaction.guild?.members.me?.permissions.has('ManageNicknames')) { + interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setColor(NEGATIVE_COLOR) + .setTitle( + `${markdownEscape(interaction.user.username)} failed to verify`, + ) + .setDescription( + "I don't have the permission to change your nickname.", + ), + ], + }); - return; - } - - if (interaction.member) { - const member = interaction.guild?.members.cache.get( - interaction.member?.user.id, - ); - - if (member) { - await member - ?.setNickname( - `${name} (${BLITZ_SERVERS_SHORT[server]})${clanTag}`, - ) - .then(async () => { - if (interaction.guildId === discord.guild_id) { - if ( - !interaction.guild?.members.me?.permissions.has( - 'ManageRoles', - ) - ) { - await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setColor(NEGATIVE_COLOR) - .setTitle( - `${markdownEscape( - interaction.user.username, - )} failed to verify`, - ) - .setDescription( - "I don't have the permission to change your manage roles.", - ), - ], - }); - - console.warn( - `${interaction.user.username} failed to verify because of no manage roles permission.`, - ); - - return; - } - - await member.roles.remove(discord.verify_role); - await member.roles.add(discord.peasant_role); - } - - await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setColor(POSITIVE_COLOR) - .setTitle(`${interaction.user.username} is verified`) - .setDescription( - `The user is now verified as ${markdownEscape( - name, - )} (${BLITZ_SERVERS_SHORT[server]})${markdownEscape( - clanTag, - )}`, - ), - ], - }); - - console.log( - `${interaction.user.username} verified as ${name} (${BLITZ_SERVERS_SHORT[server]})${clanTag}`, - ); - }) - .catch(async () => { - await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setColor(NEGATIVE_COLOR) - .setTitle( - `${interaction.user.username} failed to verify`, - ) - .setDescription( - "I can't change your nickname because you have higher permissions than me.", - ), - ], - }); - - console.warn( - 'Failed to verify because of higher permissions.', - ); - }); - } - } - }, + console.warn( + `${interaction.user.username} failed to verify because of no nickname permission.`, ); - }); + + return; + } + + if (interaction.member) { + const member = interaction.guild?.members.cache.get( + interaction.member?.user.id, + ); + + if (member) { + try { + await member.setNickname( + `${name} (${BLITZ_SERVERS_SHORT[server]})${clanTag}`, + ); + + if (interaction.guildId === discord.guild_id) { + if ( + !interaction.guild?.members.me?.permissions.has('ManageRoles') + ) { + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setColor(NEGATIVE_COLOR) + .setTitle( + `${markdownEscape( + interaction.user.username, + )} failed to verify`, + ) + .setDescription( + "I don't have the permission to change your manage roles.", + ), + ], + }); + + console.warn( + `${interaction.user.username} failed to verify because of no manage roles permission.`, + ); + + return; + } + + await member.roles.remove(discord.verify_role); + await member.roles.add(discord.peasant_role); + } + + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setColor(POSITIVE_COLOR) + .setTitle(`${interaction.user.username} is verified`) + .setDescription( + `The user is now verified as ${markdownEscape(name)} (${ + BLITZ_SERVERS_SHORT[server] + })${markdownEscape(clanTag)}`, + ), + ], + }); + + console.log( + `${interaction.user.username} verified as ${name} (${BLITZ_SERVERS_SHORT[server]})${clanTag}`, + ); + } catch (error) { + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setColor(NEGATIVE_COLOR) + .setTitle(`${interaction.user.username} failed to verify`) + .setDescription( + "I can't change your nickname because you have higher permissions than me.", + ), + ], + }); + + console.warn('Failed to verify because of higher permissions.'); + } + } + } }, } satisfies CommandRegistry; diff --git a/src/types/playerClanData.ts b/src/types/playerClanData.ts index 8ac882a40..4dab4ff4d 100644 --- a/src/types/playerClanData.ts +++ b/src/types/playerClanData.ts @@ -14,5 +14,5 @@ export type PlayerClanData = { clan_id: number; emblem_set_id: number; }; - }; + } | null; }; diff --git a/src/utilities/getBlitzAccount.ts b/src/utilities/getBlitzAccount.ts index f0125f3a2..9c39b73e1 100644 --- a/src/utilities/getBlitzAccount.ts +++ b/src/utilities/getBlitzAccount.ts @@ -6,58 +6,49 @@ import { import markdownEscape from 'markdown-escape'; import { NEGATIVE_COLOR } from '../constants/colors.js'; import { BLITZ_SERVERS, BlitzServer } from '../constants/servers.js'; -import { Player, Players } from '../types/players.js'; +import { Players } from '../types/players.js'; import { args } from './args.js'; import getWargamingResponse from './getWargamingResponse.js'; export default async function getBlitzAccount( interaction: ChatInputCommandInteraction, - command: string, name: string, server: BlitzServer, - callback: (account: Player) => void, ) { const serverName = BLITZ_SERVERS[server]; - getWargamingResponse( + const players = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/account/list/?application_id=${args['wargaming-application-id']}&search=${name}`, - interaction, - command, - async (players) => { - if ( - players.length > 0 && - players[0].nickname.toLowerCase() === name.toLowerCase() - ) { - callback(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)}"? ` - : '' - }Re-run the command and don't make typos.`, - ), - ], - }); - - console.log(`Account not found for ${name} in ${serverName} server.`); - } - }, ); + + 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/utilities/getBlitzStarsAccount.ts b/src/utilities/getBlitzStarsAccount.ts index ece42777d..d07cb1da7 100644 --- a/src/utilities/getBlitzStarsAccount.ts +++ b/src/utilities/getBlitzStarsAccount.ts @@ -1,43 +1,35 @@ -import { - CacheType, - ChatInputCommandInteraction, - EmbedBuilder, -} from 'discord.js'; +import { CacheType, ChatInputCommandInteraction } from 'discord.js'; import markdownEscape from 'markdown-escape'; import fetch from 'node-fetch'; -import { NEGATIVE_COLOR } from '../constants/colors.js'; import { PlayerStatistics } from '../types/statistics.js'; -import blitzStarsError from './blitzStarsError.js'; +import errorEmbed from './errorEmbed.js'; export default async function getBlitzStarsAccount( interaction: ChatInputCommandInteraction, - command: string, accountId: number, name: string, - callback: (account: PlayerStatistics) => void, ) { - if (!interaction.deferred) await interaction.deferReply(); + try { + const response = await fetch( + `https://www.blitzstars.com/api/top/player/${accountId}`, + ); + const parsed = (await response.json()) as PlayerStatistics; - fetch(`https://www.blitzstars.com/api/top/player/${accountId}`) - .then(async (response) => { - const data = (await response.json()) as PlayerStatistics; + if (parsed.statistics) { + return parsed; + } else throw new Error('No data to display'); + } catch (error) { + await interaction.editReply({ + embeds: [ + errorEmbed( + 'No data to display', + `${markdownEscape(name)} is not tracked by BlitzStars.`, + ), + ], + }); - if (data.statistics) { - callback(data); - } else { - await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle('No data to display') - .setDescription( - `${markdownEscape(name)} is not tracked by BlitzStars.`, - ) - .setColor(NEGATIVE_COLOR), - ], - }); + console.log(`${name} is not tracked by BlitzStars.`); - console.log(`${name} is not tracked by BlitzStars.`); - } - }) - .catch((error) => blitzStarsError(interaction, error, command)); + return null; + } } diff --git a/src/utilities/getClan.ts b/src/utilities/getClan.ts index 0824ab93a..9db47da41 100644 --- a/src/utilities/getClan.ts +++ b/src/utilities/getClan.ts @@ -6,55 +6,52 @@ import { import markdownEscape from 'markdown-escape'; import { NEGATIVE_COLOR } from '../constants/colors.js'; import { BLITZ_SERVERS, BlitzServer } from '../constants/servers.js'; -import { Clan, ClanList } from '../types/clanList.js'; +import { ClanList } from '../types/clanList.js'; import { args } from './args.js'; import getWargamingResponse from './getWargamingResponse.js'; export default async function getClan( interaction: ChatInputCommandInteraction, - command: string, name: string, server: BlitzServer, - callback: (account: Clan) => void, ) { const serverName = BLITZ_SERVERS[server]; - getWargamingResponse( + const clans = await getWargamingResponse( `https://api.wotblitz.${server}/wotb/clans/list/?application_id=${args['wargaming-application-id']}&search=${name}`, - interaction, - command, - async (clans) => { - if ( - clans.length > 0 && - (clans[0].name.toLowerCase() === name.toLowerCase() || - clans[0].tag.toLowerCase() === name.toLowerCase()) - ) { - callback(clans[0]); - } else { - // no exact match - await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setColor(NEGATIVE_COLOR) - .setTitle(`Clan not found`) - .setDescription( - `I couldn't find "${markdownEscape( - name, - )}" in the ${serverName} server. I found ${ - clans ? (clans.length < 100 ? clans.length : 'over 100') : 0 - } similarly spelled clan${clans?.length !== 1 ? 's' : ''}. ${ - clans && clans.length > 0 - ? `Did you mean "${markdownEscape( - clans[0].name, - )}" or "${markdownEscape(clans[0].tag)}"? ` - : '' - }Re-run the command, don't make typos.`, - ), - ], - }); - - console.log(`Clan not found for ${name} in ${serverName} server.`); - } - }, ); + + if ( + clans.length > 0 && + (clans[0].name.toLowerCase() === name.toLowerCase() || + clans[0].tag.toLowerCase() === name.toLowerCase()) + ) { + return clans[0]; + } else { + // no exact match + await interaction.editReply({ + embeds: [ + new EmbedBuilder() + .setColor(NEGATIVE_COLOR) + .setTitle(`Clan not found`) + .setDescription( + `I couldn't find "${markdownEscape( + name, + )}" in the ${serverName} server. I found ${ + clans ? (clans.length < 100 ? clans.length : 'over 100') : 0 + } similarly spelled clan${clans?.length !== 1 ? 's' : ''}. ${ + clans && clans.length > 0 + ? `Did you mean "${markdownEscape( + clans[0].name, + )}" or "${markdownEscape(clans[0].tag)}"? ` + : '' + }Re-run the command, don't make typos.`, + ), + ], + }); + + console.log(`Clan not found for ${name} in ${serverName} server.`); + + return null; + } } diff --git a/src/utilities/getWargamingResponse.ts b/src/utilities/getWargamingResponse.ts index 560ff297f..12a341037 100644 --- a/src/utilities/getWargamingResponse.ts +++ b/src/utilities/getWargamingResponse.ts @@ -1,28 +1,17 @@ -import { CacheType, ChatInputCommandInteraction } from 'discord.js'; import fetch from 'node-fetch'; import { WargamingResponse } from '../types/wargamingResponse.js'; -import wargamingError from './wargamingError.js'; export default async function getWargamingResponse( url: string, - interaction: ChatInputCommandInteraction, - command: string, - callback: (data: Data) => void, ) { - if (!interaction.deferred) await interaction.deferReply(); + let parsed: WargamingResponse; - fetch(url) - .then((response) => response.json() as Promise>) - .then((response) => { - if (response.status === 'ok') { - callback(response.data); - } else { - wargamingError( - interaction, - new Error(`Status ${response.status}`), - command, - ); - } - }) - .catch((error) => wargamingError(interaction, error, command)); + const response = await fetch(url); + parsed = (await response.json()) as WargamingResponse; + + if (parsed.status === 'ok') { + return parsed.data; + } else { + throw new Error(`Wargaming response status ${parsed.status}`); + } }