mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 11:21:23 +00:00
Discord: avoid reply spam on chunked sends
This commit is contained in:
@@ -887,6 +887,7 @@ async function dispatchDiscordComponentEvent(params: {
|
||||
rest: interaction.client.rest,
|
||||
runtime,
|
||||
replyToId,
|
||||
replyToMode,
|
||||
textLimit,
|
||||
maxLinesPerMessage: ctx.discordConfig?.maxLinesPerMessage,
|
||||
tableMode,
|
||||
|
||||
@@ -628,6 +628,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
||||
rest: client.rest,
|
||||
runtime,
|
||||
replyToId,
|
||||
replyToMode,
|
||||
textLimit,
|
||||
maxLinesPerMessage: discordConfig?.maxLinesPerMessage,
|
||||
tableMode,
|
||||
|
||||
@@ -84,4 +84,24 @@ describe("deliverDiscordReply", () => {
|
||||
expect(sendVoiceMessageDiscordMock).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessageDiscordMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses replyToId only for the first chunk when replyToMode is first", async () => {
|
||||
await deliverDiscordReply({
|
||||
replies: [
|
||||
{
|
||||
text: "1234567890",
|
||||
},
|
||||
],
|
||||
target: "channel:789",
|
||||
token: "token",
|
||||
runtime,
|
||||
textLimit: 5,
|
||||
replyToId: "reply-1",
|
||||
replyToMode: "first",
|
||||
});
|
||||
|
||||
expect(sendMessageDiscordMock).toHaveBeenCalledTimes(2);
|
||||
expect(sendMessageDiscordMock.mock.calls[0]?.[2]?.replyTo).toBe("reply-1");
|
||||
expect(sendMessageDiscordMock.mock.calls[1]?.[2]?.replyTo).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { RequestClient } from "@buape/carbon";
|
||||
import type { ChunkMode } from "../../auto-reply/chunk.js";
|
||||
import type { ReplyPayload } from "../../auto-reply/types.js";
|
||||
import type { MarkdownTableMode } from "../../config/types.base.js";
|
||||
import type { MarkdownTableMode, ReplyToMode } from "../../config/types.base.js";
|
||||
import { convertMarkdownTables } from "../../markdown/tables.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { chunkDiscordTextWithMode } from "../chunk.js";
|
||||
@@ -17,10 +17,29 @@ export async function deliverDiscordReply(params: {
|
||||
textLimit: number;
|
||||
maxLinesPerMessage?: number;
|
||||
replyToId?: string;
|
||||
replyToMode?: ReplyToMode;
|
||||
tableMode?: MarkdownTableMode;
|
||||
chunkMode?: ChunkMode;
|
||||
}) {
|
||||
const chunkLimit = Math.min(params.textLimit, 2000);
|
||||
const replyTo = params.replyToId?.trim() || undefined;
|
||||
const replyToMode = params.replyToMode ?? "all";
|
||||
// replyToMode=first should only apply to the first physical send.
|
||||
const replyOnce = replyToMode === "first";
|
||||
let replyUsed = false;
|
||||
const resolveReplyTo = () => {
|
||||
if (!replyTo) {
|
||||
return undefined;
|
||||
}
|
||||
if (!replyOnce) {
|
||||
return replyTo;
|
||||
}
|
||||
if (replyUsed) {
|
||||
return undefined;
|
||||
}
|
||||
replyUsed = true;
|
||||
return replyTo;
|
||||
};
|
||||
for (const payload of params.replies) {
|
||||
const mediaList = payload.mediaUrls ?? (payload.mediaUrl ? [payload.mediaUrl] : []);
|
||||
const rawText = payload.text ?? "";
|
||||
@@ -29,8 +48,6 @@ export async function deliverDiscordReply(params: {
|
||||
if (!text && mediaList.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const replyTo = params.replyToId?.trim() || undefined;
|
||||
|
||||
if (mediaList.length === 0) {
|
||||
const mode = params.chunkMode ?? "length";
|
||||
const chunks = chunkDiscordTextWithMode(text, {
|
||||
@@ -46,6 +63,7 @@ export async function deliverDiscordReply(params: {
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
const replyTo = resolveReplyTo();
|
||||
await sendMessageDiscord(params.target, trimmed, {
|
||||
token: params.token,
|
||||
rest: params.rest,
|
||||
@@ -63,6 +81,7 @@ export async function deliverDiscordReply(params: {
|
||||
|
||||
// Voice message path: audioAsVoice flag routes through sendVoiceMessageDiscord
|
||||
if (payload.audioAsVoice) {
|
||||
const replyTo = resolveReplyTo();
|
||||
await sendVoiceMessageDiscord(params.target, firstMedia, {
|
||||
token: params.token,
|
||||
rest: params.rest,
|
||||
@@ -71,6 +90,7 @@ export async function deliverDiscordReply(params: {
|
||||
});
|
||||
// Voice messages cannot include text; send remaining text separately if present
|
||||
if (text.trim()) {
|
||||
const replyTo = resolveReplyTo();
|
||||
await sendMessageDiscord(params.target, text, {
|
||||
token: params.token,
|
||||
rest: params.rest,
|
||||
@@ -80,6 +100,7 @@ export async function deliverDiscordReply(params: {
|
||||
}
|
||||
// Additional media items are sent as regular attachments (voice is single-file only)
|
||||
for (const extra of mediaList.slice(1)) {
|
||||
const replyTo = resolveReplyTo();
|
||||
await sendMessageDiscord(params.target, "", {
|
||||
token: params.token,
|
||||
rest: params.rest,
|
||||
@@ -91,6 +112,7 @@ export async function deliverDiscordReply(params: {
|
||||
continue;
|
||||
}
|
||||
|
||||
const replyTo = resolveReplyTo();
|
||||
await sendMessageDiscord(params.target, text, {
|
||||
token: params.token,
|
||||
rest: params.rest,
|
||||
@@ -99,6 +121,7 @@ export async function deliverDiscordReply(params: {
|
||||
replyTo,
|
||||
});
|
||||
for (const extra of mediaList.slice(1)) {
|
||||
const replyTo = resolveReplyTo();
|
||||
await sendMessageDiscord(params.target, "", {
|
||||
token: params.token,
|
||||
rest: params.rest,
|
||||
|
||||
Reference in New Issue
Block a user