diff options
Diffstat (limited to 'index.ts')
| -rw-r--r-- | index.ts | 226 |
1 files changed, 13 insertions, 213 deletions
@@ -1,21 +1,14 @@ import { - ActionRowBuilder, - ButtonBuilder, - ButtonStyle, - ChannelType, Client, CommandInteraction, Events, GatewayIntentBits, - PermissionFlagsBits, REST, Routes, - SlashCommandBuilder, - SlashCommandStringOption, - type ButtonInteraction, - type ChatInputCommandInteraction, } from "discord.js"; +import { buttonCommands, slashCommands } from "./commands.ts"; + function requireEnv(key: string): string { let value = process.env[key]; if (!value) { @@ -30,211 +23,11 @@ let guildId = requireEnv("DISCORD_GUILD_ID"); let client = new Client({ intents: [GatewayIntentBits.Guilds] }); -let commands = [ - { - slashCommand: new SlashCommandBuilder() - .setName("new") - .setDescription("Create a new CTF") - .addStringOption( - new SlashCommandStringOption() - .setName("name") - .setDescription("Name for the new CTF") - .setRequired(true) - ), - handler: newCtf, - }, - { - slashCommand: new SlashCommandBuilder() - .setName("archive") - .setDescription("Make this CTF category read-only for everyone"), - handler: makeCtfCategoryReadonly, - }, -]; - -let CATEGORY_BUTTON_PREFIX = "ctf-category:"; - -async function newCtf(interaction: ChatInputCommandInteraction) { - if (!interaction.guild) { - await interaction.reply({ - content: "This command can only be used inside a server.", - flags: "Ephemeral", - }); - return; - } - - let name = interaction.options.getString("name", true); - await interaction.deferReply({ flags: "Ephemeral" }); - - let category = await interaction.guild.channels.create({ - name, - type: ChannelType.GuildCategory, - permissionOverwrites: [ - { - id: interaction.guild.roles.everyone, - deny: [PermissionFlagsBits.ViewChannel], - } - ], - }); - - await Promise.all([ - await interaction.guild.channels.create({ - name: "info", - type: ChannelType.GuildText, - parent: category.id, - position: 1, - }), - await interaction.guild.channels.create({ - name: "general", - type: ChannelType.GuildText, - parent: category.id, - position: 2, - }), - await interaction.guild.channels.create({ - name: "challs", - type: ChannelType.GuildForum, - parent: category.id, - position: 3, - }), - ]); - - let commandChannel = (interaction.channelId && await interaction.guild.channels.fetch(interaction.channelId)); - - if (!commandChannel || !commandChannel.isTextBased()) - throw new Error("Wth is this channel?"); - - await commandChannel.send({ - components: [ - new ActionRowBuilder<ButtonBuilder>().addComponents( - new ButtonBuilder() - .setCustomId(`${CATEGORY_BUTTON_PREFIX}${category.id}`) - .setLabel(`Join ${name}`) - .setStyle(ButtonStyle.Primary), - ), - ], - }); - - await interaction.editReply( - `Created ${category.name}.`, - ); -} - -async function makeCtfCategoryReadonly(interaction: ChatInputCommandInteraction) { - if (!interaction.guild) { - await interaction.reply({ - content: "This command can only be used inside a server.", - flags: "Ephemeral", - }); - return; - } - - if (!interaction.channel || interaction.channel.type !== ChannelType.GuildText - || !["general", "info"].includes(interaction.channel.name) - || !interaction.channel.parentId - ) { - await interaction.reply({ - content: "This command can only be used in the `general` or `info` channel of a CTF category.", - flags: "Ephemeral", - }); - return; - } - - let category = interaction.channel.parentId && await interaction.guild.channels.fetch(interaction.channel.parentId); - if (!category || category.type !== ChannelType.GuildCategory) { - await interaction.reply({ - content: "This command can only be used in the `general` or `info` channel of a CTF category.", - flags: "Ephemeral", - }); - return; - } - - await interaction.deferReply({ flags: "Ephemeral" }); - - let allChannels = await interaction.guild.channels.fetch(); - let categoryChildren = [...allChannels.values()].filter((channel) => { - return channel !== null && "parentId" in channel && channel.parentId === category.id; - }); - - let infoChannel = categoryChildren.find((channel) => channel?.name === "info"); - let generalChannel = categoryChildren.find((channel) => channel?.name === "general"); - let challsChannel = categoryChildren.find((channel) => channel?.name === "challs"); - - if ( - !infoChannel || - !generalChannel || - !challsChannel || - infoChannel.type !== ChannelType.GuildText || - generalChannel.type !== ChannelType.GuildText || - challsChannel.type !== ChannelType.GuildForum - ) { - await interaction.editReply("This command can only be used in the `general` or `info` channel of a CTF category."); - return; - } - - let everyoneRoleId = interaction.guild.roles.everyone.id; - - await category.permissionOverwrites.edit(everyoneRoleId, { - ViewChannel: true, - ReadMessageHistory: true, - SendMessages: false, - SendMessagesInThreads: false, - CreatePublicThreads: false, - CreatePrivateThreads: false, - }); - - await Promise.all( - category.permissionOverwrites.cache - .filter(overwrite => overwrite.id !== everyoneRoleId) - .map(overwrite => category.permissionOverwrites.delete(overwrite.id)) - ); - - for (let channel of [infoChannel, generalChannel, challsChannel]) { - if (channel && "lockPermissions" in channel && typeof channel.lockPermissions === "function") { - await channel.lockPermissions(); - } - } - - await interaction.editReply( - `Updated ${category.name}: @everyone can read, (mostly) no-one can write.`, - ); -} - -async function handleJoinCtfButton(interaction: ButtonInteraction) { - if (!interaction.guild) { - await interaction.reply({ - content: "This button only works inside a server.", - flags: "Ephemeral", - }); - return; - } - - let categoryId = interaction.customId.slice(CATEGORY_BUTTON_PREFIX.length); - - let categoryChannel = await interaction.guild.channels.fetch(categoryId); - - if (!categoryChannel || categoryChannel.type !== ChannelType.GuildCategory) { - await interaction.reply({ - content: "That category no longer exists.", - flags: "Ephemeral", - }); - return; - } - - await categoryChannel.permissionOverwrites.edit(interaction.user.id, { - ViewChannel: true, - SendMessages: true, - }); - - await interaction.reply({ - content: `Granted you access to ${categoryChannel.name}.`, - flags: "Ephemeral", - }); -} - let rest = new REST({ version: "10" }).setToken(token); async function registerSlashCommands() { await rest.put(Routes.applicationGuildCommands(clientId, guildId), { - body: commands.map(command => command.slashCommand.toJSON()), + body: slashCommands.map(command => command.slashCommand.toJSON()), }); } @@ -245,16 +38,23 @@ client.once(Events.ClientReady, (readyClient) => { client.on(Events.InteractionCreate, async (interaction) => { try { if (interaction.isChatInputCommand()) { - for (let command of commands) { + for (let command of slashCommands) { if (interaction.commandName === command.slashCommand.name) { await command.handler(interaction); break; } } - } else if (interaction.isButton() && interaction.customId.startsWith(CATEGORY_BUTTON_PREFIX)) { - await handleJoinCtfButton(interaction); + } else if (interaction.isButton()) { + for (let button of Object.values(buttonCommands)) { + if (interaction.customId.startsWith(button.prefix)) { + let id = interaction.customId.slice(button.prefix.length); + await button.handler(interaction, id); + break; + } + } } } catch (error) { + if (error === "done") return; if (interaction instanceof CommandInteraction) { console.error("Error while handling interaction", error); if (interaction.deferred || interaction.replied) { |
