mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 21:48:27 +00:00
fix(slack): extract text and media from forwarded message attachments
This commit is contained in:
committed by
Peter Steinberger
parent
4928717b92
commit
b57d29d833
@@ -1,6 +1,6 @@
|
|||||||
import type { WebClient as SlackWebClient } from "@slack/web-api";
|
import type { WebClient as SlackWebClient } from "@slack/web-api";
|
||||||
import type { FetchLike } from "../../media/fetch.js";
|
import type { FetchLike } from "../../media/fetch.js";
|
||||||
import type { SlackFile } from "../types.js";
|
import type { SlackAttachment, SlackFile } from "../types.js";
|
||||||
import { normalizeHostname } from "../../infra/net/hostname.js";
|
import { normalizeHostname } from "../../infra/net/hostname.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";
|
||||||
@@ -219,6 +219,73 @@ export async function resolveSlackMedia(params: {
|
|||||||
return results.length > 0 ? results : null;
|
return results.length > 0 ? results : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Extracts text and media from forwarded-message attachments. Returns null when empty. */
|
||||||
|
export async function resolveSlackAttachmentContent(params: {
|
||||||
|
attachments?: SlackAttachment[];
|
||||||
|
token: string;
|
||||||
|
maxBytes: number;
|
||||||
|
}): Promise<{ text: string; media: SlackMediaResult[] } | null> {
|
||||||
|
const attachments = params.attachments;
|
||||||
|
if (!attachments || attachments.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textBlocks: string[] = [];
|
||||||
|
const allMedia: SlackMediaResult[] = [];
|
||||||
|
|
||||||
|
for (const att of attachments) {
|
||||||
|
const text = att.text?.trim() || att.fallback?.trim();
|
||||||
|
if (text) {
|
||||||
|
const author = att.author_name;
|
||||||
|
const heading = author ? `[Forwarded message from ${author}]` : "[Forwarded message]";
|
||||||
|
textBlocks.push(`${heading}\n${text}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageUrl = att.image_url;
|
||||||
|
if (imageUrl) {
|
||||||
|
try {
|
||||||
|
const fetched = await fetchRemoteMedia({
|
||||||
|
url: imageUrl,
|
||||||
|
maxBytes: params.maxBytes,
|
||||||
|
});
|
||||||
|
if (fetched.buffer.byteLength <= params.maxBytes) {
|
||||||
|
const saved = await saveMediaBuffer(
|
||||||
|
fetched.buffer,
|
||||||
|
fetched.contentType,
|
||||||
|
"inbound",
|
||||||
|
params.maxBytes,
|
||||||
|
);
|
||||||
|
const label = fetched.fileName ?? "forwarded image";
|
||||||
|
allMedia.push({
|
||||||
|
path: saved.path,
|
||||||
|
contentType: fetched.contentType ?? saved.contentType,
|
||||||
|
placeholder: `[Forwarded image: ${label}]`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Skip images that fail to download
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (att.files && att.files.length > 0) {
|
||||||
|
const fileMedia = await resolveSlackMedia({
|
||||||
|
files: att.files,
|
||||||
|
token: params.token,
|
||||||
|
maxBytes: params.maxBytes,
|
||||||
|
});
|
||||||
|
if (fileMedia) {
|
||||||
|
allMedia.push(...fileMedia);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const combinedText = textBlocks.join("\n\n");
|
||||||
|
if (!combinedText && allMedia.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return { text: combinedText, media: allMedia };
|
||||||
|
}
|
||||||
|
|
||||||
export type SlackThreadStarter = {
|
export type SlackThreadStarter = {
|
||||||
text: string;
|
text: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import { resolveSlackChannelConfig } from "../channel-config.js";
|
|||||||
import { stripSlackMentionsForCommandDetection } from "../commands.js";
|
import { stripSlackMentionsForCommandDetection } from "../commands.js";
|
||||||
import { normalizeSlackChannelType, type SlackMonitorContext } from "../context.js";
|
import { normalizeSlackChannelType, type SlackMonitorContext } from "../context.js";
|
||||||
import {
|
import {
|
||||||
|
resolveSlackAttachmentContent,
|
||||||
resolveSlackMedia,
|
resolveSlackMedia,
|
||||||
resolveSlackThreadHistory,
|
resolveSlackThreadHistory,
|
||||||
resolveSlackThreadStarter,
|
resolveSlackThreadStarter,
|
||||||
@@ -342,8 +343,25 @@ export async function prepareSlackMessage(params: {
|
|||||||
token: ctx.botToken,
|
token: ctx.botToken,
|
||||||
maxBytes: ctx.mediaMaxBytes,
|
maxBytes: ctx.mediaMaxBytes,
|
||||||
});
|
});
|
||||||
const mediaPlaceholder = media ? media.map((m) => m.placeholder).join(" ") : undefined;
|
|
||||||
const rawBody = (message.text ?? "").trim() || mediaPlaceholder || "";
|
// Resolve forwarded message content (text + media) from Slack attachments
|
||||||
|
const attachmentContent = await resolveSlackAttachmentContent({
|
||||||
|
attachments: message.attachments,
|
||||||
|
token: ctx.botToken,
|
||||||
|
maxBytes: ctx.mediaMaxBytes,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Merge forwarded media into the message's media array
|
||||||
|
const mergedMedia = [...(media ?? []), ...(attachmentContent?.media ?? [])];
|
||||||
|
const effectiveDirectMedia = mergedMedia.length > 0 ? mergedMedia : null;
|
||||||
|
|
||||||
|
const mediaPlaceholder = effectiveDirectMedia
|
||||||
|
? effectiveDirectMedia.map((m) => m.placeholder).join(" ")
|
||||||
|
: undefined;
|
||||||
|
const rawBody =
|
||||||
|
[(message.text ?? "").trim(), attachmentContent?.text, mediaPlaceholder]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n") || "";
|
||||||
if (!rawBody) {
|
if (!rawBody) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -478,7 +496,7 @@ export async function prepareSlackMessage(params: {
|
|||||||
const snippet = starter.text.replace(/\s+/g, " ").slice(0, 80);
|
const snippet = starter.text.replace(/\s+/g, " ").slice(0, 80);
|
||||||
threadLabel = `Slack thread ${roomLabel}${snippet ? `: ${snippet}` : ""}`;
|
threadLabel = `Slack thread ${roomLabel}${snippet ? `: ${snippet}` : ""}`;
|
||||||
// If current message has no files but thread starter does, fetch starter's files
|
// If current message has no files but thread starter does, fetch starter's files
|
||||||
if (!media && starter.files && starter.files.length > 0) {
|
if (!effectiveDirectMedia && starter.files && starter.files.length > 0) {
|
||||||
threadStarterMedia = await resolveSlackMedia({
|
threadStarterMedia = await resolveSlackMedia({
|
||||||
files: starter.files,
|
files: starter.files,
|
||||||
token: ctx.botToken,
|
token: ctx.botToken,
|
||||||
@@ -554,8 +572,8 @@ export async function prepareSlackMessage(params: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use thread starter media if current message has none
|
// Use direct media (including forwarded attachment media) if available, else thread starter media
|
||||||
const effectiveMedia = media ?? threadStarterMedia;
|
const effectiveMedia = effectiveDirectMedia ?? threadStarterMedia;
|
||||||
const firstMedia = effectiveMedia?.[0];
|
const firstMedia = effectiveMedia?.[0];
|
||||||
|
|
||||||
const inboundHistory =
|
const inboundHistory =
|
||||||
|
|||||||
@@ -8,6 +8,26 @@ export type SlackFile = {
|
|||||||
url_private_download?: string;
|
url_private_download?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SlackAttachment = {
|
||||||
|
fallback?: string;
|
||||||
|
text?: string;
|
||||||
|
pretext?: string;
|
||||||
|
author_name?: string;
|
||||||
|
author_id?: string;
|
||||||
|
from_url?: string;
|
||||||
|
ts?: string;
|
||||||
|
channel_name?: string;
|
||||||
|
channel_id?: string;
|
||||||
|
is_msg_unfurl?: boolean;
|
||||||
|
is_share?: boolean;
|
||||||
|
image_url?: string;
|
||||||
|
image_width?: number;
|
||||||
|
image_height?: number;
|
||||||
|
thumb_url?: string;
|
||||||
|
files?: SlackFile[];
|
||||||
|
message_blocks?: unknown[];
|
||||||
|
};
|
||||||
|
|
||||||
export type SlackMessageEvent = {
|
export type SlackMessageEvent = {
|
||||||
type: "message";
|
type: "message";
|
||||||
user?: string;
|
user?: string;
|
||||||
@@ -22,6 +42,7 @@ export type SlackMessageEvent = {
|
|||||||
channel: string;
|
channel: string;
|
||||||
channel_type?: "im" | "mpim" | "channel" | "group";
|
channel_type?: "im" | "mpim" | "channel" | "group";
|
||||||
files?: SlackFile[];
|
files?: SlackFile[];
|
||||||
|
attachments?: SlackAttachment[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SlackAppMentionEvent = {
|
export type SlackAppMentionEvent = {
|
||||||
@@ -36,4 +57,5 @@ export type SlackAppMentionEvent = {
|
|||||||
parent_user_id?: string;
|
parent_user_id?: string;
|
||||||
channel: string;
|
channel: string;
|
||||||
channel_type?: "im" | "mpim" | "channel" | "group";
|
channel_type?: "im" | "mpim" | "channel" | "group";
|
||||||
|
attachments?: SlackAttachment[];
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user