@chat-adapter/slack 4.17.0 → 4.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +323 -9
- package/dist/index.d.ts +15 -1
- package/dist/index.js +405 -74
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
3
3
|
import { createHmac, timingSafeEqual } from "crypto";
|
|
4
4
|
import {
|
|
5
5
|
AdapterRateLimitError,
|
|
6
|
+
AuthenticationError,
|
|
6
7
|
extractCard,
|
|
7
8
|
extractFiles,
|
|
8
9
|
NetworkError,
|
|
@@ -11,12 +12,12 @@ import {
|
|
|
11
12
|
} from "@chat-adapter/shared";
|
|
12
13
|
import { WebClient } from "@slack/web-api";
|
|
13
14
|
import {
|
|
14
|
-
ChatError,
|
|
15
15
|
ConsoleLogger,
|
|
16
16
|
convertEmojiPlaceholders,
|
|
17
17
|
defaultEmojiResolver,
|
|
18
18
|
isJSX,
|
|
19
19
|
Message,
|
|
20
|
+
parseMarkdown as parseMarkdown2,
|
|
20
21
|
StreamingMarkdownRenderer,
|
|
21
22
|
toModalElement
|
|
22
23
|
} from "chat";
|
|
@@ -59,13 +60,14 @@ function cardToBlockKit(card) {
|
|
|
59
60
|
alt_text: card.title || "Card image"
|
|
60
61
|
});
|
|
61
62
|
}
|
|
63
|
+
const state = { usedNativeTable: false };
|
|
62
64
|
for (const child of card.children) {
|
|
63
|
-
const childBlocks = convertChildToBlocks(child);
|
|
65
|
+
const childBlocks = convertChildToBlocks(child, state);
|
|
64
66
|
blocks.push(...childBlocks);
|
|
65
67
|
}
|
|
66
68
|
return blocks;
|
|
67
69
|
}
|
|
68
|
-
function convertChildToBlocks(child) {
|
|
70
|
+
function convertChildToBlocks(child, state) {
|
|
69
71
|
switch (child.type) {
|
|
70
72
|
case "text":
|
|
71
73
|
return [convertTextToBlock(child)];
|
|
@@ -76,13 +78,13 @@ function convertChildToBlocks(child) {
|
|
|
76
78
|
case "actions":
|
|
77
79
|
return [convertActionsToBlock(child)];
|
|
78
80
|
case "section":
|
|
79
|
-
return convertSectionToBlocks(child);
|
|
81
|
+
return convertSectionToBlocks(child, state);
|
|
80
82
|
case "fields":
|
|
81
83
|
return [convertFieldsToBlock(child)];
|
|
82
84
|
case "link":
|
|
83
85
|
return [convertLinkToBlock(child)];
|
|
84
86
|
case "table":
|
|
85
|
-
return convertTableToBlocks(child);
|
|
87
|
+
return convertTableToBlocks(child, state);
|
|
86
88
|
default: {
|
|
87
89
|
const text = cardChildToFallbackText(child);
|
|
88
90
|
if (text) {
|
|
@@ -250,10 +252,10 @@ function convertRadioSelectToElement(radioSelect) {
|
|
|
250
252
|
}
|
|
251
253
|
return element;
|
|
252
254
|
}
|
|
253
|
-
function convertTableToBlocks(element) {
|
|
255
|
+
function convertTableToBlocks(element, state) {
|
|
254
256
|
const MAX_ROWS = 100;
|
|
255
257
|
const MAX_COLS = 20;
|
|
256
|
-
if (element.rows.length > MAX_ROWS || element.headers.length > MAX_COLS) {
|
|
258
|
+
if (state.usedNativeTable || element.rows.length > MAX_ROWS || element.headers.length > MAX_COLS) {
|
|
257
259
|
return [
|
|
258
260
|
{
|
|
259
261
|
type: "section",
|
|
@@ -266,31 +268,28 @@ ${tableElementToAscii(element.headers, element.rows)}
|
|
|
266
268
|
}
|
|
267
269
|
];
|
|
268
270
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
text: convertEmoji(header)
|
|
274
|
-
}
|
|
271
|
+
state.usedNativeTable = true;
|
|
272
|
+
const headerRow = element.headers.map((header) => ({
|
|
273
|
+
type: "raw_text",
|
|
274
|
+
text: convertEmoji(header)
|
|
275
275
|
}));
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
type: "
|
|
276
|
+
const dataRows = element.rows.map(
|
|
277
|
+
(row) => row.map((cell) => ({
|
|
278
|
+
type: "raw_text",
|
|
279
279
|
text: convertEmoji(cell)
|
|
280
280
|
}))
|
|
281
|
-
|
|
281
|
+
);
|
|
282
282
|
return [
|
|
283
283
|
{
|
|
284
284
|
type: "table",
|
|
285
|
-
|
|
286
|
-
rows
|
|
285
|
+
rows: [headerRow, ...dataRows]
|
|
287
286
|
}
|
|
288
287
|
];
|
|
289
288
|
}
|
|
290
|
-
function convertSectionToBlocks(element) {
|
|
289
|
+
function convertSectionToBlocks(element, state) {
|
|
291
290
|
const blocks = [];
|
|
292
291
|
for (const child of element.children) {
|
|
293
|
-
blocks.push(...convertChildToBlocks(child));
|
|
292
|
+
blocks.push(...convertChildToBlocks(child, state));
|
|
294
293
|
}
|
|
295
294
|
return blocks;
|
|
296
295
|
}
|
|
@@ -428,16 +427,68 @@ var SlackFormatConverter = class extends BaseFormatConverter {
|
|
|
428
427
|
*/
|
|
429
428
|
toAst(mrkdwn) {
|
|
430
429
|
let markdown = mrkdwn;
|
|
431
|
-
markdown = markdown.replace(/<@([
|
|
432
|
-
markdown = markdown.replace(/<@([
|
|
433
|
-
markdown = markdown.replace(/<#[
|
|
434
|
-
markdown = markdown.replace(/<#([
|
|
430
|
+
markdown = markdown.replace(/<@([A-Z0-9_]+)\|([^>]+)>/g, "@$2");
|
|
431
|
+
markdown = markdown.replace(/<@([A-Z0-9_]+)>/g, "@$1");
|
|
432
|
+
markdown = markdown.replace(/<#[A-Z0-9_]+\|([^>]+)>/g, "#$1");
|
|
433
|
+
markdown = markdown.replace(/<#([A-Z0-9_]+)>/g, "#$1");
|
|
435
434
|
markdown = markdown.replace(/<(https?:\/\/[^|>]+)\|([^>]+)>/g, "[$2]($1)");
|
|
436
435
|
markdown = markdown.replace(/<(https?:\/\/[^>]+)>/g, "$1");
|
|
437
436
|
markdown = markdown.replace(/(?<![_*\\])\*([^*\n]+)\*(?![_*])/g, "**$1**");
|
|
438
437
|
markdown = markdown.replace(/(?<!~)~([^~\n]+)~(?!~)/g, "~~$1~~");
|
|
439
438
|
return parseMarkdown(markdown);
|
|
440
439
|
}
|
|
440
|
+
/**
|
|
441
|
+
* Convert AST to Slack blocks, using a native table block for the first table.
|
|
442
|
+
* Returns null if the AST contains no tables (caller should use regular text).
|
|
443
|
+
* Slack allows at most one table block per message; additional tables use ASCII.
|
|
444
|
+
*/
|
|
445
|
+
toBlocksWithTable(ast) {
|
|
446
|
+
const hasTable = ast.children.some((node) => isTableNode(node));
|
|
447
|
+
if (!hasTable) {
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
const blocks = [];
|
|
451
|
+
let usedNativeTable = false;
|
|
452
|
+
let textBuffer = [];
|
|
453
|
+
const flushText = () => {
|
|
454
|
+
if (textBuffer.length > 0) {
|
|
455
|
+
const text = textBuffer.join("\n\n");
|
|
456
|
+
if (text.trim()) {
|
|
457
|
+
blocks.push({
|
|
458
|
+
type: "section",
|
|
459
|
+
text: { type: "mrkdwn", text }
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
textBuffer = [];
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
for (const child of ast.children) {
|
|
466
|
+
const node = child;
|
|
467
|
+
if (isTableNode(node)) {
|
|
468
|
+
flushText();
|
|
469
|
+
if (usedNativeTable) {
|
|
470
|
+
blocks.push({
|
|
471
|
+
type: "section",
|
|
472
|
+
text: {
|
|
473
|
+
type: "mrkdwn",
|
|
474
|
+
text: `\`\`\`
|
|
475
|
+
${tableToAscii(node)}
|
|
476
|
+
\`\`\``
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
} else {
|
|
480
|
+
blocks.push(
|
|
481
|
+
mdastTableToSlackBlock(node, this.nodeToMrkdwn.bind(this))
|
|
482
|
+
);
|
|
483
|
+
usedNativeTable = true;
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
textBuffer.push(this.nodeToMrkdwn(node));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
flushText();
|
|
490
|
+
return blocks;
|
|
491
|
+
}
|
|
441
492
|
nodeToMrkdwn(node) {
|
|
442
493
|
if (isParagraphNode(node)) {
|
|
443
494
|
return getNodeChildren(node).map((child) => this.nodeToMrkdwn(child)).join("");
|
|
@@ -489,6 +540,26 @@ ${tableToAscii(node)}
|
|
|
489
540
|
return this.defaultNodeToText(node, (child) => this.nodeToMrkdwn(child));
|
|
490
541
|
}
|
|
491
542
|
};
|
|
543
|
+
function mdastTableToSlackBlock(node, cellConverter) {
|
|
544
|
+
const rows = [];
|
|
545
|
+
for (const row of node.children) {
|
|
546
|
+
const cells = getNodeChildren(row).map((cell) => ({
|
|
547
|
+
type: "raw_text",
|
|
548
|
+
text: getNodeChildren(cell).map(cellConverter).join("")
|
|
549
|
+
}));
|
|
550
|
+
rows.push(cells);
|
|
551
|
+
}
|
|
552
|
+
const block = { type: "table", rows };
|
|
553
|
+
if (node.align) {
|
|
554
|
+
const columnSettings = node.align.map(
|
|
555
|
+
(a) => ({
|
|
556
|
+
align: a || "left"
|
|
557
|
+
})
|
|
558
|
+
);
|
|
559
|
+
block.column_settings = columnSettings;
|
|
560
|
+
}
|
|
561
|
+
return block;
|
|
562
|
+
}
|
|
492
563
|
|
|
493
564
|
// src/modals.ts
|
|
494
565
|
function encodeModalMetadata(meta) {
|
|
@@ -636,6 +707,7 @@ function radioSelectToBlock(radioSelect) {
|
|
|
636
707
|
}
|
|
637
708
|
|
|
638
709
|
// src/index.ts
|
|
710
|
+
var SLACK_USER_ID_PATTERN = /^[A-Z0-9_]+$/;
|
|
639
711
|
var SlackAdapter = class _SlackAdapter {
|
|
640
712
|
name = "slack";
|
|
641
713
|
userName;
|
|
@@ -700,9 +772,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
700
772
|
if (this.defaultBotToken) {
|
|
701
773
|
return this.defaultBotToken;
|
|
702
774
|
}
|
|
703
|
-
throw new
|
|
704
|
-
"
|
|
705
|
-
"
|
|
775
|
+
throw new AuthenticationError(
|
|
776
|
+
"slack",
|
|
777
|
+
"No bot token available. In multi-workspace mode, ensure the webhook is being processed."
|
|
706
778
|
);
|
|
707
779
|
}
|
|
708
780
|
/**
|
|
@@ -747,9 +819,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
747
819
|
*/
|
|
748
820
|
async setInstallation(teamId, installation) {
|
|
749
821
|
if (!this.chat) {
|
|
750
|
-
throw new
|
|
751
|
-
"
|
|
752
|
-
"
|
|
822
|
+
throw new ValidationError(
|
|
823
|
+
"slack",
|
|
824
|
+
"Adapter not initialized. Ensure chat.initialize() has been called first."
|
|
753
825
|
);
|
|
754
826
|
}
|
|
755
827
|
const state = this.chat.getState();
|
|
@@ -769,9 +841,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
769
841
|
*/
|
|
770
842
|
async getInstallation(teamId) {
|
|
771
843
|
if (!this.chat) {
|
|
772
|
-
throw new
|
|
773
|
-
"
|
|
774
|
-
"
|
|
844
|
+
throw new ValidationError(
|
|
845
|
+
"slack",
|
|
846
|
+
"Adapter not initialized. Ensure chat.initialize() has been called first."
|
|
775
847
|
);
|
|
776
848
|
}
|
|
777
849
|
const state = this.chat.getState();
|
|
@@ -798,17 +870,17 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
798
870
|
*/
|
|
799
871
|
async handleOAuthCallback(request) {
|
|
800
872
|
if (!(this.clientId && this.clientSecret)) {
|
|
801
|
-
throw new
|
|
802
|
-
"
|
|
803
|
-
"
|
|
873
|
+
throw new ValidationError(
|
|
874
|
+
"slack",
|
|
875
|
+
"clientId and clientSecret are required for OAuth. Pass them in createSlackAdapter()."
|
|
804
876
|
);
|
|
805
877
|
}
|
|
806
878
|
const url = new URL(request.url);
|
|
807
879
|
const code = url.searchParams.get("code");
|
|
808
880
|
if (!code) {
|
|
809
|
-
throw new
|
|
810
|
-
"
|
|
811
|
-
"
|
|
881
|
+
throw new ValidationError(
|
|
882
|
+
"slack",
|
|
883
|
+
"Missing 'code' query parameter in OAuth callback request."
|
|
812
884
|
);
|
|
813
885
|
}
|
|
814
886
|
const redirectUri = url.searchParams.get("redirect_uri") ?? void 0;
|
|
@@ -819,9 +891,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
819
891
|
redirect_uri: redirectUri
|
|
820
892
|
});
|
|
821
893
|
if (!(result.ok && result.access_token && result.team?.id)) {
|
|
822
|
-
throw new
|
|
823
|
-
|
|
824
|
-
"
|
|
894
|
+
throw new AuthenticationError(
|
|
895
|
+
"slack",
|
|
896
|
+
`Slack OAuth failed: ${result.error || "missing access_token or team.id"}`
|
|
825
897
|
);
|
|
826
898
|
}
|
|
827
899
|
const teamId = result.team.id;
|
|
@@ -838,9 +910,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
838
910
|
*/
|
|
839
911
|
async deleteInstallation(teamId) {
|
|
840
912
|
if (!this.chat) {
|
|
841
|
-
throw new
|
|
842
|
-
"
|
|
843
|
-
"
|
|
913
|
+
throw new ValidationError(
|
|
914
|
+
"slack",
|
|
915
|
+
"Adapter not initialized. Ensure chat.initialize() has been called first."
|
|
844
916
|
);
|
|
845
917
|
}
|
|
846
918
|
const state = this.chat.getState();
|
|
@@ -1257,7 +1329,10 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1257
1329
|
if (isJSX(modal)) {
|
|
1258
1330
|
const converted = toModalElement(modal);
|
|
1259
1331
|
if (!converted) {
|
|
1260
|
-
throw new
|
|
1332
|
+
throw new ValidationError(
|
|
1333
|
+
"slack",
|
|
1334
|
+
"Invalid JSX element: must be a Modal element"
|
|
1335
|
+
);
|
|
1261
1336
|
}
|
|
1262
1337
|
return converted;
|
|
1263
1338
|
}
|
|
@@ -1344,7 +1419,7 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1344
1419
|
/**
|
|
1345
1420
|
* Handle reaction events from Slack (reaction_added, reaction_removed).
|
|
1346
1421
|
*/
|
|
1347
|
-
handleReactionEvent(event, options) {
|
|
1422
|
+
async handleReactionEvent(event, options) {
|
|
1348
1423
|
if (!this.chat) {
|
|
1349
1424
|
this.logger.warn("Chat instance not initialized, ignoring reaction");
|
|
1350
1425
|
return;
|
|
@@ -1355,9 +1430,32 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1355
1430
|
});
|
|
1356
1431
|
return;
|
|
1357
1432
|
}
|
|
1433
|
+
let parentTs = event.item.ts;
|
|
1434
|
+
try {
|
|
1435
|
+
const result = await this.client.conversations.replies(
|
|
1436
|
+
this.withToken({
|
|
1437
|
+
channel: event.item.channel,
|
|
1438
|
+
ts: event.item.ts,
|
|
1439
|
+
limit: 1
|
|
1440
|
+
})
|
|
1441
|
+
);
|
|
1442
|
+
const firstMessage = result.messages?.[0];
|
|
1443
|
+
if (firstMessage?.thread_ts) {
|
|
1444
|
+
parentTs = firstMessage.thread_ts;
|
|
1445
|
+
}
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
this.logger.warn(
|
|
1448
|
+
"Failed to resolve parent thread for reaction, using message ts",
|
|
1449
|
+
{
|
|
1450
|
+
error: String(error),
|
|
1451
|
+
channel: event.item.channel,
|
|
1452
|
+
ts: event.item.ts
|
|
1453
|
+
}
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1358
1456
|
const threadId = this.encodeThreadId({
|
|
1359
1457
|
channel: event.item.channel,
|
|
1360
|
-
threadTs:
|
|
1458
|
+
threadTs: parentTs
|
|
1361
1459
|
});
|
|
1362
1460
|
const messageId = event.item.ts;
|
|
1363
1461
|
const rawEmoji = event.reaction;
|
|
@@ -1569,12 +1667,22 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1569
1667
|
* detection doesn't apply.
|
|
1570
1668
|
*/
|
|
1571
1669
|
async resolveInlineMentions(text, skipSelfMention) {
|
|
1572
|
-
const mentionPattern = /<@([A-Z0-9]+)(?:\|[^>]*)?>/g;
|
|
1573
1670
|
const userIds = /* @__PURE__ */ new Set();
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1671
|
+
for (const segment of text.split("<")) {
|
|
1672
|
+
const end = segment.indexOf(">");
|
|
1673
|
+
if (end === -1) {
|
|
1674
|
+
continue;
|
|
1675
|
+
}
|
|
1676
|
+
const inner = segment.slice(0, end);
|
|
1677
|
+
if (!inner.startsWith("@")) {
|
|
1678
|
+
continue;
|
|
1679
|
+
}
|
|
1680
|
+
const rest = inner.slice(1);
|
|
1681
|
+
const pipeIdx = rest.indexOf("|");
|
|
1682
|
+
const uid = pipeIdx >= 0 ? rest.slice(0, pipeIdx) : rest;
|
|
1683
|
+
if (SLACK_USER_ID_PATTERN.test(uid)) {
|
|
1684
|
+
userIds.add(uid);
|
|
1685
|
+
}
|
|
1578
1686
|
}
|
|
1579
1687
|
if (userIds.size === 0) {
|
|
1580
1688
|
return text;
|
|
@@ -1592,10 +1700,29 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1592
1700
|
})
|
|
1593
1701
|
);
|
|
1594
1702
|
const nameMap = new Map(lookups);
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1703
|
+
let result = "";
|
|
1704
|
+
let remaining = text;
|
|
1705
|
+
let startIdx = remaining.indexOf("<@");
|
|
1706
|
+
while (startIdx !== -1) {
|
|
1707
|
+
result += remaining.slice(0, startIdx);
|
|
1708
|
+
remaining = remaining.slice(startIdx);
|
|
1709
|
+
const endIdx = remaining.indexOf(">");
|
|
1710
|
+
if (endIdx === -1) {
|
|
1711
|
+
break;
|
|
1712
|
+
}
|
|
1713
|
+
const inner = remaining.slice(2, endIdx);
|
|
1714
|
+
const pipeIdx = inner.indexOf("|");
|
|
1715
|
+
const uid = pipeIdx >= 0 ? inner.slice(0, pipeIdx) : inner;
|
|
1716
|
+
if (SLACK_USER_ID_PATTERN.test(uid)) {
|
|
1717
|
+
const name = nameMap.get(uid);
|
|
1718
|
+
result += name ? `<@${uid}|${name}>` : `<@${uid}>`;
|
|
1719
|
+
} else {
|
|
1720
|
+
result += remaining.slice(0, endIdx + 1);
|
|
1721
|
+
}
|
|
1722
|
+
remaining = remaining.slice(endIdx + 1);
|
|
1723
|
+
startIdx = remaining.indexOf("<@");
|
|
1724
|
+
}
|
|
1725
|
+
return result + remaining;
|
|
1599
1726
|
}
|
|
1600
1727
|
async parseSlackMessage(event, threadId, options) {
|
|
1601
1728
|
const isMe = this.isMessageFromSelf(event);
|
|
@@ -1672,6 +1799,32 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1672
1799
|
} : void 0
|
|
1673
1800
|
};
|
|
1674
1801
|
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Try to render a message using native Slack table blocks.
|
|
1804
|
+
* Returns blocks + fallback text if the message contains tables, null otherwise.
|
|
1805
|
+
*/
|
|
1806
|
+
renderWithTableBlocks(message) {
|
|
1807
|
+
let ast = null;
|
|
1808
|
+
if (typeof message === "object" && message !== null) {
|
|
1809
|
+
if ("ast" in message) {
|
|
1810
|
+
ast = message.ast;
|
|
1811
|
+
} else if ("markdown" in message) {
|
|
1812
|
+
ast = parseMarkdown2(message.markdown);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
if (!ast) {
|
|
1816
|
+
return null;
|
|
1817
|
+
}
|
|
1818
|
+
const blocks = this.formatConverter.toBlocksWithTable(ast);
|
|
1819
|
+
if (!blocks) {
|
|
1820
|
+
return null;
|
|
1821
|
+
}
|
|
1822
|
+
const fallbackText = convertEmojiPlaceholders(
|
|
1823
|
+
this.formatConverter.renderPostable(message),
|
|
1824
|
+
"slack"
|
|
1825
|
+
);
|
|
1826
|
+
return { text: fallbackText, blocks };
|
|
1827
|
+
}
|
|
1675
1828
|
async postMessage(threadId, message) {
|
|
1676
1829
|
const { channel, threadTs } = this.decodeThreadId(threadId);
|
|
1677
1830
|
try {
|
|
@@ -1718,6 +1871,33 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1718
1871
|
raw: result2
|
|
1719
1872
|
};
|
|
1720
1873
|
}
|
|
1874
|
+
const tableResult = this.renderWithTableBlocks(message);
|
|
1875
|
+
if (tableResult) {
|
|
1876
|
+
this.logger.debug("Slack API: chat.postMessage (table blocks)", {
|
|
1877
|
+
channel,
|
|
1878
|
+
threadTs,
|
|
1879
|
+
blockCount: tableResult.blocks.length
|
|
1880
|
+
});
|
|
1881
|
+
const result2 = await this.client.chat.postMessage(
|
|
1882
|
+
this.withToken({
|
|
1883
|
+
channel,
|
|
1884
|
+
thread_ts: threadTs,
|
|
1885
|
+
text: tableResult.text,
|
|
1886
|
+
blocks: tableResult.blocks,
|
|
1887
|
+
unfurl_links: false,
|
|
1888
|
+
unfurl_media: false
|
|
1889
|
+
})
|
|
1890
|
+
);
|
|
1891
|
+
this.logger.debug("Slack API: chat.postMessage response", {
|
|
1892
|
+
messageId: result2.ts,
|
|
1893
|
+
ok: result2.ok
|
|
1894
|
+
});
|
|
1895
|
+
return {
|
|
1896
|
+
id: result2.ts,
|
|
1897
|
+
threadId,
|
|
1898
|
+
raw: result2
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1721
1901
|
const text = convertEmojiPlaceholders(
|
|
1722
1902
|
this.formatConverter.renderPostable(message),
|
|
1723
1903
|
"slack"
|
|
@@ -1782,6 +1962,34 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1782
1962
|
raw: result2
|
|
1783
1963
|
};
|
|
1784
1964
|
}
|
|
1965
|
+
const tableResult = this.renderWithTableBlocks(message);
|
|
1966
|
+
if (tableResult) {
|
|
1967
|
+
this.logger.debug("Slack API: chat.postEphemeral (table blocks)", {
|
|
1968
|
+
channel,
|
|
1969
|
+
threadTs,
|
|
1970
|
+
userId,
|
|
1971
|
+
blockCount: tableResult.blocks.length
|
|
1972
|
+
});
|
|
1973
|
+
const result2 = await this.client.chat.postEphemeral(
|
|
1974
|
+
this.withToken({
|
|
1975
|
+
channel,
|
|
1976
|
+
thread_ts: threadTs || void 0,
|
|
1977
|
+
user: userId,
|
|
1978
|
+
text: tableResult.text,
|
|
1979
|
+
blocks: tableResult.blocks
|
|
1980
|
+
})
|
|
1981
|
+
);
|
|
1982
|
+
this.logger.debug("Slack API: chat.postEphemeral response", {
|
|
1983
|
+
messageTs: result2.message_ts,
|
|
1984
|
+
ok: result2.ok
|
|
1985
|
+
});
|
|
1986
|
+
return {
|
|
1987
|
+
id: result2.message_ts || "",
|
|
1988
|
+
threadId,
|
|
1989
|
+
usedFallback: false,
|
|
1990
|
+
raw: result2
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1785
1993
|
const text = convertEmojiPlaceholders(
|
|
1786
1994
|
this.formatConverter.renderPostable(message),
|
|
1787
1995
|
"slack"
|
|
@@ -1814,6 +2022,95 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1814
2022
|
this.handleSlackError(error);
|
|
1815
2023
|
}
|
|
1816
2024
|
}
|
|
2025
|
+
async scheduleMessage(threadId, message, options) {
|
|
2026
|
+
const { channel, threadTs } = this.decodeThreadId(threadId);
|
|
2027
|
+
const postAtUnix = Math.floor(options.postAt.getTime() / 1e3);
|
|
2028
|
+
if (postAtUnix <= Math.floor(Date.now() / 1e3)) {
|
|
2029
|
+
throw new ValidationError("slack", "postAt must be in the future");
|
|
2030
|
+
}
|
|
2031
|
+
const files = extractFiles(message);
|
|
2032
|
+
if (files.length > 0) {
|
|
2033
|
+
throw new ValidationError(
|
|
2034
|
+
"slack",
|
|
2035
|
+
"File uploads are not supported in scheduled messages"
|
|
2036
|
+
);
|
|
2037
|
+
}
|
|
2038
|
+
const token = this.getToken();
|
|
2039
|
+
try {
|
|
2040
|
+
const card = extractCard(message);
|
|
2041
|
+
if (card) {
|
|
2042
|
+
const blocks = cardToBlockKit(card);
|
|
2043
|
+
const fallbackText = cardToFallbackText(card);
|
|
2044
|
+
this.logger.debug("Slack API: chat.scheduleMessage (blocks)", {
|
|
2045
|
+
channel,
|
|
2046
|
+
threadTs,
|
|
2047
|
+
postAt: postAtUnix,
|
|
2048
|
+
blockCount: blocks.length
|
|
2049
|
+
});
|
|
2050
|
+
const result2 = await this.client.chat.scheduleMessage({
|
|
2051
|
+
token,
|
|
2052
|
+
channel,
|
|
2053
|
+
thread_ts: threadTs || void 0,
|
|
2054
|
+
post_at: postAtUnix,
|
|
2055
|
+
text: fallbackText,
|
|
2056
|
+
blocks,
|
|
2057
|
+
unfurl_links: false,
|
|
2058
|
+
unfurl_media: false
|
|
2059
|
+
});
|
|
2060
|
+
const scheduledMessageId2 = result2.scheduled_message_id;
|
|
2061
|
+
const adapter2 = this;
|
|
2062
|
+
return {
|
|
2063
|
+
scheduledMessageId: scheduledMessageId2,
|
|
2064
|
+
channelId: channel,
|
|
2065
|
+
postAt: options.postAt,
|
|
2066
|
+
raw: result2,
|
|
2067
|
+
async cancel() {
|
|
2068
|
+
await adapter2.client.chat.deleteScheduledMessage({
|
|
2069
|
+
token,
|
|
2070
|
+
channel,
|
|
2071
|
+
scheduled_message_id: scheduledMessageId2
|
|
2072
|
+
});
|
|
2073
|
+
}
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
const text = convertEmojiPlaceholders(
|
|
2077
|
+
this.formatConverter.renderPostable(message),
|
|
2078
|
+
"slack"
|
|
2079
|
+
);
|
|
2080
|
+
this.logger.debug("Slack API: chat.scheduleMessage", {
|
|
2081
|
+
channel,
|
|
2082
|
+
threadTs,
|
|
2083
|
+
postAt: postAtUnix,
|
|
2084
|
+
textLength: text.length
|
|
2085
|
+
});
|
|
2086
|
+
const result = await this.client.chat.scheduleMessage({
|
|
2087
|
+
token,
|
|
2088
|
+
channel,
|
|
2089
|
+
thread_ts: threadTs || void 0,
|
|
2090
|
+
post_at: postAtUnix,
|
|
2091
|
+
text,
|
|
2092
|
+
unfurl_links: false,
|
|
2093
|
+
unfurl_media: false
|
|
2094
|
+
});
|
|
2095
|
+
const scheduledMessageId = result.scheduled_message_id;
|
|
2096
|
+
const adapter = this;
|
|
2097
|
+
return {
|
|
2098
|
+
scheduledMessageId,
|
|
2099
|
+
channelId: channel,
|
|
2100
|
+
postAt: options.postAt,
|
|
2101
|
+
raw: result,
|
|
2102
|
+
async cancel() {
|
|
2103
|
+
await adapter.client.chat.deleteScheduledMessage({
|
|
2104
|
+
token,
|
|
2105
|
+
channel,
|
|
2106
|
+
scheduled_message_id: scheduledMessageId
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
};
|
|
2110
|
+
} catch (error) {
|
|
2111
|
+
this.handleSlackError(error);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
1817
2114
|
async openModal(triggerId, modal, contextId) {
|
|
1818
2115
|
const metadata = encodeModalMetadata({
|
|
1819
2116
|
contextId,
|
|
@@ -1958,6 +2255,31 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1958
2255
|
raw: result2
|
|
1959
2256
|
};
|
|
1960
2257
|
}
|
|
2258
|
+
const tableResult = this.renderWithTableBlocks(message);
|
|
2259
|
+
if (tableResult) {
|
|
2260
|
+
this.logger.debug("Slack API: chat.update (table blocks)", {
|
|
2261
|
+
channel,
|
|
2262
|
+
messageId,
|
|
2263
|
+
blockCount: tableResult.blocks.length
|
|
2264
|
+
});
|
|
2265
|
+
const result2 = await this.client.chat.update(
|
|
2266
|
+
this.withToken({
|
|
2267
|
+
channel,
|
|
2268
|
+
ts: messageId,
|
|
2269
|
+
text: tableResult.text,
|
|
2270
|
+
blocks: tableResult.blocks
|
|
2271
|
+
})
|
|
2272
|
+
);
|
|
2273
|
+
this.logger.debug("Slack API: chat.update response", {
|
|
2274
|
+
messageId: result2.ts,
|
|
2275
|
+
ok: result2.ok
|
|
2276
|
+
});
|
|
2277
|
+
return {
|
|
2278
|
+
id: result2.ts,
|
|
2279
|
+
threadId,
|
|
2280
|
+
raw: result2
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
1961
2283
|
const text = convertEmojiPlaceholders(
|
|
1962
2284
|
this.formatConverter.renderPostable(message),
|
|
1963
2285
|
"slack"
|
|
@@ -2106,9 +2428,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2106
2428
|
*/
|
|
2107
2429
|
async stream(threadId, textStream, options) {
|
|
2108
2430
|
if (!(options?.recipientUserId && options?.recipientTeamId)) {
|
|
2109
|
-
throw new
|
|
2110
|
-
"
|
|
2111
|
-
"
|
|
2431
|
+
throw new ValidationError(
|
|
2432
|
+
"slack",
|
|
2433
|
+
"Slack streaming requires recipientUserId and recipientTeamId in options"
|
|
2112
2434
|
);
|
|
2113
2435
|
}
|
|
2114
2436
|
const { channel, threadTs } = this.decodeThreadId(threadId);
|
|
@@ -2751,9 +3073,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2751
3073
|
} else {
|
|
2752
3074
|
const message = options?.message;
|
|
2753
3075
|
if (!message) {
|
|
2754
|
-
throw new
|
|
2755
|
-
"
|
|
2756
|
-
"
|
|
3076
|
+
throw new ValidationError(
|
|
3077
|
+
"slack",
|
|
3078
|
+
"Message required for replace action"
|
|
2757
3079
|
);
|
|
2758
3080
|
}
|
|
2759
3081
|
const card = extractCard(message);
|
|
@@ -2764,13 +3086,22 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2764
3086
|
blocks: cardToBlockKit(card)
|
|
2765
3087
|
};
|
|
2766
3088
|
} else {
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
3089
|
+
const tableResult = this.renderWithTableBlocks(message);
|
|
3090
|
+
if (tableResult) {
|
|
3091
|
+
payload = {
|
|
3092
|
+
replace_original: true,
|
|
3093
|
+
text: tableResult.text,
|
|
3094
|
+
blocks: tableResult.blocks
|
|
3095
|
+
};
|
|
3096
|
+
} else {
|
|
3097
|
+
payload = {
|
|
3098
|
+
replace_original: true,
|
|
3099
|
+
text: convertEmojiPlaceholders(
|
|
3100
|
+
this.formatConverter.renderPostable(message),
|
|
3101
|
+
"slack"
|
|
3102
|
+
)
|
|
3103
|
+
};
|
|
3104
|
+
}
|
|
2774
3105
|
}
|
|
2775
3106
|
if (options?.threadTs) {
|
|
2776
3107
|
payload.thread_ts = options.threadTs;
|
|
@@ -2792,9 +3123,9 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2792
3123
|
status: response.status,
|
|
2793
3124
|
body: errorText
|
|
2794
3125
|
});
|
|
2795
|
-
throw new
|
|
2796
|
-
|
|
2797
|
-
|
|
3126
|
+
throw new NetworkError(
|
|
3127
|
+
"slack",
|
|
3128
|
+
`Failed to ${action} via response_url: ${errorText}`
|
|
2798
3129
|
);
|
|
2799
3130
|
}
|
|
2800
3131
|
const responseText = await response.text();
|