mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 04:07:39 +00:00
fix(discord): support forum channel thread-create (#10062)
* fix(discord): support forum channel thread-create * fix: harden discord forum thread-create (#10062) (thanks @jarvis89757) --------- Co-authored-by: Shakker <shakkerdroid@gmail.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { RateLimitError } from "@buape/carbon";
|
||||
import { Routes } from "discord-api-types/v10";
|
||||
import { ChannelType, Routes } from "discord-api-types/v10";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
addRoleDiscord,
|
||||
@@ -60,15 +60,64 @@ describe("sendMessageDiscord", () => {
|
||||
});
|
||||
|
||||
it("creates a thread", async () => {
|
||||
const { rest, postMock } = makeRest();
|
||||
const { rest, getMock, postMock } = makeRest();
|
||||
postMock.mockResolvedValue({ id: "t1" });
|
||||
await createThreadDiscord("chan1", { name: "thread", messageId: "m1" }, { rest, token: "t" });
|
||||
expect(getMock).not.toHaveBeenCalled();
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
Routes.threads("chan1", "m1"),
|
||||
expect.objectContaining({ body: { name: "thread" } }),
|
||||
);
|
||||
});
|
||||
|
||||
it("creates forum threads with an initial message", async () => {
|
||||
const { rest, getMock, postMock } = makeRest();
|
||||
getMock.mockResolvedValue({ type: ChannelType.GuildForum });
|
||||
postMock.mockResolvedValue({ id: "t1" });
|
||||
await createThreadDiscord("chan1", { name: "thread" }, { rest, token: "t" });
|
||||
expect(getMock).toHaveBeenCalledWith(Routes.channel("chan1"));
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
Routes.threads("chan1"),
|
||||
expect.objectContaining({
|
||||
body: {
|
||||
name: "thread",
|
||||
message: { content: "thread" },
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("creates media threads with provided content", async () => {
|
||||
const { rest, getMock, postMock } = makeRest();
|
||||
getMock.mockResolvedValue({ type: ChannelType.GuildMedia });
|
||||
postMock.mockResolvedValue({ id: "t1" });
|
||||
await createThreadDiscord(
|
||||
"chan1",
|
||||
{ name: "thread", content: "initial forum post" },
|
||||
{ rest, token: "t" },
|
||||
);
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
Routes.threads("chan1"),
|
||||
expect.objectContaining({
|
||||
body: {
|
||||
name: "thread",
|
||||
message: { content: "initial forum post" },
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back when channel lookup is unavailable", async () => {
|
||||
const { rest, getMock, postMock } = makeRest();
|
||||
getMock.mockRejectedValue(new Error("lookup failed"));
|
||||
postMock.mockResolvedValue({ id: "t1" });
|
||||
await createThreadDiscord("chan1", { name: "thread" }, { rest, token: "t" });
|
||||
expect(postMock).toHaveBeenCalledWith(
|
||||
Routes.threads("chan1"),
|
||||
expect.objectContaining({ body: { name: "thread" } }),
|
||||
);
|
||||
});
|
||||
|
||||
it("lists active threads by guild", async () => {
|
||||
const { rest, getMock } = makeRest();
|
||||
getMock.mockResolvedValue({ threads: [] });
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { APIMessage } from "discord-api-types/v10";
|
||||
import { Routes } from "discord-api-types/v10";
|
||||
import type { APIChannel, APIMessage } from "discord-api-types/v10";
|
||||
import { ChannelType, Routes } from "discord-api-types/v10";
|
||||
import type {
|
||||
DiscordMessageEdit,
|
||||
DiscordMessageQuery,
|
||||
@@ -105,7 +105,26 @@ export async function createThreadDiscord(
|
||||
if (payload.autoArchiveMinutes) {
|
||||
body.auto_archive_duration = payload.autoArchiveMinutes;
|
||||
}
|
||||
const route = Routes.threads(channelId, payload.messageId);
|
||||
let channelType: ChannelType | undefined;
|
||||
if (!payload.messageId) {
|
||||
// Only detect channel kind for route-less thread creation.
|
||||
// If this lookup fails, keep prior behavior and let Discord validate.
|
||||
try {
|
||||
const channel = (await rest.get(Routes.channel(channelId))) as APIChannel | null | undefined;
|
||||
channelType = channel?.type;
|
||||
} catch {
|
||||
channelType = undefined;
|
||||
}
|
||||
}
|
||||
const isForumLike =
|
||||
channelType === ChannelType.GuildForum || channelType === ChannelType.GuildMedia;
|
||||
if (isForumLike) {
|
||||
const starterContent = payload.content?.trim() ? payload.content : payload.name;
|
||||
body.message = { content: starterContent };
|
||||
}
|
||||
const route = payload.messageId
|
||||
? Routes.threads(channelId, payload.messageId)
|
||||
: Routes.threads(channelId);
|
||||
return await rest.post(route, { body });
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ export type DiscordThreadCreate = {
|
||||
messageId?: string;
|
||||
name: string;
|
||||
autoArchiveMinutes?: number;
|
||||
content?: string;
|
||||
};
|
||||
|
||||
export type DiscordThreadList = {
|
||||
|
||||
Reference in New Issue
Block a user