diff options
| -rw-r--r-- | commands.ts | 76 | ||||
| -rw-r--r-- | index.ts | 23 |
2 files changed, 80 insertions, 19 deletions
diff --git a/commands.ts b/commands.ts index f100cc7..3b72c3a 100644 --- a/commands.ts +++ b/commands.ts @@ -9,8 +9,10 @@ import { SlashCommandChannelOption, SlashCommandStringOption, SlashCommandUserOption, + ChatInputCommandInteraction, type ButtonInteraction, - type ChatInputCommandInteraction, + type GuildForumTag, + type GuildBasedChannel, } from "discord.js"; export let slashCommands = [ @@ -107,6 +109,8 @@ async function newCtf(interaction: ChatInputCommandInteraction) { ], }); + await ensureUnsolvedTag(forum); + let generalThread = await forum.threads.create({ name: "general", message: { @@ -304,32 +308,70 @@ async function handleJoinCtfButton(interaction: ButtonInteraction, forumId: stri }); } -async function getCtfForum(interaction: ButtonInteraction | ChatInputCommandInteraction): Promise<ForumChannel> { - let errorMsg = "This command can only be used in a CTF forum channel."; - - if (!interaction.guild) { - await interaction.reply({ content: errorMsg, flags: "Ephemeral" }); +/** + * Given either a forum, a thread in a forum, or an interaction from a slash command ran in a thread + * in a forum, returns that forum if it was created by this bot. + */ +export async function getCtfForum(interactionOrChannel: ChatInputCommandInteraction | GuildBasedChannel): Promise<ForumChannel> { + let interaction = (interactionOrChannel instanceof ChatInputCommandInteraction) ? interactionOrChannel : null; + async function done(): Promise<never> { + await interaction?.reply({ content: "This command can only be used in a CTF forum channel.", flags: "Ephemeral" }); throw "done"; } - let channel = await interaction.guild.channels.fetch(interaction.channelId); + let channel: GuildBasedChannel; + if (interactionOrChannel instanceof ChatInputCommandInteraction) { + if (!interactionOrChannel.guild) throw await done(); - if (!channel) { - await interaction.reply({ content: errorMsg, flags: "Ephemeral" }); - throw "done"; + let ch = await interactionOrChannel.guild.channels.fetch(interactionOrChannel.channelId); + + if (!ch) throw await done(); + + channel = ch; + } else { + channel = interactionOrChannel; } if (channel.type === ChannelType.GuildForum) { return channel as ForumChannel; } - - if (channel.isThread() && channel.parentId) { - let parent = await interaction.guild.channels.fetch(channel.parentId); - if (parent && parent.type === ChannelType.GuildForum) { - return parent as ForumChannel; + if (!channel.isThread() || !channel.parentId) + throw await done(); + + let parent = await channel.guild.channels.fetch(channel.parentId); + if (!parent || parent.type !== ChannelType.GuildForum) + throw await done(); + + let threads = await parent.threads.fetch(); + for (let thread of threads.threads.values()) { + if (thread.name !== "general" || + thread.ownerId !== channel.client.user?.id || + !thread.flags.has("Pinned")) { + continue; } + + return parent as ForumChannel; + } + + throw await done(); +} + +export async function ensureUnsolvedTag(forum: ForumChannel): Promise<GuildForumTag> { + let currentTags = forum.availableTags ?? []; + let existing = currentTags.find(tag => tag.name === "unsolved"); + if (existing) { + return existing; + } + + await forum.setAvailableTags([ + ...currentTags, + { name: "unsolved", moderated: false }, + ]); + + let updated = forum.availableTags?.find(tag => tag.name === "unsolved"); + if (!updated) { + throw new Error(`Failed to create unsolved tag for ${forum.id}`); } - await interaction.reply({ content: errorMsg, flags: "Ephemeral" }); - throw "done"; + return updated; } @@ -1,4 +1,5 @@ import { + ChannelType, Client, CommandInteraction, Events, @@ -7,7 +8,7 @@ import { Routes, } from "discord.js"; -import { buttonCommands, slashCommands } from "./commands.ts"; +import { buttonCommands, ensureUnsolvedTag, slashCommands, getCtfForum } from "./commands.ts"; function requireEnv(key: string): string { let value = process.env[key]; @@ -21,7 +22,7 @@ let token = requireEnv("DISCORD_TOKEN"); let clientId = requireEnv("DISCORD_CLIENT_ID"); let guildId = requireEnv("DISCORD_GUILD_ID"); -let client = new Client({ intents: [GatewayIntentBits.Guilds] }); +let client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages] }); let rest = new REST({ version: "10" }).setToken(token); @@ -72,6 +73,24 @@ client.on(Events.InteractionCreate, async (interaction) => { } }); +client.on(Events.ThreadCreate, async (thread) => { + try { + let forum = await getCtfForum(thread); + + if (thread.ownerId === client.user?.id) + return; + + let tag = await ensureUnsolvedTag(forum); + if (thread.appliedTags.includes(tag.id)) + return; + + await thread.setAppliedTags([...thread.appliedTags, tag.id]); + } catch (error) { + if (error === "done") return; + console.error("Failed to auto-tag thread", error); + } +}); + try { await registerSlashCommands(); await client.login(token); |
