This commit is contained in:
Solvely-Colin 2026-01-29 21:53:23 -05:00 committed by GitHub
commit 06875c92a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 89 additions and 3 deletions

View File

@ -233,11 +233,16 @@ export async function handleDiscordMessagingAction(
const replyTo = readStringParam(params, "replyTo"); const replyTo = readStringParam(params, "replyTo");
const embeds = const embeds =
Array.isArray(params.embeds) && params.embeds.length > 0 ? params.embeds : undefined; Array.isArray(params.embeds) && params.embeds.length > 0 ? params.embeds : undefined;
const components =
Array.isArray(params.components) && params.components.length > 0
? params.components
: undefined;
const result = await sendMessageDiscord(to, content, { const result = await sendMessageDiscord(to, content, {
...(accountId ? { accountId } : {}), ...(accountId ? { accountId } : {}),
mediaUrl, mediaUrl,
replyTo, replyTo,
embeds, embeds,
components,
}); });
return jsonResult({ ok: true, result }); return jsonResult({ ok: true, result });
} }
@ -279,18 +284,31 @@ export async function handleDiscordMessagingAction(
const channelId = resolveChannelId(); const channelId = resolveChannelId();
const name = readStringParam(params, "name", { required: true }); const name = readStringParam(params, "name", { required: true });
const messageId = readStringParam(params, "messageId"); const messageId = readStringParam(params, "messageId");
// Optional initial content (required for forum posts).
const content = readStringParam(params, "content") ?? undefined;
// Optional applied tag ids (forum posts).
const appliedTagIds = Array.isArray(params.appliedTagIds)
? params.appliedTagIds.filter((x) => typeof x === "string" && x.trim())
: undefined;
const autoArchiveMinutesRaw = params.autoArchiveMinutes; const autoArchiveMinutesRaw = params.autoArchiveMinutes;
const autoArchiveMinutes = const autoArchiveMinutes =
typeof autoArchiveMinutesRaw === "number" && Number.isFinite(autoArchiveMinutesRaw) typeof autoArchiveMinutesRaw === "number" && Number.isFinite(autoArchiveMinutesRaw)
? autoArchiveMinutesRaw ? autoArchiveMinutesRaw
: undefined; : undefined;
const thread = accountId const thread = accountId
? await createThreadDiscord( ? await createThreadDiscord(
channelId, channelId,
{ name, messageId, autoArchiveMinutes }, { name, messageId, autoArchiveMinutes, content, appliedTagIds },
{ accountId }, { accountId },
) )
: await createThreadDiscord(channelId, { name, messageId, autoArchiveMinutes }); : await createThreadDiscord(channelId, {
name,
messageId,
autoArchiveMinutes,
content,
appliedTagIds,
});
return jsonResult({ ok: true, thread }); return jsonResult({ ok: true, thread });
} }
case "threadList": { case "threadList": {

View File

@ -87,6 +87,30 @@ function buildSendSchema(options: { includeButtons: boolean; includeCards: boole
}, },
), ),
), ),
embeds: Type.Optional(
Type.Array(
Type.Object(
{},
{
additionalProperties: true,
description:
"Provider-specific embed objects (Discord embeds, etc.). Passed through to the channel adapter when supported.",
},
),
),
),
components: Type.Optional(
Type.Array(
Type.Object(
{},
{
additionalProperties: true,
description:
"Provider-specific message components (Discord buttons/selects, etc.). Passed through when supported.",
},
),
),
),
}; };
if (!options.includeButtons) delete props.buttons; if (!options.includeButtons) delete props.buttons;
if (!options.includeCards) delete props.card; if (!options.includeCards) delete props.card;

View File

@ -37,6 +37,7 @@ export async function handleDiscordMessageAction(
const mediaUrl = readStringParam(params, "media", { trim: false }); const mediaUrl = readStringParam(params, "media", { trim: false });
const replyTo = readStringParam(params, "replyTo"); const replyTo = readStringParam(params, "replyTo");
const embeds = Array.isArray(params.embeds) ? params.embeds : undefined; const embeds = Array.isArray(params.embeds) ? params.embeds : undefined;
const components = Array.isArray(params.components) ? params.components : undefined;
return await handleDiscordAction( return await handleDiscordAction(
{ {
action: "sendMessage", action: "sendMessage",
@ -46,6 +47,7 @@ export async function handleDiscordMessageAction(
mediaUrl: mediaUrl ?? undefined, mediaUrl: mediaUrl ?? undefined,
replyTo: replyTo ?? undefined, replyTo: replyTo ?? undefined,
embeds, embeds,
components,
}, },
cfg, cfg,
); );
@ -180,6 +182,10 @@ export async function handleDiscordMessageAction(
if (action === "thread-create") { if (action === "thread-create") {
const name = readStringParam(params, "threadName", { required: true }); const name = readStringParam(params, "threadName", { required: true });
const messageId = readStringParam(params, "messageId"); const messageId = readStringParam(params, "messageId");
// Optional initial post content (required for forum post creation).
const content = readStringParam(params, "message");
// Optional forum tag ids.
const appliedTagIds = readStringArrayParam(params, "appliedTagIds");
const autoArchiveMinutes = readNumberParam(params, "autoArchiveMin", { const autoArchiveMinutes = readNumberParam(params, "autoArchiveMin", {
integer: true, integer: true,
}); });
@ -190,6 +196,8 @@ export async function handleDiscordMessageAction(
channelId: resolveChannelId(), channelId: resolveChannelId(),
name, name,
messageId, messageId,
content,
appliedTagIds,
autoArchiveMinutes, autoArchiveMinutes,
}, },
cfg, cfg,

View File

@ -94,10 +94,30 @@ export async function createThreadDiscord(
) { ) {
const rest = resolveDiscordRest(opts); const rest = resolveDiscordRest(opts);
const body: Record<string, unknown> = { name: payload.name }; const body: Record<string, unknown> = { name: payload.name };
if (payload.autoArchiveMinutes) { if (payload.autoArchiveMinutes) {
body.auto_archive_duration = payload.autoArchiveMinutes; body.auto_archive_duration = payload.autoArchiveMinutes;
} }
const route = Routes.threads(channelId, payload.messageId);
// Forum posts (channel type 15) require an initial message payload.
// If content is provided, create the thread with an initial post.
if (payload.content) {
body.message = { content: payload.content };
if (Array.isArray(payload.appliedTagIds) && payload.appliedTagIds.length) {
body.applied_tags = payload.appliedTagIds;
}
// NOTE: discord-api-types Routes doesn't currently expose a helper for
// POST /channels/{channel.id}/threads (it only exposes listing archived threads),
// so use the raw route string.
const route = `/channels/${channelId}/threads`;
return await rest.post(route, { body });
}
// Regular threads: create from an existing message when messageId is provided,
// otherwise create a standard thread in the channel.
const route = payload.messageId
? Routes.threads(channelId, payload.messageId)
: `/channels/${channelId}/threads`;
return await rest.post(route, { body }); return await rest.post(route, { body });
} }

View File

@ -29,6 +29,7 @@ type DiscordSendOpts = {
replyTo?: string; replyTo?: string;
retry?: RetryConfig; retry?: RetryConfig;
embeds?: unknown[]; embeds?: unknown[];
components?: unknown[];
}; };
export async function sendMessageDiscord( export async function sendMessageDiscord(
@ -63,6 +64,7 @@ export async function sendMessageDiscord(
request, request,
accountInfo.config.maxLinesPerMessage, accountInfo.config.maxLinesPerMessage,
opts.embeds, opts.embeds,
opts.components,
chunkMode, chunkMode,
); );
} else { } else {
@ -74,6 +76,7 @@ export async function sendMessageDiscord(
request, request,
accountInfo.config.maxLinesPerMessage, accountInfo.config.maxLinesPerMessage,
opts.embeds, opts.embeds,
opts.components,
chunkMode, chunkMode,
); );
} }

View File

@ -277,6 +277,7 @@ async function sendDiscordText(
request: DiscordRequest, request: DiscordRequest,
maxLinesPerMessage?: number, maxLinesPerMessage?: number,
embeds?: unknown[], embeds?: unknown[],
components?: unknown[],
chunkMode?: ChunkMode, chunkMode?: ChunkMode,
) { ) {
if (!text.trim()) { if (!text.trim()) {
@ -297,6 +298,7 @@ async function sendDiscordText(
content: chunks[0], content: chunks[0],
message_reference: messageReference, message_reference: messageReference,
...(embeds?.length ? { embeds } : {}), ...(embeds?.length ? { embeds } : {}),
...(components?.length ? { components } : {}),
}, },
}) as Promise<{ id: string; channel_id: string }>, }) as Promise<{ id: string; channel_id: string }>,
"text", "text",
@ -313,6 +315,7 @@ async function sendDiscordText(
content: chunk, content: chunk,
message_reference: isFirst ? messageReference : undefined, message_reference: isFirst ? messageReference : undefined,
...(isFirst && embeds?.length ? { embeds } : {}), ...(isFirst && embeds?.length ? { embeds } : {}),
...(isFirst && components?.length ? { components } : {}),
}, },
}) as Promise<{ id: string; channel_id: string }>, }) as Promise<{ id: string; channel_id: string }>,
"text", "text",
@ -334,6 +337,7 @@ async function sendDiscordMedia(
request: DiscordRequest, request: DiscordRequest,
maxLinesPerMessage?: number, maxLinesPerMessage?: number,
embeds?: unknown[], embeds?: unknown[],
components?: unknown[],
chunkMode?: ChunkMode, chunkMode?: ChunkMode,
) { ) {
const media = await loadWebMedia(mediaUrl); const media = await loadWebMedia(mediaUrl);
@ -354,6 +358,7 @@ async function sendDiscordMedia(
content: caption || undefined, content: caption || undefined,
message_reference: messageReference, message_reference: messageReference,
...(embeds?.length ? { embeds } : {}), ...(embeds?.length ? { embeds } : {}),
...(components?.length ? { components } : {}),
files: [ files: [
{ {
data: media.buffer, data: media.buffer,
@ -374,6 +379,7 @@ async function sendDiscordMedia(
request, request,
maxLinesPerMessage, maxLinesPerMessage,
undefined, undefined,
undefined,
chunkMode, chunkMode,
); );
} }

View File

@ -67,9 +67,16 @@ export type DiscordMessageEdit = {
}; };
export type DiscordThreadCreate = { export type DiscordThreadCreate = {
/** Optional message id to start a thread from (for classic threads). */
messageId?: string; messageId?: string;
/** Thread / forum post title. */
name: string; name: string;
/** Auto-archive duration in minutes. */
autoArchiveMinutes?: number; autoArchiveMinutes?: number;
/** Optional initial post content (required for forum post creation). */
content?: string;
/** Optional forum tag ids (applied tags). */
appliedTagIds?: string[];
}; };
export type DiscordThreadList = { export type DiscordThreadList = {