summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMathias Magnusson <mathias@magnusson.space>2026-01-17 02:05:56 +0100
committerMathias Magnusson <mathias@magnusson.space>2026-01-17 02:05:56 +0100
commitac23b1f60e99269711727e92be83231ea9c310e1 (patch)
tree7362ae0f9ae2dffe5e500422572e25e4d521b68a
parent3eb6e7729c2fb5461b3f04d5f496084f18906e41 (diff)
downloadchalle-anka-ac23b1f60e99269711727e92be83231ea9c310e1.tar.gz
automatically add "unsolved"-tag on all new posts
-rw-r--r--commands.ts76
-rw-r--r--index.ts23
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;
}
diff --git a/index.ts b/index.ts
index 57e7ba9..acc61fa 100644
--- a/index.ts
+++ b/index.ts
@@ -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);