mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-02 19:10:29 +00:00
refactor: extract Discord ack reaction helpers
This commit is contained in:
@@ -115,6 +115,10 @@ export async function handleDiscordMessagingAction(
|
||||
accountId: accountId ?? "default",
|
||||
})
|
||||
: undefined;
|
||||
const withReactionRuntimeOptions = <T extends Record<string, unknown>>(extra?: T) => ({
|
||||
...(reactionRuntimeOptions ?? cfgOptions),
|
||||
...(extra ?? {}),
|
||||
});
|
||||
const normalizeMessage = (message: unknown) => {
|
||||
if (!message || typeof message !== "object") {
|
||||
return message;
|
||||
@@ -137,44 +141,28 @@ export async function handleDiscordMessagingAction(
|
||||
removeErrorMessage: "Emoji is required to remove a Discord reaction.",
|
||||
});
|
||||
if (remove) {
|
||||
if (reactionRuntimeOptions) {
|
||||
await discordMessagingActionRuntime.removeReactionDiscord(channelId, messageId, emoji, {
|
||||
...reactionRuntimeOptions,
|
||||
});
|
||||
} else {
|
||||
await discordMessagingActionRuntime.removeReactionDiscord(
|
||||
channelId,
|
||||
messageId,
|
||||
emoji,
|
||||
cfgOptions,
|
||||
);
|
||||
}
|
||||
return jsonResult({ ok: true, removed: emoji });
|
||||
}
|
||||
if (isEmpty) {
|
||||
const removed = reactionRuntimeOptions
|
||||
? await discordMessagingActionRuntime.removeOwnReactionsDiscord(channelId, messageId, {
|
||||
...reactionRuntimeOptions,
|
||||
})
|
||||
: await discordMessagingActionRuntime.removeOwnReactionsDiscord(
|
||||
channelId,
|
||||
messageId,
|
||||
cfgOptions,
|
||||
);
|
||||
return jsonResult({ ok: true, removed: removed.removed });
|
||||
}
|
||||
if (reactionRuntimeOptions) {
|
||||
await discordMessagingActionRuntime.reactMessageDiscord(channelId, messageId, emoji, {
|
||||
...reactionRuntimeOptions,
|
||||
});
|
||||
} else {
|
||||
await discordMessagingActionRuntime.reactMessageDiscord(
|
||||
await discordMessagingActionRuntime.removeReactionDiscord(
|
||||
channelId,
|
||||
messageId,
|
||||
emoji,
|
||||
cfgOptions,
|
||||
withReactionRuntimeOptions(),
|
||||
);
|
||||
return jsonResult({ ok: true, removed: emoji });
|
||||
}
|
||||
if (isEmpty) {
|
||||
const removed = await discordMessagingActionRuntime.removeOwnReactionsDiscord(
|
||||
channelId,
|
||||
messageId,
|
||||
withReactionRuntimeOptions(),
|
||||
);
|
||||
return jsonResult({ ok: true, removed: removed.removed });
|
||||
}
|
||||
await discordMessagingActionRuntime.reactMessageDiscord(
|
||||
channelId,
|
||||
messageId,
|
||||
emoji,
|
||||
withReactionRuntimeOptions(),
|
||||
);
|
||||
return jsonResult({ ok: true, added: emoji });
|
||||
}
|
||||
case "reactions": {
|
||||
@@ -189,11 +177,7 @@ export async function handleDiscordMessagingAction(
|
||||
const reactions = await discordMessagingActionRuntime.fetchReactionsDiscord(
|
||||
channelId,
|
||||
messageId,
|
||||
{
|
||||
...cfgOptions,
|
||||
...(reactionRuntimeOptions ?? {}),
|
||||
limit,
|
||||
},
|
||||
withReactionRuntimeOptions({ limit }),
|
||||
);
|
||||
return jsonResult({ ok: true, reactions });
|
||||
}
|
||||
|
||||
70
extensions/discord/src/monitor/ack-reactions.ts
Normal file
70
extensions/discord/src/monitor/ack-reactions.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { RequestClient } from "@buape/carbon";
|
||||
import {
|
||||
createStatusReactionController,
|
||||
logAckFailure,
|
||||
type StatusReactionAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-feedback";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { createDiscordRuntimeAccountContext } from "../client.js";
|
||||
import { reactMessageDiscord, removeReactionDiscord } from "../send.js";
|
||||
import type { DiscordReactionRuntimeContext } from "../send.types.js";
|
||||
|
||||
export function createDiscordAckReactionContext(params: {
|
||||
rest: RequestClient;
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
}): DiscordReactionRuntimeContext {
|
||||
return {
|
||||
rest: params.rest,
|
||||
...createDiscordRuntimeAccountContext({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function createDiscordAckReactionAdapter(params: {
|
||||
channelId: string;
|
||||
messageId: string;
|
||||
reactionContext: DiscordReactionRuntimeContext;
|
||||
}): StatusReactionAdapter {
|
||||
return {
|
||||
setReaction: async (emoji) => {
|
||||
await reactMessageDiscord(params.channelId, params.messageId, emoji, params.reactionContext);
|
||||
},
|
||||
removeReaction: async (emoji) => {
|
||||
await removeReactionDiscord(
|
||||
params.channelId,
|
||||
params.messageId,
|
||||
emoji,
|
||||
params.reactionContext,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function queueInitialDiscordAckReaction(params: {
|
||||
enabled: boolean;
|
||||
shouldSendAckReaction: boolean;
|
||||
ackReaction: string | undefined;
|
||||
statusReactions: ReturnType<typeof createStatusReactionController>;
|
||||
reactionAdapter: StatusReactionAdapter;
|
||||
target: string;
|
||||
}) {
|
||||
if (params.enabled) {
|
||||
void params.statusReactions.setQueued();
|
||||
return;
|
||||
}
|
||||
if (!params.shouldSendAckReaction || !params.ackReaction) {
|
||||
return;
|
||||
}
|
||||
void params.reactionAdapter.setReaction(params.ackReaction).catch((err) => {
|
||||
logAckFailure({
|
||||
log: logVerbose,
|
||||
channel: "discord",
|
||||
target: params.target,
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
logAckFailure,
|
||||
logTypingFailure,
|
||||
shouldAckReaction as shouldAckReactionGate,
|
||||
type StatusReactionAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-feedback";
|
||||
import {
|
||||
formatInboundEnvelope,
|
||||
@@ -39,12 +38,14 @@ import { stripReasoningTagsFromText } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
|
||||
import { chunkDiscordTextWithMode } from "../chunk.js";
|
||||
import { createDiscordRuntimeAccountContext } from "../client.js";
|
||||
import { resolveDiscordDraftStreamingChunking } from "../draft-chunking.js";
|
||||
import { createDiscordDraftStream } from "../draft-stream.js";
|
||||
import { reactMessageDiscord, removeReactionDiscord } from "../send.js";
|
||||
import { editMessageDiscord } from "../send.messages.js";
|
||||
import type { DiscordReactionRuntimeContext } from "../send.types.js";
|
||||
import {
|
||||
createDiscordAckReactionAdapter,
|
||||
createDiscordAckReactionContext,
|
||||
queueInitialDiscordAckReaction,
|
||||
} from "./ack-reactions.js";
|
||||
import { normalizeDiscordSlug } from "./allow-list.js";
|
||||
import { resolveTimestampMs } from "./format.js";
|
||||
import {
|
||||
@@ -81,65 +82,6 @@ async function loadReplyRuntime() {
|
||||
return await replyRuntimePromise;
|
||||
}
|
||||
|
||||
function createDiscordAckReactionContext(params: {
|
||||
rest: RequestClient;
|
||||
cfg: OpenClawConfig;
|
||||
accountId: string;
|
||||
}): DiscordReactionRuntimeContext {
|
||||
return {
|
||||
rest: params.rest,
|
||||
...createDiscordRuntimeAccountContext({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function createDiscordAckReactionAdapter(params: {
|
||||
channelId: string;
|
||||
messageId: string;
|
||||
reactionContext: DiscordReactionRuntimeContext;
|
||||
}): StatusReactionAdapter {
|
||||
return {
|
||||
setReaction: async (emoji) => {
|
||||
await reactMessageDiscord(params.channelId, params.messageId, emoji, params.reactionContext);
|
||||
},
|
||||
removeReaction: async (emoji) => {
|
||||
await removeReactionDiscord(
|
||||
params.channelId,
|
||||
params.messageId,
|
||||
emoji,
|
||||
params.reactionContext,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function queueInitialDiscordAckReaction(params: {
|
||||
enabled: boolean;
|
||||
shouldSendAckReaction: boolean;
|
||||
ackReaction: string | undefined;
|
||||
statusReactions: ReturnType<typeof createStatusReactionController>;
|
||||
reactionAdapter: StatusReactionAdapter;
|
||||
target: string;
|
||||
}) {
|
||||
if (params.enabled) {
|
||||
void params.statusReactions.setQueued();
|
||||
return;
|
||||
}
|
||||
if (!params.shouldSendAckReaction || !params.ackReaction) {
|
||||
return;
|
||||
}
|
||||
void params.reactionAdapter.setReaction(params.ackReaction).catch((err) => {
|
||||
logAckFailure({
|
||||
log: logVerbose,
|
||||
channel: "discord",
|
||||
target: params.target,
|
||||
error: err,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isProcessAborted(abortSignal?: AbortSignal): boolean {
|
||||
return Boolean(abortSignal?.aborted);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,26 @@ import {
|
||||
formatReactionEmoji,
|
||||
normalizeReactionEmoji,
|
||||
} from "./send.shared.js";
|
||||
import type { DiscordReactionSummary, DiscordReactOpts } from "./send.types.js";
|
||||
import type {
|
||||
DiscordReactionRuntimeContext,
|
||||
DiscordReactionSummary,
|
||||
DiscordReactOpts,
|
||||
} from "./send.types.js";
|
||||
|
||||
function createDiscordReactionRuntimeClient(opts: DiscordReactionRuntimeContext) {
|
||||
return createDiscordClient(opts, opts.cfg);
|
||||
}
|
||||
|
||||
function resolveDiscordReactionClient(opts: DiscordReactOpts) {
|
||||
const cfg = opts.cfg ?? loadConfig();
|
||||
return createDiscordClient(opts, cfg);
|
||||
}
|
||||
|
||||
function isDiscordReactionRuntimeContext(
|
||||
opts: DiscordReactOpts,
|
||||
): opts is DiscordReactionRuntimeContext {
|
||||
return Boolean(opts.rest && opts.cfg && opts.accountId);
|
||||
}
|
||||
|
||||
export async function reactMessageDiscord(
|
||||
channelId: string,
|
||||
@@ -14,8 +33,9 @@ export async function reactMessageDiscord(
|
||||
emoji: string,
|
||||
opts: DiscordReactOpts = {},
|
||||
) {
|
||||
const cfg = opts.cfg ?? loadConfig();
|
||||
const { rest, request } = createDiscordClient(opts, cfg);
|
||||
const { rest, request } = isDiscordReactionRuntimeContext(opts)
|
||||
? createDiscordReactionRuntimeClient(opts)
|
||||
: resolveDiscordReactionClient(opts);
|
||||
const encoded = normalizeReactionEmoji(emoji);
|
||||
await request(
|
||||
() => rest.put(Routes.channelMessageOwnReaction(channelId, messageId, encoded)),
|
||||
@@ -30,8 +50,9 @@ export async function removeReactionDiscord(
|
||||
emoji: string,
|
||||
opts: DiscordReactOpts = {},
|
||||
) {
|
||||
const cfg = opts.cfg ?? loadConfig();
|
||||
const { rest } = createDiscordClient(opts, cfg);
|
||||
const { rest } = isDiscordReactionRuntimeContext(opts)
|
||||
? createDiscordReactionRuntimeClient(opts)
|
||||
: resolveDiscordReactionClient(opts);
|
||||
const encoded = normalizeReactionEmoji(emoji);
|
||||
await rest.delete(Routes.channelMessageOwnReaction(channelId, messageId, encoded));
|
||||
return { ok: true };
|
||||
@@ -42,8 +63,9 @@ export async function removeOwnReactionsDiscord(
|
||||
messageId: string,
|
||||
opts: DiscordReactOpts = {},
|
||||
): Promise<{ ok: true; removed: string[] }> {
|
||||
const cfg = opts.cfg ?? loadConfig();
|
||||
const { rest } = createDiscordClient(opts, cfg);
|
||||
const { rest } = isDiscordReactionRuntimeContext(opts)
|
||||
? createDiscordReactionRuntimeClient(opts)
|
||||
: resolveDiscordReactionClient(opts);
|
||||
const message = (await rest.get(Routes.channelMessage(channelId, messageId))) as {
|
||||
reactions?: Array<{ emoji: { id?: string | null; name?: string | null } }>;
|
||||
};
|
||||
@@ -74,8 +96,9 @@ export async function fetchReactionsDiscord(
|
||||
messageId: string,
|
||||
opts: DiscordReactOpts & { limit?: number } = {},
|
||||
): Promise<DiscordReactionSummary[]> {
|
||||
const cfg = opts.cfg ?? loadConfig();
|
||||
const { rest } = createDiscordClient(opts, cfg);
|
||||
const { rest } = isDiscordReactionRuntimeContext(opts)
|
||||
? createDiscordReactionRuntimeClient(opts)
|
||||
: resolveDiscordReactionClient(opts);
|
||||
const message = (await rest.get(Routes.channelMessage(channelId, messageId))) as {
|
||||
reactions?: Array<{
|
||||
count: number;
|
||||
|
||||
@@ -61,7 +61,6 @@ export type {
|
||||
DiscordChannelCreate,
|
||||
DiscordChannelEdit,
|
||||
DiscordChannelMove,
|
||||
DiscordOptionalRuntimeAccountContext,
|
||||
DiscordChannelPermissionSet,
|
||||
DiscordEmojiUpload,
|
||||
DiscordMessageEdit,
|
||||
|
||||
@@ -33,13 +33,6 @@ export type DiscordRuntimeAccountContext = {
|
||||
accountId: string;
|
||||
};
|
||||
|
||||
export type DiscordOptionalRuntimeAccountContext =
|
||||
| DiscordRuntimeAccountContext
|
||||
| {
|
||||
cfg?: undefined;
|
||||
accountId?: undefined;
|
||||
};
|
||||
|
||||
export type DiscordReactOpts = {
|
||||
cfg?: OpenClawConfig;
|
||||
accountId?: string;
|
||||
|
||||
Reference in New Issue
Block a user