fix(discord): support applied_tags parameter for forum thread creation

Forum channels that require tags fail with "A tag is required" when
creating threads because there was no way to pass tag IDs. Add
appliedTags parameter to the thread-create action so forum posts can
include required tags from the channel's available_tags list.
This commit is contained in:
Pushkar Kathayat
2026-03-01 11:17:28 +05:30
committed by Peter Steinberger
parent 5b64b96c6c
commit 7f4d1b7531
6 changed files with 56 additions and 6 deletions

View File

@@ -363,13 +363,17 @@ export async function handleDiscordMessagingAction(
typeof autoArchiveMinutesRaw === "number" && Number.isFinite(autoArchiveMinutesRaw)
? autoArchiveMinutesRaw
: undefined;
const appliedTags = readStringArrayParam(params, "appliedTags");
const payload = {
name,
messageId,
autoArchiveMinutes,
content,
appliedTags: appliedTags ?? undefined,
};
const thread = accountId
? await createThreadDiscord(
channelId,
{ name, messageId, autoArchiveMinutes, content },
{ accountId },
)
: await createThreadDiscord(channelId, { name, messageId, autoArchiveMinutes, content });
? await createThreadDiscord(channelId, payload, { accountId })
: await createThreadDiscord(channelId, payload);
return jsonResult({ ok: true, thread });
}
case "threadList": {

View File

@@ -312,6 +312,7 @@ function buildThreadSchema() {
return {
threadName: Type.Optional(Type.String()),
autoArchiveMin: Type.Optional(Type.Number()),
appliedTags: Type.Optional(Type.Array(Type.String())),
};
}

View File

@@ -230,6 +230,7 @@ export async function handleDiscordMessageAction(
const autoArchiveMinutes = readNumberParam(params, "autoArchiveMin", {
integer: true,
});
const appliedTags = readStringArrayParam(params, "appliedTags");
return await handleDiscordAction(
{
action: "threadCreate",
@@ -239,6 +240,7 @@ export async function handleDiscordMessageAction(
messageId,
content,
autoArchiveMinutes,
appliedTags: appliedTags ?? undefined,
},
cfg,
actionOptions,

View File

@@ -76,6 +76,44 @@ describe("sendMessageDiscord", () => {
);
});
it("passes applied_tags for forum threads", async () => {
const { rest, getMock, postMock } = makeDiscordRest();
getMock.mockResolvedValue({ type: ChannelType.GuildForum });
postMock.mockResolvedValue({ id: "t1" });
await createThreadDiscord(
"chan1",
{ name: "tagged post", appliedTags: ["tag1", "tag2"] },
{ rest, token: "t" },
);
expect(postMock).toHaveBeenCalledWith(
Routes.threads("chan1"),
expect.objectContaining({
body: {
name: "tagged post",
message: { content: "tagged post" },
applied_tags: ["tag1", "tag2"],
},
}),
);
});
it("omits applied_tags for non-forum threads", async () => {
const { rest, getMock, postMock } = makeDiscordRest();
getMock.mockResolvedValue({ type: ChannelType.GuildText });
postMock.mockResolvedValue({ id: "t1" });
await createThreadDiscord(
"chan1",
{ name: "thread", appliedTags: ["tag1"] },
{ rest, token: "t" },
);
expect(postMock).toHaveBeenCalledWith(
Routes.threads("chan1"),
expect.objectContaining({
body: expect.not.objectContaining({ applied_tags: expect.anything() }),
}),
);
});
it("falls back when channel lookup is unavailable", async () => {
const { rest, getMock, postMock } = makeDiscordRest();
getMock.mockRejectedValue(new Error("lookup failed"));

View File

@@ -124,6 +124,9 @@ export async function createThreadDiscord(
if (isForumLike) {
const starterContent = payload.content?.trim() ? payload.content : payload.name;
body.message = { content: starterContent };
if (payload.appliedTags?.length) {
body.applied_tags = payload.appliedTags;
}
}
// When creating a standalone thread (no messageId) in a non-forum channel,
// default to public thread (type 11). Discord defaults to private (type 12)

View File

@@ -74,6 +74,8 @@ export type DiscordThreadCreate = {
content?: string;
/** Discord thread type (default: PublicThread for standalone threads). */
type?: number;
/** Tag IDs to apply when creating a forum/media thread (Discord `applied_tags`). */
appliedTags?: string[];
};
export type DiscordThreadList = {