feat: migration 3

This commit is contained in:
TrèsAbhi committed 2024-09-06 23:26:35 -05:00
1 parent 6285f33989
commit 4055988478
110 files changed
+28 -76

No files matched your search

@@ -0,0 +1,15 @@
import { AutocompleteInteraction, CacheType } from 'discord.js';
import { commands } from '..';
export default async function handleAutocomplete(
interaction: AutocompleteInteraction<CacheType>,
) {
const command = (await commands)[interaction.commandName];
if (!command) {
console.error(`No command matching ${interaction.commandName} was found.`);
return;
}
if (command.autocomplete) command.autocomplete(interaction);
}
@@ -0,0 +1,29 @@
import { ButtonInteraction, CacheType } from 'discord.js';
import { commands } from '..';
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
export default async function handleButton(
interaction: ButtonInteraction<CacheType>,
) {
await interaction.deferUpdate();
const url = new URL(`https://example.com/${interaction.customId}`);
const commandName = url.pathname.split('/')[1];
const button = (await commands)[commandName]?.button;
if (!button) {
throw new Error(`Button handler not found for "${commandName}"`);
}
if (interaction.message.interaction?.user.id !== interaction.user.id) {
interaction.followUp({
ephemeral: true,
content: 'You are not the author of this command.',
});
return;
}
await interaction.message.edit(
await normalizeInteractionReturnable(button(interaction)),
);
}
@@ -0,0 +1,153 @@
import {
ActionRowBuilder,
AttachmentBuilder,
ButtonBuilder,
ButtonStyle,
CacheType,
ChatInputCommandInteraction,
InteractionReplyOptions,
} from 'discord.js';
import { InteractionRawReturnable, commands } from '..';
import buttonLink from '../../../core/discord/buttonLink';
import embedWarning from '../../../core/discord/embedWarning';
import normalizeInteractionReturnable from '../../../core/discord/normalizeInteractionReturnable';
import { psa } from '../../../core/discord/psa';
import { translator } from '../../../core/localization/translator';
import { UserError } from '../../../hooks/userError';
import { Writeable } from '../../../types/writable';
export default async function handleChatInputCommand(
interaction: ChatInputCommandInteraction<CacheType>,
) {
const awaitedCommands = await commands;
const registry = awaitedCommands[interaction.commandName];
await interaction.deferReply();
try {
const returnable = await registry.handler(interaction);
if (registry.handlesInteraction) return;
const chunks: InteractionRawReturnable[][] = [[]];
(Array.isArray(returnable) ? returnable : [returnable]).forEach(
(object) => {
if (typeof object === 'string') {
chunks.push([object]);
} else chunks.at(-1)?.push(object!);
},
);
let index = 0;
for (const chunk of chunks.filter((chunk) => chunk.length > 0)) {
const reply = await normalizeInteractionReturnable(chunk);
if (
psa.data &&
!psa.data.secret &&
(psa.data.commands === undefined ||
psa.data.commands.includes(interaction.commandName))
) {
if (psa.data.type === undefined || psa.data.type === 'embed') {
if (!reply.embeds) reply.embeds = [];
const embed = embedWarning(psa.data.title, psa.data.description);
if (psa.data.image) embed.setImage(psa.data.image);
(reply.embeds as Writeable<typeof reply.embeds>).push(embed);
} else if (psa.data.type === 'image') {
if (!reply.files) reply.files = [];
(reply.files as Writeable<typeof reply.files>).push(
new AttachmentBuilder(psa.data.image),
);
reply.content = reply.content ?? psa.data.title;
}
if (psa.data.links) {
if (!reply.components?.[0]) {
reply.components = [new ActionRowBuilder<ButtonBuilder>()];
}
(
reply.components[0] as ActionRowBuilder<ButtonBuilder>
).addComponents(
psa.data.links.map(({ title, url }) => buttonLink(url, title)),
);
}
}
if (index === 0) {
await interaction.editReply(reply);
} else {
await interaction.followUp(reply);
}
if (
psa.data &&
psa.data.secret &&
(psa.data.commands === undefined ||
psa.data.commands.includes(interaction.commandName))
) {
const followUp: InteractionReplyOptions = {
ephemeral: true,
};
if (psa.data.type === undefined || psa.data.type === 'embed') {
if (!followUp.embeds) followUp.embeds = [];
const embed = embedWarning(psa.data.title, psa.data.description);
if (psa.data.image) embed.setImage(psa.data.image);
(followUp.embeds as Writeable<typeof followUp.embeds>).push(embed);
} else if (psa.data.type === 'image') {
if (!followUp.files) followUp.files = [];
(followUp.files as Writeable<typeof followUp.files>).push(
new AttachmentBuilder(psa.data.image),
);
followUp.content = followUp.content ?? psa.data.title;
}
if (psa.data.links) {
if (!followUp.components?.[0]) {
followUp.components = [new ActionRowBuilder<ButtonBuilder>()];
}
(
followUp.components[0] as ActionRowBuilder<ButtonBuilder>
).addComponents(
psa.data.links.map(({ title, url }) => buttonLink(url, title)),
);
}
interaction.followUp(followUp);
}
index++;
}
} catch (error) {
const { t } = translator(interaction.locale);
const components = [
new ActionRowBuilder<ButtonBuilder>().addComponents(
new ButtonBuilder()
.setLabel(t`bot.common.errors.get_help`)
.setURL('https://discord.gg/nDt7AjGJQH')
.setStyle(ButtonStyle.Link),
),
];
if (error instanceof UserError) {
interaction.editReply({
content: error.message,
components,
});
} else {
console.error(interaction.commandName, error);
interaction.editReply({
content: t`bot.common.errors.uncaught_error`,
components,
});
}
}
}
@@ -0,0 +1,143 @@
import {
AttachmentBuilder,
AutocompleteInteraction,
ButtonBuilder,
ButtonInteraction,
CacheType,
ChatInputCommandInteraction,
EmbedBuilder,
Interaction,
REST,
Routes,
SlashCommandBuilder,
SlashCommandOptionsOnlyBuilder,
SlashCommandSubcommandsOnlyBuilder,
} from 'discord.js';
import { assertSecret } from '../../../packages/core/src/blitzkit/assertSecret';
import { aboutCommand } from '../../commands/about';
import { breakdownCommand } from '../../commands/breakdown';
import { debugCommand } from '../../commands/debug';
import { evolutionCommand } from '../../commands/evolution';
import { fullStatsCommand } from '../../commands/fullStats';
import { inactiveCommand } from '../../commands/inactive';
import { verifyCommand } from '../../commands/link';
import { ownedTanksCommand } from '../../commands/ownedTanks';
import { permissionsCommand } from '../../commands/permissions';
import { pingCommand } from '../../commands/ping';
import { playerInfoCommand } from '../../commands/playerInfo';
import { ratingCommand } from '../../commands/rating';
import { ratingLeaderboardCommand } from '../../commands/ratingLeaderboard';
import { replayCommand } from '../../commands/replay';
import { researchCommand } from '../../commands/research';
import { searchClansCommand } from '../../commands/searchClans';
import { searchPlayersCommand } from '../../commands/searchPlayers';
import { statsCommand } from '../../commands/stats';
import { todayCommand } from '../../commands/today';
import { RenderConfiguration } from '../../core/blitzkit/renderConfiguration';
import handleAutocomplete from './handlers/autocomplete';
import handleButton from './handlers/button';
import handleChatInputCommand from './handlers/chatInputCommand';
export type InteractionRawReturnable =
| string
| EmbedBuilder
| ButtonBuilder
| AttachmentBuilder
| RenderConfiguration
| JSX.Element
| null;
export type InteractionIterableReturnable =
| InteractionRawReturnable
| InteractionRawReturnable[];
export type InteractionReturnable =
| InteractionIterableReturnable
| Promise<InteractionIterableReturnable>;
interface CommandRegistryBase {
command:
| SlashCommandBuilder
| SlashCommandSubcommandsOnlyBuilder
| SlashCommandOptionsOnlyBuilder
| Omit<SlashCommandBuilder, 'addSubcommand' | 'addSubcommandGroup'>;
autocomplete?: (interaction: AutocompleteInteraction<CacheType>) => void;
button?: (interaction: ButtonInteraction<CacheType>) => InteractionReturnable;
}
interface CommandRegistryImplicit extends CommandRegistryBase {
handlesInteraction: true;
handler: (interaction: ChatInputCommandInteraction<CacheType>) => void;
}
interface CommandRegistryExplicit extends CommandRegistryBase {
handlesInteraction?: false;
handler: (
interaction: ChatInputCommandInteraction<CacheType>,
) => InteractionReturnable;
}
export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit;
export const COMMANDS_RAW: Promise<CommandRegistry>[] = [
permissionsCommand,
debugCommand,
aboutCommand,
inactiveCommand,
ownedTanksCommand,
playerInfoCommand,
searchClansCommand,
searchPlayersCommand,
fullStatsCommand,
breakdownCommand,
verifyCommand,
pingCommand,
evolutionCommand,
statsCommand,
ratingLeaderboardCommand,
todayCommand,
researchCommand,
ratingCommand,
replayCommand,
];
export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => {
return rawCommands.reduce<Record<string, CommandRegistry>>(
(commands, registry, index) => {
if (registry.status === 'rejected') {
console.warn(
`Command ${index} failed to load; skipping...`,
registry.reason,
);
return commands;
}
return { ...commands, [registry.value.command.name]: registry.value };
},
{},
);
});
const rest = new REST().setToken(assertSecret(process.env.DISCORD_TOKEN));
commands.then((awaitedCommands) => {
const body = Object.values(awaitedCommands).map((registry) =>
registry.command.toJSON(),
);
rest.put(
Routes.applicationCommands(assertSecret(process.env.DISCORD_CLIENT_ID)),
{
body,
},
);
});
export default async function interactionCreate(
interaction: Interaction<CacheType>,
) {
if (interaction.isAutocomplete()) {
handleAutocomplete(interaction);
} else if (interaction.isChatInputCommand()) {
handleChatInputCommand(interaction);
} else if (interaction.isButton()) {
handleButton(interaction);
}
}
+29
View File
@@ -0,0 +1,29 @@
import { ActivityType, Client } from 'discord.js';
export default function ready(client: Client<true>) {
console.log(`🟢 Launched bot ${client.shard?.ids[0] ?? 'default'}`);
const activities: (() => string)[] = [
() => `Living in ${client.guilds.cache.size.toLocaleString()} servers`,
() =>
`Serving ${client.guilds.cache
.reduce((accumulator, guild) => accumulator + guild.memberCount, 0)
.toLocaleString()} users`,
() =>
`Observing ${client.guilds.cache
.reduce(
(accumulator, guild) => accumulator + guild.channels.cache.size,
0,
)
.toLocaleString()} channels`,
];
client.user.setPresence({
activities: [
{
type: ActivityType.Custom,
name: activities[Math.floor(Math.random() * activities.length)](),
},
],
});
}