mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 03:59:50 +02:00
feat: migration 3
This commit is contained in:
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);
|
||||
}
|
||||
}
|
||||
@@ -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)](),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user