fix(slack): extract text and media from forwarded message attachments

This commit is contained in:
Tom Peri
2026-02-17 00:44:41 +02:00
committed by Peter Steinberger
parent 4928717b92
commit b57d29d833
3 changed files with 113 additions and 6 deletions

View File

@@ -1,6 +1,6 @@
import type { WebClient as SlackWebClient } from "@slack/web-api";
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 { fetchRemoteMedia } from "../../media/fetch.js";
import { saveMediaBuffer } from "../../media/store.js";
@@ -219,6 +219,73 @@ export async function resolveSlackMedia(params: {
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 = {
text: string;
userId?: string;