diff --git a/src/api/routes/channels/#channel_id/threads.ts b/src/api/routes/channels/#channel_id/threads.ts
index 3845b96d..8ef2c0fe 100644
--- a/src/api/routes/channels/#channel_id/threads.ts
+++ b/src/api/routes/channels/#channel_id/threads.ts
@@ -17,7 +17,22 @@
*/
import { handleMessage, postHandleMessage, route, sendMessage } from "@spacebar/api";
-import { Channel, emitEvent, User, uploadFile, Attachment, Member, ReadState, MessageCreateEvent, FieldErrors, getPermission, ThreadMember, Message } from "@spacebar/util";
+import {
+ Channel,
+ emitEvent,
+ User,
+ uploadFile,
+ Attachment,
+ Member,
+ ReadState,
+ MessageCreateEvent,
+ FieldErrors,
+ getPermission,
+ ThreadMember,
+ Message,
+ ChannelFlags,
+ DiscordApiErrors,
+} from "@spacebar/util";
import { ChannelType, MessageType, ThreadCreationSchema, MessageCreateAttachment, MessageCreateCloudAttachment } from "@spacebar/schemas";
import { Request, Response, Router } from "express";
@@ -56,7 +71,22 @@ router.post(
const channel = await Channel.findOneOrFail({
where: { id: channel_id },
+ relations: ["available_tags"],
});
+ if (!body.applied_tags?.length) {
+ const required = channel.flags & Number(ChannelFlags.FLAGS.REQUIRE_TAG);
+ //TODO better error
+ if (required) throw new Error("Tag is required for this API");
+ } else if (channel.available_tags) {
+ const realTags = new Map(channel.available_tags.map((tag) => [tag.id, tag]));
+ const bad = body.applied_tags.find((tag) => !realTags.has(tag));
+ //TODO better error
+ if (bad) throw new Error("Invalid tag " + bad);
+ const permsNeeded = body.applied_tags.find((_) => realTags.get(_)?.moderated);
+ if (permsNeeded) {
+ req.permission?.hasThrow("MANAGE_THREADS");
+ }
+ }
const user = await User.findOneOrFail({ where: { id: req.user_id } });
const thread = await Channel.createChannel(
@@ -72,6 +102,7 @@ router.post(
rate_limit_per_user: body.rate_limit_per_user,
type: body.type || (channel.threadOnly() ? ChannelType.GUILD_PUBLIC_THREAD : ChannelType.GUILD_PRIVATE_THREAD),
recipients: [],
+ applied_tags: body.applied_tags || [],
thread_metadata: {
archived: false,
auto_archive_duration: body.auto_archive_duration || channel.default_auto_archive_duration || 4320,
diff --git a/src/util/util/ChannelFlags.ts b/src/util/util/ChannelFlags.ts
new file mode 100644
index 00000000..e0bea434
--- /dev/null
+++ b/src/util/util/ChannelFlags.ts
@@ -0,0 +1,30 @@
+// based on https://github.com/discordjs/discord.js/blob/master/src/util/MessageFlags.js
+// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah, 2022 Erkin Alp Güney
+// @fc-license-skip
+
+import { BitField } from "./BitField";
+
+export class ChannelFlags extends BitField {
+ static FLAGS = {
+ GUILD_FEED_REMOVED: BigInt(1) << BigInt(0),
+ PINNED: BigInt(1) << BigInt(1),
+ ACTIVE_CHANNELS_REMOVED: BigInt(1) << BigInt(2),
+ REQUIRE_TAG: BigInt(1) << BigInt(3),
+ URGENT: BigInt(1) << BigInt(4),
+ IS_SPAM: BigInt(1) << BigInt(5),
+ //Missing 6
+ IS_GUILD_RESOURCE_CHANNEL: BigInt(1) << BigInt(7),
+ CLYDE_AI: BigInt(1) << BigInt(8),
+ IS_SCHEDULED_FOR_DELETION: BigInt(1) << BigInt(9),
+ //Unused 10
+ SUMMARIES_DISABLED: BigInt(1) << BigInt(11),
+ //Deprecated 12
+ IS_ROLE_SUBSCRIPTION_TEMPLATE_PREVIEW_CHANNEL: BigInt(1) << BigInt(13),
+ IS_BROADCASTING: BigInt(1) << BigInt(14),
+ HIDE_MEDIA_DOWNLOAD_OPTIONS: BigInt(1) << BigInt(15),
+ IS_JOIN_REQUEST_INTERVIEW_CHANNEL: BigInt(1) << BigInt(16),
+ OBFUSCATED: BigInt(1) << BigInt(17),
+ //Missing 18
+ IS_MODERATOR_REPORT_CHANNEL: BigInt(1) << BigInt(19),
+ };
+}
diff --git a/src/util/util/index.ts b/src/util/util/index.ts
index b2cfc568..aac4e3c0 100644
--- a/src/util/util/index.ts
+++ b/src/util/util/index.ts
@@ -47,6 +47,7 @@ export * from "./Timespan";
export * from "./Token";
export * from "./TraverseDirectory";
export * from "./WebAuthn";
+export * from "./ChannelFlags";
export * from "./Gifs";
export * from "./Application";
export * from "./NameValidation";
|