"use strict";Object.defineProperty(exports, "__esModule", { value: true });exports.a = resolveSlackWebClientOptions;exports.c = validateSlackBlocksArray;exports.i = createSlackWebClient;exports.n = markdownToSlackMrkdwnChunks;exports.o = buildSlackBlocksFallbackText;exports.r = normalizeSlackOutboundText;exports.s = parseSlackBlocksInput;exports.t = sendMessageSlack;var _configDiiPndBn = require("./config-DiiPndBn.js"); var _loggerU3s76KST = require("./logger-U3s76KST.js"); var _pluginsBhm3N6Y = require("./plugins-Bhm3N6Y-.js"); var _fetchGuardBMQY_BjF = require("./fetch-guard-BMQY_BjF.js"); var _irKp5uANes = require("./ir-Kp5uANes.js"); var _renderC15_5JiR = require("./render-C15_5JiR.js"); var _tokensCgeKcoW = require("./tokens-CgeKcoW1.js"); var _webApi = require("@slack/web-api"); //#region src/slack/blocks-input.ts const SLACK_MAX_BLOCKS = 50; function parseBlocksJson(raw) { try { return JSON.parse(raw); } catch { throw new Error("blocks must be valid JSON"); } } function assertBlocksArray(raw) { if (!Array.isArray(raw)) throw new Error("blocks must be an array"); if (raw.length === 0) throw new Error("blocks must contain at least one block"); if (raw.length > SLACK_MAX_BLOCKS) throw new Error(`blocks cannot exceed ${SLACK_MAX_BLOCKS} items`); for (const block of raw) { if (!block || typeof block !== "object" || Array.isArray(block)) throw new Error("each block must be an object"); const type = block.type; if (typeof type !== "string" || type.trim().length === 0) throw new Error("each block must include a non-empty string type"); } } function validateSlackBlocksArray(raw) { assertBlocksArray(raw); return raw; } function parseSlackBlocksInput(raw) { if (raw == null) return; return validateSlackBlocksArray(typeof raw === "string" ? parseBlocksJson(raw) : raw); } //#endregion //#region src/slack/blocks-fallback.ts function cleanCandidate(value) { if (typeof value !== "string") return; const normalized = value.replace(/\s+/g, " ").trim(); return normalized.length > 0 ? normalized : void 0; } function readSectionText(block) { return cleanCandidate(block.text?.text); } function readHeaderText(block) { return cleanCandidate(block.text?.text); } function readImageText(block) { return cleanCandidate(block.alt_text) ?? cleanCandidate(block.title?.text); } function readVideoText(block) { return cleanCandidate(block.title?.text) ?? cleanCandidate(block.alt_text); } function readContextText(block) { if (!Array.isArray(block.elements)) return; const textParts = block.elements.map((element) => cleanCandidate(element.text)).filter((value) => Boolean(value)); return textParts.length > 0 ? textParts.join(" ") : void 0; } function buildSlackBlocksFallbackText(blocks) { for (const raw of blocks) { const block = raw; switch (block.type) { case "header":{ const text = readHeaderText(block); if (text) return text; break; } case "section":{ const text = readSectionText(block); if (text) return text; break; } case "image":{ const text = readImageText(block); if (text) return text; return "Shared an image"; } case "video":{ const text = readVideoText(block); if (text) return text; return "Shared a video"; } case "file":return "Shared a file"; case "context":{ const text = readContextText(block); if (text) return text; break; } default:break; } } return "Shared a Block Kit message"; } //#endregion //#region src/slack/client.ts const SLACK_DEFAULT_RETRY_OPTIONS = { retries: 2, factor: 2, minTimeout: 500, maxTimeout: 3e3, randomize: true }; function resolveSlackWebClientOptions(options = {}) { return { ...options, retryConfig: options.retryConfig ?? SLACK_DEFAULT_RETRY_OPTIONS }; } function createSlackWebClient(token, options = {}) { return new _webApi.WebClient(token, resolveSlackWebClientOptions(options)); } //#endregion //#region src/slack/format.ts function escapeSlackMrkdwnSegment(text) { return text.replace(/&/g, "&").replace(//g, ">"); } const SLACK_ANGLE_TOKEN_RE = /<[^>\n]+>/g; function isAllowedSlackAngleToken(token) { if (!token.startsWith("<") || !token.endsWith(">")) return false; const inner = token.slice(1, -1); return inner.startsWith("@") || inner.startsWith("#") || inner.startsWith("!") || inner.startsWith("mailto:") || inner.startsWith("tel:") || inner.startsWith("http://") || inner.startsWith("https://") || inner.startsWith("slack://"); } function escapeSlackMrkdwnContent(text) { if (!text) return ""; if (!text.includes("&") && !text.includes("<") && !text.includes(">")) return text; SLACK_ANGLE_TOKEN_RE.lastIndex = 0; const out = []; let lastIndex = 0; for (let match = SLACK_ANGLE_TOKEN_RE.exec(text); match; match = SLACK_ANGLE_TOKEN_RE.exec(text)) { const matchIndex = match.index ?? 0; out.push(escapeSlackMrkdwnSegment(text.slice(lastIndex, matchIndex))); const token = match[0] ?? ""; out.push(isAllowedSlackAngleToken(token) ? token : escapeSlackMrkdwnSegment(token)); lastIndex = matchIndex + token.length; } out.push(escapeSlackMrkdwnSegment(text.slice(lastIndex))); return out.join(""); } function escapeSlackMrkdwnText(text) { if (!text) return ""; if (!text.includes("&") && !text.includes("<") && !text.includes(">")) return text; return text.split("\n").map((line) => { if (line.startsWith("> ")) return `> ${escapeSlackMrkdwnContent(line.slice(2))}`; return escapeSlackMrkdwnContent(line); }).join("\n"); } function buildSlackLink(link, text) { const href = link.href.trim(); if (!href) return null; const trimmedLabel = text.slice(link.start, link.end).trim(); const comparableHref = href.startsWith("mailto:") ? href.slice(7) : href; if (!(trimmedLabel.length > 0 && trimmedLabel !== href && trimmedLabel !== comparableHref)) return null; const safeHref = escapeSlackMrkdwnSegment(href); return { start: link.start, end: link.end, open: `<${safeHref}|`, close: ">" }; } function buildSlackRenderOptions() { return { styleMarkers: { bold: { open: "*", close: "*" }, italic: { open: "_", close: "_" }, strikethrough: { open: "~", close: "~" }, code: { open: "`", close: "`" }, code_block: { open: "```\n", close: "```" } }, escapeText: escapeSlackMrkdwnText, buildLink: buildSlackLink }; } function markdownToSlackMrkdwn(markdown, options = {}) { return (0, _renderC15_5JiR.t)((0, _irKp5uANes.n)(markdown ?? "", { linkify: false, autolink: false, headingStyle: "bold", blockquotePrefix: "> ", tableMode: options.tableMode }), buildSlackRenderOptions()); } function normalizeSlackOutboundText(markdown) { return markdownToSlackMrkdwn(markdown ?? ""); } function markdownToSlackMrkdwnChunks(markdown, limit, options = {}) { const chunks = (0, _irKp5uANes.t)((0, _irKp5uANes.n)(markdown ?? "", { linkify: false, autolink: false, headingStyle: "bold", blockquotePrefix: "> ", tableMode: options.tableMode }), limit); const renderOptions = buildSlackRenderOptions(); return chunks.map((chunk) => (0, _renderC15_5JiR.t)(chunk, renderOptions)); } //#endregion //#region src/slack/send.ts const SLACK_TEXT_LIMIT = 4e3; const SLACK_UPLOAD_SSRF_POLICY = { allowedHostnames: [ "*.slack.com", "*.slack-edge.com", "*.slack-files.com"], allowRfc2544BenchmarkRange: true }; function hasCustomIdentity(identity) { return Boolean(identity?.username || identity?.iconUrl || identity?.iconEmoji); } function isSlackCustomizeScopeError(err) { if (!(err instanceof Error)) return false; const maybeData = err; if (maybeData.data?.error?.toLowerCase() !== "missing_scope") return false; if (maybeData.data?.needed?.toLowerCase()?.includes("chat:write.customize")) return true; return [...(maybeData.data?.response_metadata?.scopes ?? []), ...(maybeData.data?.response_metadata?.acceptedScopes ?? [])].map((scope) => scope.toLowerCase()).includes("chat:write.customize"); } async function postSlackMessageBestEffort(params) { const basePayload = { channel: params.channelId, text: params.text, thread_ts: params.threadTs, ...(params.blocks?.length ? { blocks: params.blocks } : {}) }; try { if (params.identity?.iconUrl) return await params.client.chat.postMessage({ ...basePayload, ...(params.identity.username ? { username: params.identity.username } : {}), icon_url: params.identity.iconUrl }); if (params.identity?.iconEmoji) return await params.client.chat.postMessage({ ...basePayload, ...(params.identity.username ? { username: params.identity.username } : {}), icon_emoji: params.identity.iconEmoji }); return await params.client.chat.postMessage({ ...basePayload, ...(params.identity?.username ? { username: params.identity.username } : {}) }); } catch (err) { if (!hasCustomIdentity(params.identity) || !isSlackCustomizeScopeError(err)) throw err; (0, _loggerU3s76KST.R)("slack send: missing chat:write.customize, retrying without custom identity"); return params.client.chat.postMessage(basePayload); } } function resolveToken(params) { const explicit = (0, _pluginsBhm3N6Y.z)(params.explicit); if (explicit) return explicit; const fallback = (0, _pluginsBhm3N6Y.z)(params.fallbackToken); if (!fallback) { (0, _loggerU3s76KST.R)(`slack send: missing bot token for account=${params.accountId} explicit=${Boolean(params.explicit)} source=${params.fallbackSource ?? "unknown"}`); throw new Error(`Slack bot token missing for account "${params.accountId}" (set channels.slack.accounts.${params.accountId}.botToken or SLACK_BOT_TOKEN for default).`); } return fallback; } function parseRecipient(raw) { const target = (0, _pluginsBhm3N6Y.m)(raw); if (!target) throw new Error("Recipient is required for Slack sends"); return { kind: target.kind, id: target.id }; } async function resolveChannelId(client, recipient) { if (!(recipient.kind === "user" || /^U[A-Z0-9]+$/i.test(recipient.id))) return { channelId: recipient.id }; const channelId = (await client.conversations.open({ users: recipient.id })).channel?.id; if (!channelId) throw new Error("Failed to open Slack DM channel"); return { channelId, isDm: true }; } async function uploadSlackFile(params) { const { buffer, contentType, fileName } = await (0, _irKp5uANes.v)(params.mediaUrl, { maxBytes: params.maxBytes, localRoots: params.mediaLocalRoots }); const uploadUrlResp = await params.client.files.getUploadURLExternal({ filename: fileName ?? "upload", length: buffer.length }); if (!uploadUrlResp.ok || !uploadUrlResp.upload_url || !uploadUrlResp.file_id) throw new Error(`Failed to get upload URL: ${uploadUrlResp.error ?? "unknown error"}`); const uploadBody = new Uint8Array(buffer); const { response: uploadResp, release } = await (0, _fetchGuardBMQY_BjF.t)((0, _fetchGuardBMQY_BjF.r)({ url: uploadUrlResp.upload_url, init: { method: "POST", ...(contentType ? { headers: { "Content-Type": contentType } } : {}), body: uploadBody }, policy: SLACK_UPLOAD_SSRF_POLICY, auditContext: "slack-upload-file" })); try { if (!uploadResp.ok) throw new Error(`Failed to upload file: HTTP ${uploadResp.status}`); } finally { await release(); } const completeResp = await params.client.files.completeUploadExternal({ files: [{ id: uploadUrlResp.file_id, title: fileName ?? "upload" }], channel_id: params.channelId, ...(params.caption ? { initial_comment: params.caption } : {}), ...(params.threadTs ? { thread_ts: params.threadTs } : {}) }); if (!completeResp.ok) throw new Error(`Failed to complete upload: ${completeResp.error ?? "unknown error"}`); return uploadUrlResp.file_id; } async function sendMessageSlack(to, message, opts = {}) { const trimmedMessage = message?.trim() ?? ""; if ((0, _tokensCgeKcoW.i)(trimmedMessage) && !opts.mediaUrl && !opts.blocks) { (0, _loggerU3s76KST.R)("slack send: suppressed NO_REPLY token before API call"); return { messageId: "suppressed", channelId: "" }; } const blocks = opts.blocks == null ? void 0 : validateSlackBlocksArray(opts.blocks); if (!trimmedMessage && !opts.mediaUrl && !blocks) throw new Error("Slack send requires text, blocks, or media"); const cfg = opts.cfg ?? (0, _configDiiPndBn.i)(); const account = (0, _pluginsBhm3N6Y.I)({ cfg, accountId: opts.accountId }); const token = resolveToken({ explicit: opts.token, accountId: account.accountId, fallbackToken: account.botToken, fallbackSource: account.botTokenSource }); const client = opts.client ?? createSlackWebClient(token); const { channelId } = await resolveChannelId(client, parseRecipient(to)); if (blocks) { if (opts.mediaUrl) throw new Error("Slack send does not support blocks with mediaUrl"); return { messageId: (await postSlackMessageBestEffort({ client, channelId, text: trimmedMessage || buildSlackBlocksFallbackText(blocks), threadTs: opts.threadTs, identity: opts.identity, blocks })).ts ?? "unknown", channelId }; } const textLimit = (0, _irKp5uANes.f)(cfg, "slack", account.accountId); const chunkLimit = Math.min(textLimit, SLACK_TEXT_LIMIT); const tableMode = (0, _irKp5uANes.i)({ cfg, channel: "slack", accountId: account.accountId }); const chunkMode = (0, _irKp5uANes.d)(cfg, "slack", account.accountId); const chunks = (chunkMode === "newline" ? (0, _irKp5uANes.c)(trimmedMessage, chunkLimit, chunkMode) : [trimmedMessage]).flatMap((markdown) => markdownToSlackMrkdwnChunks(markdown, chunkLimit, { tableMode })); if (!chunks.length && trimmedMessage) chunks.push(trimmedMessage); const mediaMaxBytes = typeof account.config.mediaMaxMb === "number" ? account.config.mediaMaxMb * 1024 * 1024 : void 0; let lastMessageId = ""; if (opts.mediaUrl) { const [firstChunk, ...rest] = chunks; lastMessageId = await uploadSlackFile({ client, channelId, mediaUrl: opts.mediaUrl, mediaLocalRoots: opts.mediaLocalRoots, caption: firstChunk, threadTs: opts.threadTs, maxBytes: mediaMaxBytes }); for (const chunk of rest) lastMessageId = (await postSlackMessageBestEffort({ client, channelId, text: chunk, threadTs: opts.threadTs, identity: opts.identity })).ts ?? lastMessageId; } else for (const chunk of chunks.length ? chunks : [""]) lastMessageId = (await postSlackMessageBestEffort({ client, channelId, text: chunk, threadTs: opts.threadTs, identity: opts.identity })).ts ?? lastMessageId; return { messageId: lastMessageId || "unknown", channelId }; } //#endregion /* v9-c6da9936b40c951b */