mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-20 21:51:28 +00:00
fix(discord): recover forwarded referenced message content
# Conflicts: # extensions/discord/src/monitor/message-utils.ts
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { ChannelType, type Client, type Message } from "@buape/carbon";
|
||||
import { StickerFormatType } from "discord-api-types/v10";
|
||||
import { MessageReferenceType, StickerFormatType } from "discord-api-types/v10";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const fetchRemoteMedia = vi.fn();
|
||||
@@ -135,6 +135,35 @@ function asForwardedSnapshotMessage(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function asReferencedForwardMessage(params: {
|
||||
content?: string;
|
||||
embeds?: Array<{ title?: string; description?: string }>;
|
||||
attachments?: Array<Record<string, unknown>>;
|
||||
messageReferenceType?: MessageReferenceType;
|
||||
}) {
|
||||
return asMessage({
|
||||
content: "",
|
||||
messageReference: {
|
||||
type: params.messageReferenceType ?? MessageReferenceType.Forward,
|
||||
message_id: "m0",
|
||||
channel_id: "c1",
|
||||
},
|
||||
referencedMessage: asMessage({
|
||||
id: "m0",
|
||||
channelId: "c1",
|
||||
content: params.content ?? "",
|
||||
attachments: params.attachments ?? [],
|
||||
embeds: params.embeds ?? [],
|
||||
stickers: [],
|
||||
author: {
|
||||
id: "u2",
|
||||
username: "Bob",
|
||||
discriminator: "0",
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
describe("resolveDiscordMessageChannelId", () => {
|
||||
it.each([
|
||||
{
|
||||
@@ -295,6 +324,38 @@ describe("resolveForwardedMediaList", () => {
|
||||
expect(fetchRemoteMedia).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("downloads forwarded referenced attachments when snapshots are absent", async () => {
|
||||
const attachment = {
|
||||
id: "att-ref-1",
|
||||
url: "https://cdn.discordapp.com/attachments/1/ref-image.png",
|
||||
filename: "ref-image.png",
|
||||
content_type: "image/png",
|
||||
};
|
||||
fetchRemoteMedia.mockResolvedValueOnce({
|
||||
buffer: Buffer.from("image"),
|
||||
contentType: "image/png",
|
||||
});
|
||||
saveMediaBuffer.mockResolvedValueOnce({
|
||||
path: "/tmp/ref-image.png",
|
||||
contentType: "image/png",
|
||||
});
|
||||
|
||||
const result = await resolveForwardedMediaList(
|
||||
asReferencedForwardMessage({
|
||||
attachments: [attachment],
|
||||
}),
|
||||
512,
|
||||
);
|
||||
|
||||
expectSinglePngDownload({
|
||||
result,
|
||||
expectedUrl: attachment.url,
|
||||
filePathHint: attachment.filename,
|
||||
expectedPath: "/tmp/ref-image.png",
|
||||
placeholder: "<media:image>",
|
||||
});
|
||||
});
|
||||
|
||||
it("skips snapshots without attachments", async () => {
|
||||
const result = await resolveForwardedMediaList(
|
||||
asMessage({
|
||||
@@ -775,6 +836,30 @@ describe("resolveDiscordMessageText", () => {
|
||||
expect(text).toContain("forwarded hello");
|
||||
});
|
||||
|
||||
it("falls back to referenced forward message text when snapshots are absent", () => {
|
||||
const text = resolveDiscordMessageText(
|
||||
asReferencedForwardMessage({
|
||||
content: "forwarded from referenced message",
|
||||
}),
|
||||
{ includeForwarded: true },
|
||||
);
|
||||
|
||||
expect(text).toContain("[Forwarded message from @Bob]");
|
||||
expect(text).toContain("forwarded from referenced message");
|
||||
});
|
||||
|
||||
it("does not treat ordinary replies as forwarded context", () => {
|
||||
const text = resolveDiscordMessageText(
|
||||
asReferencedForwardMessage({
|
||||
content: "quoted reply content",
|
||||
messageReferenceType: MessageReferenceType.Default,
|
||||
}),
|
||||
{ includeForwarded: true },
|
||||
);
|
||||
|
||||
expect(text).toBe("");
|
||||
});
|
||||
|
||||
it("resolves user mentions in content", () => {
|
||||
const text = resolveDiscordMessageText(
|
||||
asMessage({
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { ChannelType, Client, Message } from "@buape/carbon";
|
||||
import { StickerFormatType, type APIAttachment, type APIStickerItem } from "discord-api-types/v10";
|
||||
import {
|
||||
MessageReferenceType,
|
||||
StickerFormatType,
|
||||
type APIAttachment,
|
||||
type APIStickerItem,
|
||||
} from "discord-api-types/v10";
|
||||
import { fetchRemoteMedia, type FetchLike } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
|
||||
import { buildMediaPayload } from "openclaw/plugin-sdk/reply-payload";
|
||||
@@ -253,35 +258,61 @@ export async function resolveForwardedMediaList(
|
||||
options?: DiscordMediaResolveOptions,
|
||||
): Promise<DiscordMediaInfo[]> {
|
||||
const snapshots = resolveDiscordMessageSnapshots(message);
|
||||
if (snapshots.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const out: DiscordMediaInfo[] = [];
|
||||
const resolvedSsrFPolicy = resolveDiscordMediaSsrFPolicy(options?.ssrfPolicy);
|
||||
for (const snapshot of snapshots) {
|
||||
await appendResolvedMediaFromAttachments({
|
||||
attachments: snapshot.message?.attachments,
|
||||
maxBytes,
|
||||
out,
|
||||
errorPrefix: "discord: failed to download forwarded attachment",
|
||||
fetchImpl: options?.fetchImpl,
|
||||
ssrfPolicy: resolvedSsrFPolicy,
|
||||
readIdleTimeoutMs: options?.readIdleTimeoutMs,
|
||||
totalTimeoutMs: options?.totalTimeoutMs,
|
||||
abortSignal: options?.abortSignal,
|
||||
});
|
||||
await appendResolvedMediaFromStickers({
|
||||
stickers: snapshot.message ? resolveDiscordSnapshotStickers(snapshot.message) : [],
|
||||
maxBytes,
|
||||
out,
|
||||
errorPrefix: "discord: failed to download forwarded sticker",
|
||||
fetchImpl: options?.fetchImpl,
|
||||
ssrfPolicy: resolvedSsrFPolicy,
|
||||
readIdleTimeoutMs: options?.readIdleTimeoutMs,
|
||||
totalTimeoutMs: options?.totalTimeoutMs,
|
||||
abortSignal: options?.abortSignal,
|
||||
});
|
||||
if (snapshots.length > 0) {
|
||||
for (const snapshot of snapshots) {
|
||||
await appendResolvedMediaFromAttachments({
|
||||
attachments: snapshot.message?.attachments,
|
||||
maxBytes,
|
||||
out,
|
||||
errorPrefix: "discord: failed to download forwarded attachment",
|
||||
fetchImpl: options?.fetchImpl,
|
||||
ssrfPolicy: resolvedSsrFPolicy,
|
||||
readIdleTimeoutMs: options?.readIdleTimeoutMs,
|
||||
totalTimeoutMs: options?.totalTimeoutMs,
|
||||
abortSignal: options?.abortSignal,
|
||||
});
|
||||
await appendResolvedMediaFromStickers({
|
||||
stickers: snapshot.message ? resolveDiscordSnapshotStickers(snapshot.message) : [],
|
||||
maxBytes,
|
||||
out,
|
||||
errorPrefix: "discord: failed to download forwarded sticker",
|
||||
fetchImpl: options?.fetchImpl,
|
||||
ssrfPolicy: resolvedSsrFPolicy,
|
||||
readIdleTimeoutMs: options?.readIdleTimeoutMs,
|
||||
totalTimeoutMs: options?.totalTimeoutMs,
|
||||
abortSignal: options?.abortSignal,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
const referencedForward = resolveDiscordReferencedForwardMessage(message);
|
||||
if (!referencedForward) {
|
||||
return out;
|
||||
}
|
||||
await appendResolvedMediaFromAttachments({
|
||||
attachments: referencedForward.attachments,
|
||||
maxBytes,
|
||||
out,
|
||||
errorPrefix: "discord: failed to download forwarded attachment",
|
||||
fetchImpl: options?.fetchImpl,
|
||||
ssrfPolicy: resolvedSsrFPolicy,
|
||||
readIdleTimeoutMs: options?.readIdleTimeoutMs,
|
||||
totalTimeoutMs: options?.totalTimeoutMs,
|
||||
abortSignal: options?.abortSignal,
|
||||
});
|
||||
await appendResolvedMediaFromStickers({
|
||||
stickers: resolveDiscordMessageStickers(referencedForward),
|
||||
maxBytes,
|
||||
out,
|
||||
errorPrefix: "discord: failed to download forwarded sticker",
|
||||
fetchImpl: options?.fetchImpl,
|
||||
ssrfPolicy: resolvedSsrFPolicy,
|
||||
readIdleTimeoutMs: options?.readIdleTimeoutMs,
|
||||
totalTimeoutMs: options?.totalTimeoutMs,
|
||||
abortSignal: options?.abortSignal,
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -635,35 +666,23 @@ function resolveDiscordMentions(text: string, message: Message): string {
|
||||
}
|
||||
|
||||
function resolveDiscordForwardedMessagesText(message: Message): string {
|
||||
return resolveDiscordForwardedMessagesTextFromSnapshots(resolveDiscordMessageSnapshots(message));
|
||||
}
|
||||
|
||||
function resolveDiscordMessageSnapshots(message: Message): DiscordMessageSnapshot[] {
|
||||
const rawData = (message as { rawData?: { message_snapshots?: unknown } }).rawData;
|
||||
return normalizeDiscordMessageSnapshots(
|
||||
rawData?.message_snapshots ??
|
||||
(message as { message_snapshots?: unknown }).message_snapshots ??
|
||||
(message as { messageSnapshots?: unknown }).messageSnapshots,
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeDiscordMessageSnapshots(snapshots: unknown): DiscordMessageSnapshot[] {
|
||||
if (!Array.isArray(snapshots)) {
|
||||
return [];
|
||||
const snapshots = resolveDiscordMessageSnapshots(message);
|
||||
if (snapshots.length > 0) {
|
||||
return resolveDiscordForwardedMessagesTextFromSnapshots(snapshots);
|
||||
}
|
||||
return snapshots.filter(
|
||||
(entry): entry is DiscordMessageSnapshot => Boolean(entry) && typeof entry === "object",
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveDiscordForwardedMessagesTextFromSnapshots(snapshots: unknown): string {
|
||||
const forwardedBlocks = normalizeDiscordMessageSnapshots(snapshots)
|
||||
.map((snapshot) => buildDiscordForwardedMessageBlock(snapshot.message))
|
||||
.filter((entry): entry is string => Boolean(entry));
|
||||
if (forwardedBlocks.length === 0) {
|
||||
const referencedForward = resolveDiscordReferencedForwardMessage(message);
|
||||
if (!referencedForward) {
|
||||
return "";
|
||||
}
|
||||
return forwardedBlocks.join("\n\n");
|
||||
const referencedText = resolveDiscordMessageText(referencedForward, {
|
||||
includeForwarded: true,
|
||||
});
|
||||
if (!referencedText) {
|
||||
return "";
|
||||
}
|
||||
const authorLabel = formatDiscordSnapshotAuthor(referencedForward.author);
|
||||
const heading = authorLabel ? `[Forwarded message from ${authorLabel}]` : "[Forwarded message]";
|
||||
return `${heading}\n${referencedText}`;
|
||||
}
|
||||
|
||||
function buildDiscordForwardedMessageBlock(
|
||||
@@ -681,6 +700,12 @@ function buildDiscordForwardedMessageBlock(
|
||||
return `${heading}\n${text}`;
|
||||
}
|
||||
|
||||
function resolveDiscordReferencedForwardMessage(message: Message): Message | null {
|
||||
return message.messageReference?.type === MessageReferenceType.Forward
|
||||
? message.referencedMessage
|
||||
: null;
|
||||
}
|
||||
|
||||
function resolveDiscordSnapshotMessageText(snapshot: DiscordSnapshotMessage): string {
|
||||
const content = snapshot.content?.trim() ?? "";
|
||||
const attachmentText = buildDiscordMediaPlaceholder({
|
||||
|
||||
Reference in New Issue
Block a user