mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-10 00:11:19 +00:00
fix(slack): include attachment text in thread context (#97727)
* fix(slack): include attachment text in thread context * fix(slack): cover attachment block fallback * fix(slack): collect full block thread text * refactor(slack): share thread block text extraction Co-authored-by: notask-007 <37237335+chthtlo@users.noreply.github.com> --------- Co-authored-by: notask-007 <> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
parent
939f9aeff5
commit
94ff3c6b85
5 changed files with 366 additions and 209 deletions
190
extensions/slack/src/monitor/block-text.ts
Normal file
190
extensions/slack/src/monitor/block-text.ts
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
import {
|
||||
normalizeOptionalString,
|
||||
readStringValue as readString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
type SlackTextObject = {
|
||||
text?: unknown;
|
||||
};
|
||||
|
||||
type SlackRichTextElement = {
|
||||
type?: unknown;
|
||||
text?: unknown;
|
||||
url?: unknown;
|
||||
user_id?: unknown;
|
||||
channel_id?: unknown;
|
||||
usergroup_id?: unknown;
|
||||
name?: unknown;
|
||||
range?: unknown;
|
||||
elements?: unknown;
|
||||
};
|
||||
|
||||
type SlackBlockLike = {
|
||||
type?: unknown;
|
||||
text?: unknown;
|
||||
elements?: unknown;
|
||||
fields?: unknown;
|
||||
alt_text?: unknown;
|
||||
title?: unknown;
|
||||
};
|
||||
|
||||
type SlackBlocksText = {
|
||||
text: string;
|
||||
hasRichText: boolean;
|
||||
};
|
||||
|
||||
function readTextObject(value: unknown): string | undefined {
|
||||
if (!value || typeof value !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return normalizeOptionalString(readString((value as SlackTextObject).text));
|
||||
}
|
||||
|
||||
function renderSlackRichTextLeaf(element: SlackRichTextElement): string {
|
||||
switch (element.type) {
|
||||
case "text":
|
||||
return readString(element.text) ?? "";
|
||||
case "link":
|
||||
return readString(element.text) ?? readString(element.url) ?? "";
|
||||
case "user": {
|
||||
const userId = readString(element.user_id);
|
||||
return userId ? `<@${userId}>` : "";
|
||||
}
|
||||
case "channel": {
|
||||
const channelId = readString(element.channel_id);
|
||||
return channelId ? `<#${channelId}>` : "";
|
||||
}
|
||||
case "usergroup": {
|
||||
const usergroupId = readString(element.usergroup_id);
|
||||
return usergroupId ? `<!subteam^${usergroupId}>` : "";
|
||||
}
|
||||
case "broadcast": {
|
||||
const range = readString(element.range);
|
||||
return range ? `<!${range}>` : "";
|
||||
}
|
||||
case "emoji": {
|
||||
const name = readString(element.name);
|
||||
return name ? `:${name}:` : "";
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function renderSlackRichTextElements(elements: unknown): string {
|
||||
if (!Array.isArray(elements)) {
|
||||
return "";
|
||||
}
|
||||
const parts: string[] = [];
|
||||
for (const rawElement of elements) {
|
||||
if (!rawElement || typeof rawElement !== "object") {
|
||||
continue;
|
||||
}
|
||||
const element = rawElement as SlackRichTextElement;
|
||||
switch (element.type) {
|
||||
case "rich_text_section":
|
||||
case "rich_text_preformatted":
|
||||
case "rich_text_quote":
|
||||
parts.push(renderSlackRichTextElements(element.elements));
|
||||
break;
|
||||
case "rich_text_list": {
|
||||
const listParts: string[] = [];
|
||||
if (Array.isArray(element.elements)) {
|
||||
for (const child of element.elements) {
|
||||
if (!child || typeof child !== "object") {
|
||||
continue;
|
||||
}
|
||||
const rendered = renderSlackRichTextElements((child as SlackRichTextElement).elements);
|
||||
if (rendered) {
|
||||
listParts.push(rendered);
|
||||
}
|
||||
}
|
||||
}
|
||||
parts.push(listParts.join("\n"));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
parts.push(renderSlackRichTextLeaf(element));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return parts.join("");
|
||||
}
|
||||
|
||||
function readSlackBlockText(block: unknown): string | undefined {
|
||||
if (!block || typeof block !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const blockLike = block as SlackBlockLike;
|
||||
switch (blockLike.type) {
|
||||
case "rich_text":
|
||||
return normalizeOptionalString(renderSlackRichTextElements(blockLike.elements));
|
||||
case "section": {
|
||||
const text = readTextObject(blockLike.text);
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
if (!Array.isArray(blockLike.fields)) {
|
||||
return undefined;
|
||||
}
|
||||
const fields = blockLike.fields.flatMap((field) => readTextObject(field) ?? []);
|
||||
return fields.length > 0 ? fields.join("\n") : undefined;
|
||||
}
|
||||
case "header":
|
||||
return readTextObject(blockLike.text);
|
||||
case "context": {
|
||||
if (!Array.isArray(blockLike.elements)) {
|
||||
return undefined;
|
||||
}
|
||||
const parts = blockLike.elements.flatMap((element) => readTextObject(element) ?? []);
|
||||
return parts.length > 0 ? parts.join(" ") : undefined;
|
||||
}
|
||||
case "image":
|
||||
return (
|
||||
normalizeOptionalString(readString(blockLike.alt_text)) ?? readTextObject(blockLike.title)
|
||||
);
|
||||
case "video":
|
||||
return (
|
||||
readTextObject(blockLike.title) ?? normalizeOptionalString(readString(blockLike.alt_text))
|
||||
);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveSlackBlocksText(blocks: unknown[] | undefined): SlackBlocksText | undefined {
|
||||
if (!blocks?.length) {
|
||||
return undefined;
|
||||
}
|
||||
const parts: string[] = [];
|
||||
let hasRichText = false;
|
||||
for (const block of blocks) {
|
||||
if (block && typeof block === "object" && (block as SlackBlockLike).type === "rich_text") {
|
||||
hasRichText = true;
|
||||
}
|
||||
const text = readSlackBlockText(block);
|
||||
if (text) {
|
||||
parts.push(text);
|
||||
}
|
||||
}
|
||||
return parts.length > 0 ? { text: parts.join("\n"), hasRichText } : undefined;
|
||||
}
|
||||
|
||||
export function chooseSlackPrimaryText(params: {
|
||||
messageText: string | undefined;
|
||||
blocksText: SlackBlocksText | undefined;
|
||||
}): string | undefined {
|
||||
const { messageText, blocksText } = params;
|
||||
if (!blocksText) {
|
||||
return messageText;
|
||||
}
|
||||
if (!messageText) {
|
||||
return blocksText.text;
|
||||
}
|
||||
if (blocksText.hasRichText && blocksText.text.length > messageText.length) {
|
||||
return blocksText.text;
|
||||
}
|
||||
return blocksText.text.length > messageText.length && blocksText.text.startsWith(messageText)
|
||||
? blocksText.text
|
||||
: messageText;
|
||||
}
|
||||
|
|
@ -1022,6 +1022,82 @@ describe("resolveSlackThreadHistory", () => {
|
|||
expect(result[1]?.text).toBe("hello");
|
||||
});
|
||||
|
||||
it("extracts thread text from Slack attachment and block surfaces", async () => {
|
||||
const replies = vi.fn().mockResolvedValueOnce({
|
||||
messages: [
|
||||
{
|
||||
text: " ",
|
||||
bot_id: "BMONITOR",
|
||||
ts: "1.000",
|
||||
attachments: [
|
||||
{
|
||||
title: "Filesystem on /dev/sda1 has only 14.93% available space left.",
|
||||
fallback: "Alert: filesystem space is low",
|
||||
fields: [{ title: "Host", value: "dc2.ipa.mgt" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: " ",
|
||||
bot_id: "BMONITOR",
|
||||
ts: "2.000",
|
||||
blocks: [{ type: "section", text: { type: "mrkdwn", text: "Pod restart rate is high" } }],
|
||||
},
|
||||
{
|
||||
text: " ",
|
||||
bot_id: "BMONITOR",
|
||||
ts: "3.000",
|
||||
attachments: [
|
||||
{
|
||||
blocks: [
|
||||
{ type: "header", text: { type: "plain_text", text: "Alert firing" } },
|
||||
{
|
||||
type: "section",
|
||||
fields: [
|
||||
{ type: "mrkdwn", text: "*host:* dc2.ipa.mgt" },
|
||||
{ type: "mrkdwn", text: "*device:* /dev/sda1" },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "section",
|
||||
text: { type: "mrkdwn", text: "Free space below threshold" },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: " line one\nline two ",
|
||||
ts: "4.000",
|
||||
},
|
||||
],
|
||||
response_metadata: { next_cursor: "" },
|
||||
});
|
||||
const client = {
|
||||
conversations: { replies },
|
||||
} as unknown as Parameters<typeof resolveSlackThreadHistory>[0]["client"];
|
||||
|
||||
const result = await resolveSlackThreadHistory({
|
||||
channelId: "C1",
|
||||
threadTs: "1.000",
|
||||
client,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
expect(result.map((entry) => entry.text)).toEqual([
|
||||
"Filesystem on /dev/sda1 has only 14.93% available space left.\nAlert: filesystem space is low\nHost\ndc2.ipa.mgt",
|
||||
"Pod restart rate is high",
|
||||
"Alert firing\n*host:* dc2.ipa.mgt\n*device:* /dev/sda1\nFree space below threshold",
|
||||
"line one\nline two",
|
||||
]);
|
||||
expect(result.map((entry) => entry.botId)).toEqual([
|
||||
"BMONITOR",
|
||||
"BMONITOR",
|
||||
"BMONITOR",
|
||||
undefined,
|
||||
]);
|
||||
});
|
||||
|
||||
it("returns empty when limit is zero without calling Slack API", async () => {
|
||||
const replies = vi.fn();
|
||||
const client = {
|
||||
|
|
@ -1106,6 +1182,43 @@ describe("resolveSlackThreadStarter", () => {
|
|||
expect(vi.mocked(logVerbose)).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns the starter text from Slack attachments when bot message text is empty", async () => {
|
||||
const replies = vi.fn().mockResolvedValueOnce({
|
||||
messages: [
|
||||
{
|
||||
text: " ",
|
||||
bot_id: "BMONITOR",
|
||||
ts: "1.000",
|
||||
attachments: [
|
||||
{
|
||||
pretext: "[FIRING:1] HostFilesystemSpaceLow",
|
||||
title: "Filesystem on /dev/sda1 has only 14.93% available space left.",
|
||||
fallback: "dc2.ipa.mgt /dev/sda1 low free space",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const client = {
|
||||
conversations: { replies },
|
||||
} as unknown as Parameters<typeof resolveSlackThreadStarter>[0]["client"];
|
||||
|
||||
const result = await resolveSlackThreadStarter({
|
||||
channelId: "C1",
|
||||
threadTs: "1.000",
|
||||
client,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
text: "[FIRING:1] HostFilesystemSpaceLow\nFilesystem on /dev/sda1 has only 14.93% available space left.\ndc2.ipa.mgt /dev/sda1 low free space",
|
||||
userId: undefined,
|
||||
botId: "BMONITOR",
|
||||
ts: "1.000",
|
||||
files: undefined,
|
||||
});
|
||||
expect(vi.mocked(logVerbose)).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("returns a placeholder starter when the root message only has files", async () => {
|
||||
const replies = vi.fn().mockResolvedValueOnce({
|
||||
messages: [
|
||||
|
|
|
|||
|
|
@ -3,12 +3,10 @@ import type { WebClient as SlackWebClient } from "@slack/web-api";
|
|||
import { runTasksWithConcurrency } from "openclaw/plugin-sdk/concurrency-runtime";
|
||||
import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import {
|
||||
normalizeOptionalString,
|
||||
readStringValue as readString,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { formatSlackFileReference } from "../../file-reference.js";
|
||||
import type { SlackFile, SlackMessageEvent } from "../../types.js";
|
||||
import { chooseSlackPrimaryText, resolveSlackBlocksText } from "../block-text.js";
|
||||
import { MAX_SLACK_MEDIA_FILES, type SlackMediaResult } from "../media-types.js";
|
||||
import type { SlackThreadStarter } from "../thread.js";
|
||||
|
||||
|
|
@ -21,36 +19,6 @@ const SLACK_MENTION_RESOLUTION_CONCURRENCY = 4;
|
|||
const SLACK_MENTION_RESOLUTION_MAX_LOOKUPS_PER_MESSAGE = 20;
|
||||
const SLACK_USER_MENTION_RE = /<@([A-Z0-9]+)(?:\|[^>]+)?>/gi;
|
||||
|
||||
type SlackTextObject = {
|
||||
text?: unknown;
|
||||
};
|
||||
|
||||
type SlackRichTextElement = {
|
||||
type?: unknown;
|
||||
text?: unknown;
|
||||
url?: unknown;
|
||||
user_id?: unknown;
|
||||
channel_id?: unknown;
|
||||
usergroup_id?: unknown;
|
||||
name?: unknown;
|
||||
range?: unknown;
|
||||
elements?: unknown;
|
||||
};
|
||||
|
||||
type SlackBlockLike = {
|
||||
type?: unknown;
|
||||
text?: unknown;
|
||||
elements?: unknown;
|
||||
fields?: unknown;
|
||||
alt_text?: unknown;
|
||||
title?: unknown;
|
||||
};
|
||||
|
||||
type SlackBlocksText = {
|
||||
text: string;
|
||||
hasRichText: boolean;
|
||||
};
|
||||
|
||||
const loadSlackMediaModule = createLazyRuntimeModule(() => import("../media.js"));
|
||||
|
||||
function collectUniqueSlackMentionIds(texts: Array<string | undefined>): string[] {
|
||||
|
|
@ -87,176 +55,6 @@ function renderSlackUserMentions(
|
|||
});
|
||||
}
|
||||
|
||||
function readTextObject(value: unknown): string | undefined {
|
||||
if (!value || typeof value !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
return normalizeOptionalString(readString((value as SlackTextObject).text));
|
||||
}
|
||||
|
||||
function renderSlackRichTextLeaf(element: SlackRichTextElement): string {
|
||||
switch (element.type) {
|
||||
case "text":
|
||||
return readString(element.text) ?? "";
|
||||
case "link":
|
||||
return readString(element.text) ?? readString(element.url) ?? "";
|
||||
case "user": {
|
||||
const userId = readString(element.user_id);
|
||||
return userId ? `<@${userId}>` : "";
|
||||
}
|
||||
case "channel": {
|
||||
const channelId = readString(element.channel_id);
|
||||
return channelId ? `<#${channelId}>` : "";
|
||||
}
|
||||
case "usergroup": {
|
||||
const usergroupId = readString(element.usergroup_id);
|
||||
return usergroupId ? `<!subteam^${usergroupId}>` : "";
|
||||
}
|
||||
case "broadcast": {
|
||||
const range = readString(element.range);
|
||||
return range ? `<!${range}>` : "";
|
||||
}
|
||||
case "emoji": {
|
||||
const name = readString(element.name);
|
||||
return name ? `:${name}:` : "";
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function renderSlackRichTextElements(elements: unknown): string {
|
||||
if (!Array.isArray(elements)) {
|
||||
return "";
|
||||
}
|
||||
const parts: string[] = [];
|
||||
for (const rawElement of elements) {
|
||||
if (!rawElement || typeof rawElement !== "object") {
|
||||
continue;
|
||||
}
|
||||
const element = rawElement as SlackRichTextElement;
|
||||
switch (element.type) {
|
||||
case "rich_text_section":
|
||||
case "rich_text_preformatted":
|
||||
case "rich_text_quote": {
|
||||
parts.push(renderSlackRichTextElements(element.elements));
|
||||
break;
|
||||
}
|
||||
case "rich_text_list": {
|
||||
const listParts: string[] = [];
|
||||
if (Array.isArray(element.elements)) {
|
||||
for (const child of element.elements) {
|
||||
if (!child || typeof child !== "object") {
|
||||
continue;
|
||||
}
|
||||
const rendered = renderSlackRichTextElements((child as SlackRichTextElement).elements);
|
||||
if (rendered) {
|
||||
listParts.push(rendered);
|
||||
}
|
||||
}
|
||||
}
|
||||
const listText = listParts.join("\n");
|
||||
parts.push(listText);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
parts.push(renderSlackRichTextLeaf(element));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return parts.join("");
|
||||
}
|
||||
|
||||
function readSlackBlockText(block: unknown): string | undefined {
|
||||
if (!block || typeof block !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const blockLike = block as SlackBlockLike;
|
||||
switch (blockLike.type) {
|
||||
case "rich_text":
|
||||
return normalizeOptionalString(renderSlackRichTextElements(blockLike.elements));
|
||||
case "section": {
|
||||
const text = readTextObject(blockLike.text);
|
||||
if (text) {
|
||||
return text;
|
||||
}
|
||||
if (Array.isArray(blockLike.fields)) {
|
||||
const fields: string[] = [];
|
||||
for (const field of blockLike.fields) {
|
||||
const fieldText = readTextObject(field);
|
||||
if (fieldText) {
|
||||
fields.push(fieldText);
|
||||
}
|
||||
}
|
||||
return fields.length > 0 ? fields.join("\n") : undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
case "header":
|
||||
return readTextObject(blockLike.text);
|
||||
case "context": {
|
||||
if (!Array.isArray(blockLike.elements)) {
|
||||
return undefined;
|
||||
}
|
||||
const parts: string[] = [];
|
||||
for (const element of blockLike.elements) {
|
||||
const text = readTextObject(element);
|
||||
if (text) {
|
||||
parts.push(text);
|
||||
}
|
||||
}
|
||||
return parts.length > 0 ? parts.join(" ") : undefined;
|
||||
}
|
||||
case "image":
|
||||
return (
|
||||
normalizeOptionalString(readString(blockLike.alt_text)) ?? readTextObject(blockLike.title)
|
||||
);
|
||||
case "video":
|
||||
return (
|
||||
readTextObject(blockLike.title) ?? normalizeOptionalString(readString(blockLike.alt_text))
|
||||
);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSlackBlocksText(blocks: unknown[] | undefined): SlackBlocksText | undefined {
|
||||
if (!blocks?.length) {
|
||||
return undefined;
|
||||
}
|
||||
const parts: string[] = [];
|
||||
let hasRichText = false;
|
||||
for (const block of blocks) {
|
||||
if (block && typeof block === "object" && (block as SlackBlockLike).type === "rich_text") {
|
||||
hasRichText = true;
|
||||
}
|
||||
const text = readSlackBlockText(block);
|
||||
if (text) {
|
||||
parts.push(text);
|
||||
}
|
||||
}
|
||||
return parts.length > 0 ? { text: parts.join("\n"), hasRichText } : undefined;
|
||||
}
|
||||
|
||||
function chooseSlackPrimaryText(params: {
|
||||
messageText: string | undefined;
|
||||
blocksText: SlackBlocksText | undefined;
|
||||
}): string | undefined {
|
||||
const { messageText, blocksText } = params;
|
||||
if (!blocksText) {
|
||||
return messageText;
|
||||
}
|
||||
if (!messageText) {
|
||||
return blocksText.text;
|
||||
}
|
||||
if (blocksText.hasRichText && blocksText.text.length > messageText.length) {
|
||||
return blocksText.text;
|
||||
}
|
||||
return blocksText.text.length > messageText.length && blocksText.text.startsWith(messageText)
|
||||
? blocksText.text
|
||||
: messageText;
|
||||
}
|
||||
|
||||
function filterInheritedParentFiles(params: {
|
||||
files: SlackFile[] | undefined;
|
||||
isThreadReply: boolean;
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ import {
|
|||
asDateTimestampMs,
|
||||
resolveExpiresAtMsFromDurationMs,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { formatSlackFileReferenceList } from "../file-reference.js";
|
||||
import type { SlackFile } from "../types.js";
|
||||
import type { SlackAttachment, SlackFile } from "../types.js";
|
||||
import { resolveSlackBlocksText } from "./block-text.js";
|
||||
import { logVerbose } from "./thread.runtime.js";
|
||||
|
||||
export type SlackThreadStarter = {
|
||||
|
|
@ -45,6 +47,52 @@ function formatSlackFilePlaceholder(files: SlackFile[] | undefined): string {
|
|||
return `[attached: ${formatSlackFileReferenceList(files)}]`;
|
||||
}
|
||||
|
||||
function pushUniqueText(parts: string[], value: string | undefined): void {
|
||||
const text = normalizeOptionalString(value);
|
||||
if (text && !parts.includes(text)) {
|
||||
parts.push(text);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSlackBlocksFallbackText(blocks: unknown[] | undefined): string | undefined {
|
||||
return resolveSlackBlocksText(blocks)?.text;
|
||||
}
|
||||
|
||||
function resolveSlackAttachmentFallbackText(
|
||||
attachments: SlackAttachment[] | undefined,
|
||||
): string | undefined {
|
||||
if (!Array.isArray(attachments) || attachments.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const parts: string[] = [];
|
||||
for (const attachment of attachments) {
|
||||
pushUniqueText(parts, attachment.pretext);
|
||||
pushUniqueText(parts, attachment.title);
|
||||
pushUniqueText(parts, attachment.text);
|
||||
pushUniqueText(parts, attachment.fallback);
|
||||
for (const field of attachment.fields ?? []) {
|
||||
pushUniqueText(parts, field.title);
|
||||
pushUniqueText(parts, field.value);
|
||||
}
|
||||
pushUniqueText(parts, resolveSlackBlocksFallbackText(attachment.blocks));
|
||||
pushUniqueText(parts, resolveSlackBlocksFallbackText(attachment.message_blocks));
|
||||
}
|
||||
return parts.length > 0 ? parts.join("\n") : undefined;
|
||||
}
|
||||
|
||||
function resolveSlackMessageText(message: {
|
||||
text?: string;
|
||||
blocks?: unknown[];
|
||||
attachments?: SlackAttachment[];
|
||||
}): string | undefined {
|
||||
return (
|
||||
normalizeOptionalString(message.text) ??
|
||||
resolveSlackAttachmentFallbackText(message.attachments) ??
|
||||
resolveSlackBlocksFallbackText(message.blocks)
|
||||
);
|
||||
}
|
||||
|
||||
export async function resolveSlackThreadStarter(params: {
|
||||
channelId: string;
|
||||
threadTs: string;
|
||||
|
|
@ -73,10 +121,12 @@ export async function resolveSlackThreadStarter(params: {
|
|||
bot_id?: string;
|
||||
ts?: string;
|
||||
files?: SlackFile[];
|
||||
blocks?: unknown[];
|
||||
attachments?: SlackAttachment[];
|
||||
}>;
|
||||
};
|
||||
const message = response?.messages?.[0];
|
||||
const text = (message?.text ?? "").trim();
|
||||
const text = message ? resolveSlackMessageText(message) : undefined;
|
||||
const files = message?.files?.length ? message.files : undefined;
|
||||
if (!message || (!text && !files)) {
|
||||
return null;
|
||||
|
|
@ -126,6 +176,8 @@ type SlackRepliesPageMessage = {
|
|||
bot_id?: string;
|
||||
ts?: string;
|
||||
files?: SlackFile[];
|
||||
blocks?: unknown[];
|
||||
attachments?: SlackAttachment[];
|
||||
};
|
||||
|
||||
type SlackRepliesPage = {
|
||||
|
|
@ -168,8 +220,9 @@ export async function resolveSlackThreadHistory(params: {
|
|||
})) as SlackRepliesPage;
|
||||
|
||||
for (const msg of response.messages ?? []) {
|
||||
// Keep messages with text OR file attachments.
|
||||
if (!msg.text?.trim() && !msg.files?.length) {
|
||||
const text = resolveSlackMessageText(msg);
|
||||
// Keep messages with text, Slack attachment/block fallback text, or file attachments.
|
||||
if (!text && !msg.files?.length) {
|
||||
continue;
|
||||
}
|
||||
if (params.currentMessageTs && msg.ts === params.currentMessageTs) {
|
||||
|
|
@ -187,7 +240,7 @@ export async function resolveSlackThreadHistory(params: {
|
|||
|
||||
return retained.map((msg) => ({
|
||||
// For file-only messages, create a placeholder showing attached filenames.
|
||||
text: msg.text?.trim() ? msg.text : formatSlackFilePlaceholder(msg.files),
|
||||
text: resolveSlackMessageText(msg) ?? formatSlackFilePlaceholder(msg.files),
|
||||
userId: msg.user,
|
||||
botId: msg.bot_id,
|
||||
ts: msg.ts,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export type SlackFile = {
|
|||
|
||||
export type SlackAttachment = {
|
||||
fallback?: string;
|
||||
title?: string;
|
||||
text?: string;
|
||||
pretext?: string;
|
||||
author_name?: string;
|
||||
|
|
@ -26,6 +27,8 @@ export type SlackAttachment = {
|
|||
image_height?: number;
|
||||
thumb_url?: string;
|
||||
files?: SlackFile[];
|
||||
fields?: Array<{ title?: string; value?: string }>;
|
||||
blocks?: unknown[];
|
||||
message_blocks?: unknown[];
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue