| import type { OpenClawConfig } from "../../../config/config.js"; |
| import { |
| getThreadBindingManager, |
| type ThreadBindingRecord, |
| } from "../../../discord/monitor/thread-bindings.js"; |
| import { |
| sendMessageDiscord, |
| sendPollDiscord, |
| sendWebhookMessageDiscord, |
| } from "../../../discord/send.js"; |
| import type { OutboundIdentity } from "../../../infra/outbound/identity.js"; |
| import { normalizeDiscordOutboundTarget } from "../normalize/discord.js"; |
| import type { ChannelOutboundAdapter } from "../types.js"; |
| import { sendTextMediaPayload } from "./direct-text-media.js"; |
|
|
| function resolveDiscordOutboundTarget(params: { |
| to: string; |
| threadId?: string | number | null; |
| }): string { |
| if (params.threadId == null) { |
| return params.to; |
| } |
| const threadId = String(params.threadId).trim(); |
| if (!threadId) { |
| return params.to; |
| } |
| return `channel:${threadId}`; |
| } |
|
|
| function resolveDiscordWebhookIdentity(params: { |
| identity?: OutboundIdentity; |
| binding: ThreadBindingRecord; |
| }): { username?: string; avatarUrl?: string } { |
| const usernameRaw = params.identity?.name?.trim(); |
| const fallbackUsername = params.binding.label?.trim() || params.binding.agentId; |
| const username = (usernameRaw || fallbackUsername || "").slice(0, 80) || undefined; |
| const avatarUrl = params.identity?.avatarUrl?.trim() || undefined; |
| return { username, avatarUrl }; |
| } |
|
|
| async function maybeSendDiscordWebhookText(params: { |
| cfg?: OpenClawConfig; |
| text: string; |
| threadId?: string | number | null; |
| accountId?: string | null; |
| identity?: OutboundIdentity; |
| replyToId?: string | null; |
| }): Promise<{ messageId: string; channelId: string } | null> { |
| if (params.threadId == null) { |
| return null; |
| } |
| const threadId = String(params.threadId).trim(); |
| if (!threadId) { |
| return null; |
| } |
| const manager = getThreadBindingManager(params.accountId ?? undefined); |
| if (!manager) { |
| return null; |
| } |
| const binding = manager.getByThreadId(threadId); |
| if (!binding?.webhookId || !binding?.webhookToken) { |
| return null; |
| } |
| const persona = resolveDiscordWebhookIdentity({ |
| identity: params.identity, |
| binding, |
| }); |
| const result = await sendWebhookMessageDiscord(params.text, { |
| webhookId: binding.webhookId, |
| webhookToken: binding.webhookToken, |
| accountId: binding.accountId, |
| threadId: binding.threadId, |
| cfg: params.cfg, |
| replyTo: params.replyToId ?? undefined, |
| username: persona.username, |
| avatarUrl: persona.avatarUrl, |
| }); |
| return result; |
| } |
|
|
| export const discordOutbound: ChannelOutboundAdapter = { |
| deliveryMode: "direct", |
| chunker: null, |
| textChunkLimit: 2000, |
| pollMaxOptions: 10, |
| resolveTarget: ({ to }) => normalizeDiscordOutboundTarget(to), |
| sendPayload: async (ctx) => |
| await sendTextMediaPayload({ channel: "discord", ctx, adapter: discordOutbound }), |
| sendText: async ({ cfg, to, text, accountId, deps, replyToId, threadId, identity, silent }) => { |
| if (!silent) { |
| const webhookResult = await maybeSendDiscordWebhookText({ |
| cfg, |
| text, |
| threadId, |
| accountId, |
| identity, |
| replyToId, |
| }).catch(() => null); |
| if (webhookResult) { |
| return { channel: "discord", ...webhookResult }; |
| } |
| } |
| const send = deps?.sendDiscord ?? sendMessageDiscord; |
| const target = resolveDiscordOutboundTarget({ to, threadId }); |
| const result = await send(target, text, { |
| verbose: false, |
| replyTo: replyToId ?? undefined, |
| accountId: accountId ?? undefined, |
| silent: silent ?? undefined, |
| cfg, |
| }); |
| return { channel: "discord", ...result }; |
| }, |
| sendMedia: async ({ |
| cfg, |
| to, |
| text, |
| mediaUrl, |
| mediaLocalRoots, |
| accountId, |
| deps, |
| replyToId, |
| threadId, |
| silent, |
| }) => { |
| const send = deps?.sendDiscord ?? sendMessageDiscord; |
| const target = resolveDiscordOutboundTarget({ to, threadId }); |
| const result = await send(target, text, { |
| verbose: false, |
| mediaUrl, |
| mediaLocalRoots, |
| replyTo: replyToId ?? undefined, |
| accountId: accountId ?? undefined, |
| silent: silent ?? undefined, |
| cfg, |
| }); |
| return { channel: "discord", ...result }; |
| }, |
| sendPoll: async ({ cfg, to, poll, accountId, threadId, silent }) => { |
| const target = resolveDiscordOutboundTarget({ to, threadId }); |
| return await sendPollDiscord(target, poll, { |
| accountId: accountId ?? undefined, |
| silent: silent ?? undefined, |
| cfg, |
| }); |
| }, |
| }; |
|
|