diff --git a/.vscode/settings.json b/.vscode/settings.json index e44870599..57e6e0dab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,13 +8,18 @@ "kranvagn", "outfile", "ownedtanks", + "playerachievements", + "playerinfo", "Resvg", + "searchclans", "searchplayers", + "searchtanks", "SKLLD", "Smri", "startcase", "strvk", "tankopedia", + "tankstats", "Unicum", "Unregistering", "vickers", diff --git a/package.json b/package.json index b7e103244..eb623b4c1 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "src/index.ts", "type": "module", "scripts": { - "dev": "yarn build && yarn start", - "start": "node --enable-source-maps dist/index.cjs", + "dev": "yarn build --dev && yarn start", + "start": "node --enable-source-maps --trace-warnings dist/index.cjs", "zip": "zip -r dist.zip dist", "build": "node scripts/build", "lint": "tsc" diff --git a/scripts/build.js b/scripts/build.js index 975ff801b..64d95a31f 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,10 +1,14 @@ import { build } from 'esbuild'; import { rmSync } from 'fs'; +import { argv } from 'process'; + +const isDev = argv.includes('--dev'); +const isProd = !isDev; console.log('Removing dist...'); rmSync('dist', { recursive: true, force: true }); -console.log('Creating context...'); +console.log('Building...'); build({ entryPoints: ['src/index.ts'], outfile: 'dist/index.cjs', @@ -15,9 +19,9 @@ build({ '.ttf': 'file', }, - bundle: true, - sourcemap: true, + bundle: isProd, + sourcemap: isProd, minifyIdentifiers: false, // causes errors - minifySyntax: true, - minifyWhitespace: true, + minifySyntax: isProd, + minifyWhitespace: isProd, }); diff --git a/src/commands/debug.ts b/src/commands/debug.ts index 278060b45..9b7175480 100644 --- a/src/commands/debug.ts +++ b/src/commands/debug.ts @@ -20,31 +20,27 @@ export default { .setName(cmdName('debug')) .setDescription('Debug information about the bot'), - async execute(interaction) { + execute(interaction) { const currentTime = new Date().getTime(); const uptime = currentTime - executionStart; - await interaction.editReply({ - embeds: [ - infoEmbed( - `${client.user?.username} debug information`, - cleanTable([ - ['Version', packageJSON.version], - ['Client ID', getClientId()], - ['Tag', interaction.client.user.tag], - [ - 'Uptime', - `${Math.floor(uptime / 1000 / 60 / 60 / 24)}d ${Math.floor( - (uptime / 1000 / 60 / 60) % 24, - )}h ${Math.floor((uptime / 1000 / 60) % 60)}m ${Math.floor( - (uptime / 1000) % 60, - )}s ${Math.floor(uptime % 1000)}ms`, - ], - ['Tankopedia cached', `${tankopedia !== undefined}`], - ['Tank averages cached', `${tankAverages !== undefined}`], - ]), - ), - ], - }); + return infoEmbed( + `${client.user?.username} debug information`, + cleanTable([ + ['Version', packageJSON.version], + ['Client ID', getClientId()], + ['Tag', interaction.client.user.tag], + [ + 'Uptime', + `${Math.floor(uptime / 1000 / 60 / 60 / 24)}d ${Math.floor( + (uptime / 1000 / 60 / 60) % 24, + )}h ${Math.floor((uptime / 1000 / 60) % 60)}m ${Math.floor( + (uptime / 1000) % 60, + )}s ${Math.floor(uptime % 1000)}ms`, + ], + ['Tankopedia cached', `${tankopedia !== undefined}`], + ['Tank averages cached', `${tankAverages !== undefined}`], + ]), + ); }, } satisfies CommandRegistry; diff --git a/src/commands/eligible.ts b/src/commands/eligible.ts index f6a65bd33..8dc80c208 100644 --- a/src/commands/eligible.ts +++ b/src/commands/eligible.ts @@ -166,10 +166,6 @@ export default { )}`; } - await interaction.editReply({ - embeds: isEligible - ? [positiveEmbed(title, body)] - : [negativeEmbed(title, body)], - }); + return (isEligible ? positiveEmbed : negativeEmbed)(title, body); }, } satisfies CommandRegistry; diff --git a/src/commands/help.ts b/src/commands/help.ts index 327e465bc..292c3d379 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -1,7 +1,7 @@ -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { SlashCommandBuilder } from 'discord.js'; import discord from '../../discord.json' assert { type: 'json' }; -import { ACCENT_COLOR } from '../constants/colors.js'; import cmdName from '../core/interaction/cmdName.js'; +import infoEmbed from '../core/interaction/infoEmbed.js'; import { CommandRegistry, guildCommands, @@ -11,33 +11,27 @@ import { client } from '../index.js'; export default { inProduction: true, - inDevelopment: true, + inDevelopment: false, inPublic: true, command: new SlashCommandBuilder() .setName(cmdName('help')) .setDescription('All the help you need about the bot'), - async execute(interaction) { - await interaction.editReply({ - embeds: [ - new EmbedBuilder() - .setTitle(`${client.user?.username} help`) - .setColor(ACCENT_COLOR) - .setDescription( - `**About**\n${ - client.user?.username - } automates many mundane tasks and provide statistics in numerous flexible ways.\n\nMade by TresAbhi from the Skilled [SKLLD] clan.\nGitHub: https://github.com/tresabhi/blitzkrieg\nDiscord server: https://discord.gg/nDt7AjGJQH\nSkilled server: https://discord.gg/ZPvcEG7DS8\n\n**Commands**\n${(interaction.guildId === - discord.sklld_guild_id - ? [...guildCommands, ...publicCommands] - : publicCommands - ) - .map((command) => `**/${command.name}**: ${command.description}`) - .join( - '\n', - )}\n\n*I am a new bot; I may make mistakes and go offline unexpectedly from time to time.*`, - ), - ], - }); + execute(interaction) { + return infoEmbed( + `${client.user?.username} help`, + `**About**\n${ + client.user?.username + } automates many mundane tasks and provide statistics in numerous flexible ways.\n\nMade by TresAbhi from the Skilled [SKLLD] clan.\nGitHub: https://github.com/tresabhi/blitzkrieg\nDiscord server: https://discord.gg/nDt7AjGJQH\nSkilled server: https://discord.gg/ZPvcEG7DS8\n\n**Commands**\n${(interaction.guildId === + discord.sklld_guild_id + ? [...guildCommands, ...publicCommands] + : publicCommands + ) + .map((command) => `**/${command.name}**: ${command.description}`) + .join( + '\n', + )}\n\n*I am a new bot; I may make mistakes and go offline unexpectedly from time to time.*`, + ); }, } satisfies CommandRegistry; diff --git a/src/commands/inactive.tsx b/src/commands/inactive.tsx index 9b91fa8eb..8c781ee85 100644 --- a/src/commands/inactive.tsx +++ b/src/commands/inactive.tsx @@ -11,7 +11,6 @@ import getWargamingResponse from '../core/blitz/getWargamingResponse.js'; import cmdName from '../core/interaction/cmdName.js'; import addClanChoices from '../core/options/addClanChoices.js'; import { WARGAMING_APPLICATION_ID } from '../core/process/args.js'; -import render from '../core/ui/render.js'; import { CommandRegistry } from '../events/interactionCreate.js'; import { AccountInfo } from '../types/accountInfo.js'; import { ClanInfo } from '../types/clanInfo.js'; @@ -67,7 +66,7 @@ export default { ); const hasInactiveMembers = inactive.length > 0; - const image = await render( + return ( } - , + ); - - await interaction.editReply({ files: [image] }); }, autocomplete: clanAutocomplete, diff --git a/src/commands/ownedtanks.tsx b/src/commands/ownedtanks.tsx index 98df0be0f..15d4deb09 100644 --- a/src/commands/ownedtanks.tsx +++ b/src/commands/ownedtanks.tsx @@ -13,7 +13,6 @@ import { TIER_ROMAN_NUMERALS, tankopedia } from '../core/blitz/tankopedia.js'; import cmdName from '../core/interaction/cmdName.js'; import addUsernameOption from '../core/options/addUsernameOption.js'; import { WARGAMING_APPLICATION_ID } from '../core/process/args.js'; -import render from '../core/ui/render.js'; import { CommandRegistry } from '../events/interactionCreate.js'; import { AccountInfo } from '../types/accountInfo.js'; import { PlayerClanData } from '../types/playerClanData.js'; @@ -41,24 +40,34 @@ const COMP_TANKS = [ export default { inProduction: true, - inDevelopment: true, + inDevelopment: false, inPublic: true, command: new SlashCommandBuilder() .setName(cmdName('ownedtanks')) .setDescription("Shows a player's owned tanks") - .addIntegerOption((option) => + .addStringOption((option) => option .setName('tier') .setDescription('The tier you want to see') - .setMinValue(1) - .setMaxValue(10) + .setChoices( + { name: 'Tier I', value: '1' }, + { name: 'Tier II', value: '2' }, + { name: 'Tier III', value: '3' }, + { name: 'Tier IV', value: '4' }, + { name: 'Tier V', value: '5' }, + { name: 'Tier VI', value: '6' }, + { name: 'Tier VII', value: '7' }, + { name: 'Tier VIII', value: '8' }, + { name: 'Tier IX', value: '9' }, + { name: 'Tier X', value: '10' }, + ) .setRequired(true), ) .addStringOption(addUsernameOption), async execute(interaction) { - const tier = interaction.options.getInteger('tier')!; + const tier = Number(interaction.options.getString('tier')); const account = await getBlitzAccount(interaction); const { id, server } = account; const accountInfo = await getWargamingResponse( @@ -72,7 +81,7 @@ export default { `https://api.wotblitz.${server}/wotb/clans/accountinfo/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}&extra=clan`, ); - const image = await render( + return ( {/* TODO: integrate some of these into title bar */} - , + ); - - await interaction.editReply({ files: [image] }); }, autocomplete: usernameAutocomplete, diff --git a/src/commands/playerachievements.ts b/src/commands/playerachievements.ts index cbe3626fd..9846a2abc 100644 --- a/src/commands/playerachievements.ts +++ b/src/commands/playerachievements.ts @@ -51,30 +51,26 @@ export default { ...accountAchievements.max_series, }; - interaction.editReply({ - embeds: [ - infoEmbed( - `${markdownEscape(accounts[id].nickname)}'s information`, + return infoEmbed( + `${markdownEscape(accounts[id].nickname)}'s information`, - cleanTable( - Object.keys(compound) - .filter((achievement) => compound[achievement] > 0) - .sort( - sortBy === 'count' - ? (a, b) => compound[b] - compound[a] - : undefined, - ) - .map( - (achievement) => - [ - startCase(achievement), - compound[achievement], - ] as TableInputEntry, - ), + cleanTable( + Object.keys(compound) + .filter((achievement) => compound[achievement] > 0) + .sort( + sortBy === 'count' + ? (a, b) => compound[b] - compound[a] + : undefined, + ) + .map( + (achievement) => + [ + startCase(achievement), + compound[achievement], + ] as TableInputEntry, ), - ), - ], - }); + ), + ); }, autocomplete: usernameAutocomplete, diff --git a/src/commands/playerinfo.ts b/src/commands/playerinfo.ts index 46a3eefb8..77b339508 100644 --- a/src/commands/playerinfo.ts +++ b/src/commands/playerinfo.ts @@ -29,36 +29,32 @@ export default { ); const accountInfo = accounts[id]; - interaction.editReply({ - embeds: [ - infoEmbed( - `${markdownEscape(accountInfo.nickname)}'s information`, + return infoEmbed( + `${markdownEscape(accountInfo.nickname)}'s information`, - cleanTable([ - ['Nickname', `${accountInfo.nickname}`], - ['Battles', `${accountInfo.statistics.all.battles}`], - [ - 'Winrate', - `${( - 100 * - (accountInfo.statistics.all.wins / - accountInfo.statistics.all.battles) - ).toFixed(2)}%`, - ], - [], - ['Account ID', `${accountInfo.account_id}`], - [ - 'Created', - `${new Date(accountInfo.created_at * 1000).toDateString()}`, - ], - [ - 'Last battle', - `${new Date(accountInfo.last_battle_time * 1000).toDateString()}`, - ], - ]), - ), - ], - }); + cleanTable([ + ['Nickname', `${accountInfo.nickname}`], + ['Battles', `${accountInfo.statistics.all.battles}`], + [ + 'Winrate', + `${( + 100 * + (accountInfo.statistics.all.wins / + accountInfo.statistics.all.battles) + ).toFixed(2)}%`, + ], + [], + ['Account ID', `${accountInfo.account_id}`], + [ + 'Created', + `${new Date(accountInfo.created_at * 1000).toDateString()}`, + ], + [ + 'Last battle', + `${new Date(accountInfo.last_battle_time * 1000).toDateString()}`, + ], + ]), + ); }, autocomplete: usernameAutocomplete, diff --git a/src/commands/searchclans.ts b/src/commands/searchclans.ts index 28216932d..ef65b10b3 100644 --- a/src/commands/searchclans.ts +++ b/src/commands/searchclans.ts @@ -36,19 +36,13 @@ export default { `https://api.wotblitz.${server}/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${clan}&limit=${limit}`, ); - await interaction.editReply({ - embeds: [ - infoEmbed( - `Clan search for "${markdownEscape(clan)}" in ${ - BLITZ_SERVERS[server] - }`, - clanList.length === 0 - ? 'No clans found.' - : `\`\`\`\n${clanList - .map((clan) => `${clan.name} [${clan.tag}]`) - .join('\n')}\n\`\`\``, - ), - ], - }); + return infoEmbed( + `Clan search for "${markdownEscape(clan)}" in ${BLITZ_SERVERS[server]}`, + clanList.length === 0 + ? 'No clans found.' + : `\`\`\`\n${clanList + .map((clan) => `${clan.name} [${clan.tag}]`) + .join('\n')}\n\`\`\``, + ); }, } satisfies CommandRegistry; diff --git a/src/commands/searchplayers.ts b/src/commands/searchplayers.ts index 1f29125e5..63a61d61a 100644 --- a/src/commands/searchplayers.ts +++ b/src/commands/searchplayers.ts @@ -44,19 +44,13 @@ export default { ) : []; - await interaction.editReply({ - embeds: [ - infoEmbed( - `Player search for "${markdownEscape(name)}" in ${ - BLITZ_SERVERS[server] - }`, - `\`\`\`${ - players.length === 0 - ? 'No players found.' - : players.map((player) => player.nickname).join('\n') - }\`\`\``, - ), - ], - }); + return infoEmbed( + `Player search for "${markdownEscape(name)}" in ${BLITZ_SERVERS[server]}`, + `\`\`\`${ + players.length === 0 + ? 'No players found.' + : players.map((player) => player.nickname).join('\n') + }\`\`\``, + ); }, } satisfies CommandRegistry; diff --git a/src/commands/searchtanks.ts b/src/commands/searchtanks.ts index 2d525cdc0..c9760df1c 100644 --- a/src/commands/searchtanks.ts +++ b/src/commands/searchtanks.ts @@ -31,15 +31,11 @@ export default { (result) => result.target, ); - await interaction.editReply({ - embeds: [ - infoEmbed( - `Tank search for "${markdownEscape(tank)}"`, - results.length === 0 - ? 'No tanks found.' - : `\`\`\`\n${results.join('\n')}\n\`\`\``, - ), - ], - }); + return infoEmbed( + `Tank search for "${markdownEscape(tank)}"`, + results.length === 0 + ? 'No tanks found.' + : `\`\`\`\n${results.join('\n')}\n\`\`\``, + ); }, } satisfies CommandRegistry; diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx index 9939e2c20..6c541451a 100644 --- a/src/commands/stats.tsx +++ b/src/commands/stats.tsx @@ -1,8 +1,4 @@ -import { - ActionRowBuilder, - ButtonBuilder, - SlashCommandBuilder, -} from 'discord.js'; +import { SlashCommandBuilder } from 'discord.js'; import GenericAllStats from '../components/GenericAllStats.js'; import NoData, { NoDataType } from '../components/NoData.js'; import PoweredByBlitzStars from '../components/PoweredByBlitzStars.js'; @@ -29,7 +25,6 @@ import addStatPeriodChoices, { } from '../core/options/addStatPeriodChoices.js'; import addUsernameOption from '../core/options/addUsernameOption.js'; import { WARGAMING_APPLICATION_ID } from '../core/process/args.js'; -import render from '../core/ui/render.js'; import { CommandRegistry } from '../events/interactionCreate.js'; import { AccountInfo, @@ -134,7 +129,7 @@ export default { if (!hasWN8AccumulatedAtAll) supplementaryStats.WN8 = undefined; - const image = await render( + return [ , - ); - - const actionRow = new ActionRowBuilder().addComponents( fullBlitzStarsStats(server, accountInfo[id].nickname), supportBlitzStars, - ); - - await interaction.editReply({ - files: [image], - components: [actionRow], - }); + ]; }, autocomplete: usernameAutocomplete, diff --git a/src/commands/tankstats.tsx b/src/commands/tankstats.tsx index baae75a9c..6585a2fca 100644 --- a/src/commands/tankstats.tsx +++ b/src/commands/tankstats.tsx @@ -1,8 +1,4 @@ -import { - ActionRowBuilder, - ButtonBuilder, - SlashCommandBuilder, -} from 'discord.js'; +import { SlashCommandBuilder } from 'discord.js'; import GenericAllStats from '../components/GenericAllStats.js'; import NoData, { NoDataType } from '../components/NoData.js'; import PoweredByBlitzStars from '../components/PoweredByBlitzStars.js'; @@ -34,13 +30,12 @@ import addStatPeriodChoices, { import addTankChoices from '../core/options/addTankChoices.js'; import addUsernameOption from '../core/options/addUsernameOption.js'; import { WARGAMING_APPLICATION_ID } from '../core/process/args.js'; -import render from '../core/ui/render.js'; import { CommandRegistry } from '../events/interactionCreate.js'; import { AccountInfo, AllStats } from '../types/accountInfo.js'; export default { inProduction: true, - inDevelopment: true, + inDevelopment: false, inPublic: true, command: new SlashCommandBuilder() @@ -81,7 +76,7 @@ export default { stats = tankStatsOverTime[tankId] ?? emptyAllStats; } - const image = await render( + return [ , - ); - - const actionRow = new ActionRowBuilder().addComponents( fullBlitzStarsStats(server, accountInfo[id].nickname), supportBlitzStars, - ); - - await interaction.editReply({ - files: [image], - components: [actionRow], - }); + ]; }, autocomplete: (interaction) => { diff --git a/src/commands/today.tsx b/src/commands/today.tsx index 887e3f27a..34fe10b29 100644 --- a/src/commands/today.tsx +++ b/src/commands/today.tsx @@ -1,8 +1,4 @@ -import { - ActionRowBuilder, - ButtonBuilder, - SlashCommandBuilder, -} from 'discord.js'; +import { SlashCommandBuilder } from 'discord.js'; import * as Breakdown from '../components/Breakdown/index.js'; import NoData, { NoDataType } from '../components/NoData.js'; import PoweredByBlitzStars from '../components/PoweredByBlitzStars.js'; @@ -25,7 +21,6 @@ import fullBlitzStarsStats from '../core/interaction/fullBlitzStarsStats.js'; import { supportBlitzStars } from '../core/interaction/supportBlitzStars.js'; import addUsernameOption from '../core/options/addUsernameOption.js'; import { WARGAMING_APPLICATION_ID } from '../core/process/args.js'; -import render from '../core/ui/render.js'; import { CommandRegistry } from '../events/interactionCreate.js'; import { AccountInfo, AllStats } from '../types/accountInfo.js'; import { PlayerClanData } from '../types/playerClanData.js'; @@ -159,7 +154,7 @@ export default { ); }); - const image = await render( + return [ , - ); - - const actionRow = new ActionRowBuilder().addComponents( fullBlitzStarsStats(server, accountInfo[id].nickname), supportBlitzStars, - ); - - await interaction.editReply({ - files: [image], - components: [actionRow], - }); + ]; }, autocomplete: usernameAutocomplete, diff --git a/src/commands/verify.ts b/src/commands/verify.ts index 7fbfdeabc..cbc0643f8 100644 --- a/src/commands/verify.ts +++ b/src/commands/verify.ts @@ -1,11 +1,12 @@ -import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; +import { SlashCommandBuilder } from 'discord.js'; import markdownEscape from 'markdown-escape'; import discord from '../../discord.json' assert { type: 'json' }; -import { NEGATIVE_COLOR, POSITIVE_COLOR } from '../constants/colors.js'; import usernameAutocomplete from '../core/autocomplete/username.js'; import getBlitzAccount from '../core/blitz/getBlitzAccount.js'; import getWargamingResponse from '../core/blitz/getWargamingResponse.js'; import cmdName from '../core/interaction/cmdName.js'; +import negativeEmbed from '../core/interaction/negativeEmbed.js'; +import positiveEmbed from '../core/interaction/positiveEmbed.js'; import addUsernameOption from '../core/options/addUsernameOption.js'; import { WARGAMING_APPLICATION_ID } from '../core/process/args.js'; import { CommandRegistry } from '../events/interactionCreate.js'; @@ -33,84 +34,49 @@ export default { ); const clan = clanData[id]?.clan; const clanTag = clan ? ` [${clan!.tag}]` : ''; + const member = + interaction.member && + interaction.guild?.members.cache.get(interaction.member?.user.id); 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; + return negativeEmbed( + `${markdownEscape(interaction.user.username)} failed to verify`, + "I don't have the permission to change your nickname.", + ); + } + if (!interaction.member || !member) { + return negativeEmbed( + `${interaction.user.username} is not in this server`, + "I couldn't find this person in this server. Did they leave?", + ); } - if (interaction.member) { - const member = interaction.guild?.members.cache.get( - interaction.member?.user.id, - ); + try { + await member.setNickname(`${accountInfo[id].nickname}${clanTag}`); - if (member) { - try { - await member.setNickname(`${accountInfo[id].nickname}${clanTag}`); - - if (interaction.guildId === discord.sklld_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.", - ), - ], - }); - - return; - } - - await member.roles.remove(discord.sklld_verify_role); - await member.roles.add(discord.sklld_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( - accountInfo[id].nickname, - )}${markdownEscape(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. Try setting your nickname to your Blitz username manually.", - ), - ], - }); + if (interaction.guildId === discord.sklld_guild_id) { + if (!interaction.guild?.members.me?.permissions.has('ManageRoles')) { + return negativeEmbed( + `${markdownEscape(interaction.user.username)} failed to verify`, + "I don't have the permission to change your manage roles.", + ); } + + await member.roles.remove(discord.sklld_verify_role); + await member.roles.add(discord.sklld_peasant_role); } + + return positiveEmbed( + `${interaction.user.username} is verified`, + `The user is now verified as ${markdownEscape( + accountInfo[id].nickname, + )}${markdownEscape(clanTag)}`, + ); + } catch (error) { + return negativeEmbed( + `${interaction.user.username} failed to verify`, + "I can't change your nickname because you have higher permissions than me. Try setting your nickname to your Blitz username manually.", + ); } }, diff --git a/src/constants/colors.ts b/src/constants/colors.ts index ba9d56931..bb2c7f51d 100644 --- a/src/constants/colors.ts +++ b/src/constants/colors.ts @@ -1,3 +1,4 @@ -export const ACCENT_COLOR = '#ecedee'; // theme.colors.textHighContrast +export const ACCENT_COLOR = '#ecedee'; export const POSITIVE_COLOR = '#82ff29'; export const NEGATIVE_COLOR = '#ff4747'; +export const WARNING_COLOR = '#ffab00'; diff --git a/src/core/interaction/warningEmbed.ts b/src/core/interaction/warningEmbed.ts new file mode 100644 index 000000000..b8006d1c2 --- /dev/null +++ b/src/core/interaction/warningEmbed.ts @@ -0,0 +1,9 @@ +import { EmbedBuilder } from 'discord.js'; +import { WARNING_COLOR } from '../../constants/colors.js'; + +export default function warningEmbed(title: string, description: string) { + return new EmbedBuilder() + .setColor(WARNING_COLOR) + .setTitle(title) + .setDescription(description); +} diff --git a/src/core/process/psa.ts b/src/core/process/psa.ts new file mode 100644 index 000000000..a08e90812 --- /dev/null +++ b/src/core/process/psa.ts @@ -0,0 +1,20 @@ +export interface PSAData { + title: string; + description: string; +} + +export interface PSA { + data?: PSAData; +} + +export const psa: PSA = {}; + +console.log('Caching PSA...'); +fetch( + 'https://gist.githubusercontent.com/tresabhi/ed4b136f08856e615212e573aabe1968/raw', +) + .then((response) => response.json()) + .then((data) => { + psa.data = data as PSAData; + console.log('PSA cached'); + }); diff --git a/src/events/interactionCreate.ts b/src/events/interactionCreate.ts index 37ec47d85..dc71678e3 100644 --- a/src/events/interactionCreate.ts +++ b/src/events/interactionCreate.ts @@ -5,7 +5,9 @@ import { ButtonStyle, CacheType, ChatInputCommandInteraction, + EmbedBuilder, Interaction, + InteractionEditReplyOptions, REST, RESTPostAPIChatInputApplicationCommandsJSONBody, Routes, @@ -27,39 +29,49 @@ import tankstats from '../commands/tankstats.js'; import today from '../commands/today.js'; import verify from '../commands/verify.js'; import negativeEmbed from '../core/interaction/negativeEmbed.js'; +import warningEmbed from '../core/interaction/warningEmbed.js'; import { DISCORD_TOKEN } from '../core/process/args.js'; import getClientId from '../core/process/getClientId.js'; import isDev from '../core/process/isDev.js'; +import { psa } from '../core/process/psa.js'; +import render from '../core/ui/render.js'; import { handleError } from './error.js'; +export type CommandReturnable = EmbedBuilder | ButtonBuilder | JSX.Element; +export type RichCommandReturnable = CommandReturnable | CommandReturnable[]; + export interface CommandRegistry { inDevelopment: boolean; inProduction: boolean; inPublic: boolean; command: Omit; - execute: (interaction: ChatInputCommandInteraction) => void; + execute: ( + interaction: ChatInputCommandInteraction, + ) => RichCommandReturnable | Promise; autocomplete?: (interaction: AutocompleteInteraction) => void; } const rest = new REST().setToken(DISCORD_TOKEN); -const commands = [ - debug, - eligible, - help, - inactive, - ownedtanks, - playerachievements, - playerinfo, - searchclans, - searchplayers, - searchtanks, - stats, - tankstats, - today, - verify, -].reduce>((accumulator, registry) => { +const commands = ( + [ + debug, + eligible, + help, + inactive, + ownedtanks, + playerachievements, + playerinfo, + searchclans, + searchplayers, + searchtanks, + stats, + tankstats, + today, + verify, + ] as CommandRegistry[] +).reduce>((accumulator, registry) => { return { ...accumulator, [registry.command.name]: registry }; }, {}); @@ -138,7 +150,36 @@ export default async function interactionCreate( await interaction.deferReply(); try { - await command.execute(interaction); + const result = await command.execute(interaction); + const normalizedResult = Array.isArray(result) ? result : [result]; + const reply: InteractionEditReplyOptions = {}; + + if (psa.data) { + normalizedResult.push( + warningEmbed(psa.data.title, psa.data.description), + ); + } + + await Promise.all( + normalizedResult.map(async (item) => { + if (item instanceof EmbedBuilder) { + if (!reply.embeds) reply.embeds = []; + reply.embeds.push(item); + } else if (item instanceof ButtonBuilder) { + if (!reply.components) + reply.components = [new ActionRowBuilder()]; + ( + reply.components[0] as ActionRowBuilder + ).addComponents(item); + } else { + const image = await render(item); + if (!reply.files) reply.files = []; + reply.files.push(image); + } + }), + ); + + await interaction.editReply(reply); } catch (error) { const actionRow = new ActionRowBuilder().addComponents( new ButtonBuilder()