mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 23:04:32 +00:00
Discord: ingest inbound stickers
This commit is contained in:
@@ -61,6 +61,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Discord/Streaming: apply `replyToMode: first` only to the first Discord chunk so block-streamed replies do not spam mention pings. (#20726) Thanks @thewilloftheshadow for the report.
|
- Discord/Streaming: apply `replyToMode: first` only to the first Discord chunk so block-streamed replies do not spam mention pings. (#20726) Thanks @thewilloftheshadow for the report.
|
||||||
|
|
||||||
- Discord/Gateway: handle close code 4014 (missing privileged gateway intents) without crashing the gateway. Thanks @thewilloftheshadow.
|
- Discord/Gateway: handle close code 4014 (missing privileged gateway intents) without crashing the gateway. Thanks @thewilloftheshadow.
|
||||||
|
- Discord: ingest inbound stickers as media so sticker-only messages and forwarded stickers are visible to agents. Thanks @thewilloftheshadow.
|
||||||
- Security/Net: strip sensitive headers (`Authorization`, `Proxy-Authorization`, `Cookie`, `Cookie2`) on cross-origin redirects in `fetchWithSsrFGuard` to prevent credential forwarding across origin boundaries. (#20313) Thanks @afurm.
|
- Security/Net: strip sensitive headers (`Authorization`, `Proxy-Authorization`, `Cookie`, `Cookie2`) on cross-origin redirects in `fetchWithSsrFGuard` to prevent credential forwarding across origin boundaries. (#20313) Thanks @afurm.
|
||||||
- Security/Systemd: reject CR/LF in systemd unit environment values and fix argument escaping so generated units cannot be injected with extra directives. Thanks @thewilloftheshadow.
|
- Security/Systemd: reject CR/LF in systemd unit environment values and fix argument escaping so generated units cannot be injected with extra directives. Thanks @thewilloftheshadow.
|
||||||
- Skills/Security: sanitize skill env overrides to block unsafe runtime injection variables and only allow sensitive keys when declared in skill metadata, with warnings for suspicious values. Thanks @thewilloftheshadow.
|
- Skills/Security: sanitize skill env overrides to block unsafe runtime injection variables and only allow sensitive keys when declared in skill metadata, with warnings for suspicious values. Thanks @thewilloftheshadow.
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ import type { DiscordMessageEvent, DiscordMessageHandler } from "./listeners.js"
|
|||||||
import { preflightDiscordMessage } from "./message-handler.preflight.js";
|
import { preflightDiscordMessage } from "./message-handler.preflight.js";
|
||||||
import type { DiscordMessagePreflightParams } from "./message-handler.preflight.types.js";
|
import type { DiscordMessagePreflightParams } from "./message-handler.preflight.types.js";
|
||||||
import { processDiscordMessage } from "./message-handler.process.js";
|
import { processDiscordMessage } from "./message-handler.process.js";
|
||||||
import { resolveDiscordMessageChannelId, resolveDiscordMessageText } from "./message-utils.js";
|
import {
|
||||||
|
hasDiscordMessageStickers,
|
||||||
|
resolveDiscordMessageChannelId,
|
||||||
|
resolveDiscordMessageText,
|
||||||
|
} from "./message-utils.js";
|
||||||
|
|
||||||
type DiscordMessageHandlerParams = Omit<
|
type DiscordMessageHandlerParams = Omit<
|
||||||
DiscordMessagePreflightParams,
|
DiscordMessagePreflightParams,
|
||||||
@@ -48,6 +52,9 @@ export function createDiscordMessageHandler(
|
|||||||
if (message.attachments && message.attachments.length > 0) {
|
if (message.attachments && message.attachments.length > 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasDiscordMessageStickers(message)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const baseText = resolveDiscordMessageText(message, { includeForwarded: false });
|
const baseText = resolveDiscordMessageText(message, { includeForwarded: false });
|
||||||
if (!baseText.trim()) {
|
if (!baseText.trim()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ChannelType, type Client, type Message } from "@buape/carbon";
|
import { ChannelType, type Client, type Message } from "@buape/carbon";
|
||||||
|
import { StickerFormatType } from "discord-api-types/v10";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
const fetchRemoteMedia = vi.fn();
|
const fetchRemoteMedia = vi.fn();
|
||||||
@@ -22,6 +23,7 @@ const {
|
|||||||
resolveDiscordMessageChannelId,
|
resolveDiscordMessageChannelId,
|
||||||
resolveDiscordMessageText,
|
resolveDiscordMessageText,
|
||||||
resolveForwardedMediaList,
|
resolveForwardedMediaList,
|
||||||
|
resolveMediaList,
|
||||||
} = await import("./message-utils.js");
|
} = await import("./message-utils.js");
|
||||||
|
|
||||||
function asMessage(payload: Record<string, unknown>): Message {
|
function asMessage(payload: Record<string, unknown>): Message {
|
||||||
@@ -102,6 +104,46 @@ describe("resolveForwardedMediaList", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("downloads forwarded stickers", async () => {
|
||||||
|
const sticker = {
|
||||||
|
id: "sticker-1",
|
||||||
|
name: "wave",
|
||||||
|
format_type: StickerFormatType.PNG,
|
||||||
|
};
|
||||||
|
fetchRemoteMedia.mockResolvedValueOnce({
|
||||||
|
buffer: Buffer.from("sticker"),
|
||||||
|
contentType: "image/png",
|
||||||
|
});
|
||||||
|
saveMediaBuffer.mockResolvedValueOnce({
|
||||||
|
path: "/tmp/sticker.png",
|
||||||
|
contentType: "image/png",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await resolveForwardedMediaList(
|
||||||
|
asMessage({
|
||||||
|
rawData: {
|
||||||
|
message_snapshots: [{ message: { sticker_items: [sticker] } }],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
512,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(fetchRemoteMedia).toHaveBeenCalledTimes(1);
|
||||||
|
expect(fetchRemoteMedia).toHaveBeenCalledWith({
|
||||||
|
url: "https://media.discordapp.net/stickers/sticker-1.png",
|
||||||
|
filePathHint: "wave.png",
|
||||||
|
});
|
||||||
|
expect(saveMediaBuffer).toHaveBeenCalledTimes(1);
|
||||||
|
expect(saveMediaBuffer).toHaveBeenCalledWith(expect.any(Buffer), "image/png", "inbound", 512);
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
path: "/tmp/sticker.png",
|
||||||
|
contentType: "image/png",
|
||||||
|
placeholder: "<media:sticker>",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("returns empty when no snapshots are present", async () => {
|
it("returns empty when no snapshots are present", async () => {
|
||||||
const result = await resolveForwardedMediaList(asMessage({}), 512);
|
const result = await resolveForwardedMediaList(asMessage({}), 512);
|
||||||
|
|
||||||
@@ -124,6 +166,51 @@ describe("resolveForwardedMediaList", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("resolveMediaList", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fetchRemoteMedia.mockReset();
|
||||||
|
saveMediaBuffer.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("downloads stickers", async () => {
|
||||||
|
const sticker = {
|
||||||
|
id: "sticker-2",
|
||||||
|
name: "hello",
|
||||||
|
format_type: StickerFormatType.PNG,
|
||||||
|
};
|
||||||
|
fetchRemoteMedia.mockResolvedValueOnce({
|
||||||
|
buffer: Buffer.from("sticker"),
|
||||||
|
contentType: "image/png",
|
||||||
|
});
|
||||||
|
saveMediaBuffer.mockResolvedValueOnce({
|
||||||
|
path: "/tmp/sticker-2.png",
|
||||||
|
contentType: "image/png",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await resolveMediaList(
|
||||||
|
asMessage({
|
||||||
|
stickers: [sticker],
|
||||||
|
}),
|
||||||
|
512,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(fetchRemoteMedia).toHaveBeenCalledTimes(1);
|
||||||
|
expect(fetchRemoteMedia).toHaveBeenCalledWith({
|
||||||
|
url: "https://media.discordapp.net/stickers/sticker-2.png",
|
||||||
|
filePathHint: "hello.png",
|
||||||
|
});
|
||||||
|
expect(saveMediaBuffer).toHaveBeenCalledTimes(1);
|
||||||
|
expect(saveMediaBuffer).toHaveBeenCalledWith(expect.any(Buffer), "image/png", "inbound", 512);
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
path: "/tmp/sticker-2.png",
|
||||||
|
contentType: "image/png",
|
||||||
|
placeholder: "<media:sticker>",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("resolveDiscordMessageText", () => {
|
describe("resolveDiscordMessageText", () => {
|
||||||
it("includes forwarded message snapshots in body text", () => {
|
it("includes forwarded message snapshots in body text", () => {
|
||||||
const text = resolveDiscordMessageText(
|
const text = resolveDiscordMessageText(
|
||||||
@@ -152,6 +239,23 @@ describe("resolveDiscordMessageText", () => {
|
|||||||
expect(text).toContain("[Forwarded message from @Bob]");
|
expect(text).toContain("[Forwarded message from @Bob]");
|
||||||
expect(text).toContain("forwarded hello");
|
expect(text).toContain("forwarded hello");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("uses sticker placeholders when content is empty", () => {
|
||||||
|
const text = resolveDiscordMessageText(
|
||||||
|
asMessage({
|
||||||
|
content: "",
|
||||||
|
stickers: [
|
||||||
|
{
|
||||||
|
id: "sticker-3",
|
||||||
|
name: "party",
|
||||||
|
format_type: StickerFormatType.PNG,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(text).toBe("<media:sticker> (1 sticker)");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("resolveDiscordChannelInfo", () => {
|
describe("resolveDiscordChannelInfo", () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { ChannelType, Client, Message } from "@buape/carbon";
|
import type { ChannelType, Client, Message } from "@buape/carbon";
|
||||||
import type { APIAttachment } from "discord-api-types/v10";
|
import { StickerFormatType, type APIAttachment, type APIStickerItem } from "discord-api-types/v10";
|
||||||
import { logVerbose } from "../../globals.js";
|
import { logVerbose } from "../../globals.js";
|
||||||
import { fetchRemoteMedia } from "../../media/fetch.js";
|
import { fetchRemoteMedia } from "../../media/fetch.js";
|
||||||
import { saveMediaBuffer } from "../../media/store.js";
|
import { saveMediaBuffer } from "../../media/store.js";
|
||||||
@@ -35,6 +35,8 @@ type DiscordSnapshotMessage = {
|
|||||||
content?: string | null;
|
content?: string | null;
|
||||||
embeds?: Array<{ description?: string | null; title?: string | null }> | null;
|
embeds?: Array<{ description?: string | null; title?: string | null }> | null;
|
||||||
attachments?: APIAttachment[] | null;
|
attachments?: APIAttachment[] | null;
|
||||||
|
stickers?: APIStickerItem[] | null;
|
||||||
|
sticker_items?: APIStickerItem[] | null;
|
||||||
author?: DiscordSnapshotAuthor | null;
|
author?: DiscordSnapshotAuthor | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -48,6 +50,7 @@ const DISCORD_CHANNEL_INFO_CACHE = new Map<
|
|||||||
string,
|
string,
|
||||||
{ value: DiscordChannelInfo | null; expiresAt: number }
|
{ value: DiscordChannelInfo | null; expiresAt: number }
|
||||||
>();
|
>();
|
||||||
|
const DISCORD_STICKER_ASSET_BASE_URL = "https://media.discordapp.net/stickers";
|
||||||
|
|
||||||
export function __resetDiscordChannelInfoCacheForTest() {
|
export function __resetDiscordChannelInfoCacheForTest() {
|
||||||
DISCORD_CHANNEL_INFO_CACHE.clear();
|
DISCORD_CHANNEL_INFO_CACHE.clear();
|
||||||
@@ -122,21 +125,55 @@ export async function resolveDiscordChannelInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeStickerItems(value: unknown): APIStickerItem[] {
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return value.filter(
|
||||||
|
(entry): entry is APIStickerItem =>
|
||||||
|
Boolean(entry) &&
|
||||||
|
typeof entry === "object" &&
|
||||||
|
typeof (entry as { id?: unknown }).id === "string" &&
|
||||||
|
typeof (entry as { name?: unknown }).name === "string",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveDiscordMessageStickers(message: Message): APIStickerItem[] {
|
||||||
|
const stickers = (message as { stickers?: unknown }).stickers;
|
||||||
|
const normalized = normalizeStickerItems(stickers);
|
||||||
|
if (normalized.length > 0) {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
const rawData = (message as { rawData?: { sticker_items?: unknown; stickers?: unknown } })
|
||||||
|
.rawData;
|
||||||
|
return normalizeStickerItems(rawData?.sticker_items ?? rawData?.stickers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDiscordSnapshotStickers(snapshot: DiscordSnapshotMessage): APIStickerItem[] {
|
||||||
|
return normalizeStickerItems(snapshot.stickers ?? snapshot.sticker_items);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasDiscordMessageStickers(message: Message): boolean {
|
||||||
|
return resolveDiscordMessageStickers(message).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
export async function resolveMediaList(
|
export async function resolveMediaList(
|
||||||
message: Message,
|
message: Message,
|
||||||
maxBytes: number,
|
maxBytes: number,
|
||||||
): Promise<DiscordMediaInfo[]> {
|
): Promise<DiscordMediaInfo[]> {
|
||||||
const attachments = message.attachments ?? [];
|
|
||||||
if (attachments.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const out: DiscordMediaInfo[] = [];
|
const out: DiscordMediaInfo[] = [];
|
||||||
await appendResolvedMediaFromAttachments({
|
await appendResolvedMediaFromAttachments({
|
||||||
attachments,
|
attachments: message.attachments ?? [],
|
||||||
maxBytes,
|
maxBytes,
|
||||||
out,
|
out,
|
||||||
errorPrefix: "discord: failed to download attachment",
|
errorPrefix: "discord: failed to download attachment",
|
||||||
});
|
});
|
||||||
|
await appendResolvedMediaFromStickers({
|
||||||
|
stickers: resolveDiscordMessageStickers(message),
|
||||||
|
maxBytes,
|
||||||
|
out,
|
||||||
|
errorPrefix: "discord: failed to download sticker",
|
||||||
|
});
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +193,12 @@ export async function resolveForwardedMediaList(
|
|||||||
out,
|
out,
|
||||||
errorPrefix: "discord: failed to download forwarded attachment",
|
errorPrefix: "discord: failed to download forwarded attachment",
|
||||||
});
|
});
|
||||||
|
await appendResolvedMediaFromStickers({
|
||||||
|
stickers: snapshot.message ? resolveDiscordSnapshotStickers(snapshot.message) : [],
|
||||||
|
maxBytes,
|
||||||
|
out,
|
||||||
|
errorPrefix: "discord: failed to download forwarded sticker",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -194,6 +237,100 @@ async function appendResolvedMediaFromAttachments(params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DiscordStickerAssetCandidate = {
|
||||||
|
url: string;
|
||||||
|
fileName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function resolveStickerAssetCandidates(sticker: APIStickerItem): DiscordStickerAssetCandidate[] {
|
||||||
|
const baseName = sticker.name?.trim() || `sticker-${sticker.id}`;
|
||||||
|
switch (sticker.format_type) {
|
||||||
|
case StickerFormatType.GIF:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: `${DISCORD_STICKER_ASSET_BASE_URL}/${sticker.id}.gif`,
|
||||||
|
fileName: `${baseName}.gif`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
case StickerFormatType.Lottie:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: `${DISCORD_STICKER_ASSET_BASE_URL}/${sticker.id}.png?size=160`,
|
||||||
|
fileName: `${baseName}.png`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: `${DISCORD_STICKER_ASSET_BASE_URL}/${sticker.id}.json`,
|
||||||
|
fileName: `${baseName}.json`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
case StickerFormatType.APNG:
|
||||||
|
case StickerFormatType.PNG:
|
||||||
|
default:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
url: `${DISCORD_STICKER_ASSET_BASE_URL}/${sticker.id}.png`,
|
||||||
|
fileName: `${baseName}.png`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatStickerError(err: unknown): string {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
return err.message;
|
||||||
|
}
|
||||||
|
if (typeof err === "string") {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.stringify(err) ?? "unknown error";
|
||||||
|
} catch {
|
||||||
|
return "unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function appendResolvedMediaFromStickers(params: {
|
||||||
|
stickers?: APIStickerItem[] | null;
|
||||||
|
maxBytes: number;
|
||||||
|
out: DiscordMediaInfo[];
|
||||||
|
errorPrefix: string;
|
||||||
|
}) {
|
||||||
|
const stickers = params.stickers;
|
||||||
|
if (!stickers || stickers.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const sticker of stickers) {
|
||||||
|
const candidates = resolveStickerAssetCandidates(sticker);
|
||||||
|
let lastError: unknown;
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
try {
|
||||||
|
const fetched = await fetchRemoteMedia({
|
||||||
|
url: candidate.url,
|
||||||
|
filePathHint: candidate.fileName,
|
||||||
|
});
|
||||||
|
const saved = await saveMediaBuffer(
|
||||||
|
fetched.buffer,
|
||||||
|
fetched.contentType,
|
||||||
|
"inbound",
|
||||||
|
params.maxBytes,
|
||||||
|
);
|
||||||
|
params.out.push({
|
||||||
|
path: saved.path,
|
||||||
|
contentType: saved.contentType,
|
||||||
|
placeholder: "<media:sticker>",
|
||||||
|
});
|
||||||
|
lastError = null;
|
||||||
|
break;
|
||||||
|
} catch (err) {
|
||||||
|
lastError = err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (lastError) {
|
||||||
|
logVerbose(`${params.errorPrefix} ${sticker.id}: ${formatStickerError(lastError)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function inferPlaceholder(attachment: APIAttachment): string {
|
function inferPlaceholder(attachment: APIAttachment): string {
|
||||||
const mime = attachment.content_type ?? "";
|
const mime = attachment.content_type ?? "";
|
||||||
if (mime.startsWith("image/")) {
|
if (mime.startsWith("image/")) {
|
||||||
@@ -232,13 +369,37 @@ function buildDiscordAttachmentPlaceholder(attachments?: APIAttachment[]): strin
|
|||||||
return `${tag} (${count} ${suffix})`;
|
return `${tag} (${count} ${suffix})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildDiscordStickerPlaceholder(stickers?: APIStickerItem[]): string {
|
||||||
|
if (!stickers || stickers.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const count = stickers.length;
|
||||||
|
const label = count === 1 ? "sticker" : "stickers";
|
||||||
|
return `<media:sticker> (${count} ${label})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDiscordMediaPlaceholder(params: {
|
||||||
|
attachments?: APIAttachment[];
|
||||||
|
stickers?: APIStickerItem[];
|
||||||
|
}): string {
|
||||||
|
const attachmentText = buildDiscordAttachmentPlaceholder(params.attachments);
|
||||||
|
const stickerText = buildDiscordStickerPlaceholder(params.stickers);
|
||||||
|
if (attachmentText && stickerText) {
|
||||||
|
return `${attachmentText}\n${stickerText}`;
|
||||||
|
}
|
||||||
|
return attachmentText || stickerText || "";
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveDiscordMessageText(
|
export function resolveDiscordMessageText(
|
||||||
message: Message,
|
message: Message,
|
||||||
options?: { fallbackText?: string; includeForwarded?: boolean },
|
options?: { fallbackText?: string; includeForwarded?: boolean },
|
||||||
): string {
|
): string {
|
||||||
const baseText =
|
const baseText =
|
||||||
message.content?.trim() ||
|
message.content?.trim() ||
|
||||||
buildDiscordAttachmentPlaceholder(message.attachments) ||
|
buildDiscordMediaPlaceholder({
|
||||||
|
attachments: message.attachments ?? undefined,
|
||||||
|
stickers: resolveDiscordMessageStickers(message),
|
||||||
|
}) ||
|
||||||
message.embeds?.[0]?.description ||
|
message.embeds?.[0]?.description ||
|
||||||
options?.fallbackText?.trim() ||
|
options?.fallbackText?.trim() ||
|
||||||
"";
|
"";
|
||||||
@@ -299,7 +460,10 @@ function resolveDiscordMessageSnapshots(message: Message): DiscordMessageSnapsho
|
|||||||
|
|
||||||
function resolveDiscordSnapshotMessageText(snapshot: DiscordSnapshotMessage): string {
|
function resolveDiscordSnapshotMessageText(snapshot: DiscordSnapshotMessage): string {
|
||||||
const content = snapshot.content?.trim() ?? "";
|
const content = snapshot.content?.trim() ?? "";
|
||||||
const attachmentText = buildDiscordAttachmentPlaceholder(snapshot.attachments ?? undefined);
|
const attachmentText = buildDiscordMediaPlaceholder({
|
||||||
|
attachments: snapshot.attachments ?? undefined,
|
||||||
|
stickers: resolveDiscordSnapshotStickers(snapshot),
|
||||||
|
});
|
||||||
const embed = snapshot.embeds?.[0];
|
const embed = snapshot.embeds?.[0];
|
||||||
const embedText = embed?.description?.trim() || embed?.title?.trim() || "";
|
const embedText = embed?.description?.trim() || embed?.title?.trim() || "";
|
||||||
return content || attachmentText || embedText || "";
|
return content || attachmentText || embedText || "";
|
||||||
|
|||||||
Reference in New Issue
Block a user