@chat-adapter/slack 4.17.0 → 4.18.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/dist/index.d.ts +11 -0
- package/dist/index.js +256 -40
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -59,6 +59,12 @@ declare class SlackFormatConverter extends BaseFormatConverter {
|
|
|
59
59
|
* Parse Slack mrkdwn into an AST.
|
|
60
60
|
*/
|
|
61
61
|
toAst(mrkdwn: string): Root;
|
|
62
|
+
/**
|
|
63
|
+
* Convert AST to Slack blocks, using a native table block for the first table.
|
|
64
|
+
* Returns null if the AST contains no tables (caller should use regular text).
|
|
65
|
+
* Slack allows at most one table block per message; additional tables use ASCII.
|
|
66
|
+
*/
|
|
67
|
+
toBlocksWithTable(ast: Root): SlackBlock[] | null;
|
|
62
68
|
private nodeToMrkdwn;
|
|
63
69
|
}
|
|
64
70
|
|
|
@@ -307,6 +313,11 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
|
|
|
307
313
|
* Includes a fetchData method that uses the bot token for auth.
|
|
308
314
|
*/
|
|
309
315
|
private createAttachment;
|
|
316
|
+
/**
|
|
317
|
+
* Try to render a message using native Slack table blocks.
|
|
318
|
+
* Returns blocks + fallback text if the message contains tables, null otherwise.
|
|
319
|
+
*/
|
|
320
|
+
private renderWithTableBlocks;
|
|
310
321
|
postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
|
|
311
322
|
postEphemeral(threadId: string, userId: string, message: AdapterPostableMessage): Promise<EphemeralMessage>;
|
|
312
323
|
openModal(triggerId: string, modal: ModalElement, contextId?: string): Promise<{
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
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;
|
|
@@ -1569,12 +1641,22 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1569
1641
|
* detection doesn't apply.
|
|
1570
1642
|
*/
|
|
1571
1643
|
async resolveInlineMentions(text, skipSelfMention) {
|
|
1572
|
-
const mentionPattern = /<@([A-Z0-9]+)(?:\|[^>]*)?>/g;
|
|
1573
1644
|
const userIds = /* @__PURE__ */ new Set();
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1645
|
+
for (const segment of text.split("<")) {
|
|
1646
|
+
const end = segment.indexOf(">");
|
|
1647
|
+
if (end === -1) {
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
1650
|
+
const inner = segment.slice(0, end);
|
|
1651
|
+
if (!inner.startsWith("@")) {
|
|
1652
|
+
continue;
|
|
1653
|
+
}
|
|
1654
|
+
const rest = inner.slice(1);
|
|
1655
|
+
const pipeIdx = rest.indexOf("|");
|
|
1656
|
+
const uid = pipeIdx >= 0 ? rest.slice(0, pipeIdx) : rest;
|
|
1657
|
+
if (SLACK_USER_ID_PATTERN.test(uid)) {
|
|
1658
|
+
userIds.add(uid);
|
|
1659
|
+
}
|
|
1578
1660
|
}
|
|
1579
1661
|
if (userIds.size === 0) {
|
|
1580
1662
|
return text;
|
|
@@ -1592,10 +1674,29 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1592
1674
|
})
|
|
1593
1675
|
);
|
|
1594
1676
|
const nameMap = new Map(lookups);
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1677
|
+
let result = "";
|
|
1678
|
+
let remaining = text;
|
|
1679
|
+
let startIdx = remaining.indexOf("<@");
|
|
1680
|
+
while (startIdx !== -1) {
|
|
1681
|
+
result += remaining.slice(0, startIdx);
|
|
1682
|
+
remaining = remaining.slice(startIdx);
|
|
1683
|
+
const endIdx = remaining.indexOf(">");
|
|
1684
|
+
if (endIdx === -1) {
|
|
1685
|
+
break;
|
|
1686
|
+
}
|
|
1687
|
+
const inner = remaining.slice(2, endIdx);
|
|
1688
|
+
const pipeIdx = inner.indexOf("|");
|
|
1689
|
+
const uid = pipeIdx >= 0 ? inner.slice(0, pipeIdx) : inner;
|
|
1690
|
+
if (SLACK_USER_ID_PATTERN.test(uid)) {
|
|
1691
|
+
const name = nameMap.get(uid);
|
|
1692
|
+
result += name ? `<@${uid}|${name}>` : `<@${uid}>`;
|
|
1693
|
+
} else {
|
|
1694
|
+
result += remaining.slice(0, endIdx + 1);
|
|
1695
|
+
}
|
|
1696
|
+
remaining = remaining.slice(endIdx + 1);
|
|
1697
|
+
startIdx = remaining.indexOf("<@");
|
|
1698
|
+
}
|
|
1699
|
+
return result + remaining;
|
|
1599
1700
|
}
|
|
1600
1701
|
async parseSlackMessage(event, threadId, options) {
|
|
1601
1702
|
const isMe = this.isMessageFromSelf(event);
|
|
@@ -1672,6 +1773,32 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1672
1773
|
} : void 0
|
|
1673
1774
|
};
|
|
1674
1775
|
}
|
|
1776
|
+
/**
|
|
1777
|
+
* Try to render a message using native Slack table blocks.
|
|
1778
|
+
* Returns blocks + fallback text if the message contains tables, null otherwise.
|
|
1779
|
+
*/
|
|
1780
|
+
renderWithTableBlocks(message) {
|
|
1781
|
+
let ast = null;
|
|
1782
|
+
if (typeof message === "object" && message !== null) {
|
|
1783
|
+
if ("ast" in message) {
|
|
1784
|
+
ast = message.ast;
|
|
1785
|
+
} else if ("markdown" in message) {
|
|
1786
|
+
ast = parseMarkdown2(message.markdown);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
if (!ast) {
|
|
1790
|
+
return null;
|
|
1791
|
+
}
|
|
1792
|
+
const blocks = this.formatConverter.toBlocksWithTable(ast);
|
|
1793
|
+
if (!blocks) {
|
|
1794
|
+
return null;
|
|
1795
|
+
}
|
|
1796
|
+
const fallbackText = convertEmojiPlaceholders(
|
|
1797
|
+
this.formatConverter.renderPostable(message),
|
|
1798
|
+
"slack"
|
|
1799
|
+
);
|
|
1800
|
+
return { text: fallbackText, blocks };
|
|
1801
|
+
}
|
|
1675
1802
|
async postMessage(threadId, message) {
|
|
1676
1803
|
const { channel, threadTs } = this.decodeThreadId(threadId);
|
|
1677
1804
|
try {
|
|
@@ -1718,6 +1845,33 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1718
1845
|
raw: result2
|
|
1719
1846
|
};
|
|
1720
1847
|
}
|
|
1848
|
+
const tableResult = this.renderWithTableBlocks(message);
|
|
1849
|
+
if (tableResult) {
|
|
1850
|
+
this.logger.debug("Slack API: chat.postMessage (table blocks)", {
|
|
1851
|
+
channel,
|
|
1852
|
+
threadTs,
|
|
1853
|
+
blockCount: tableResult.blocks.length
|
|
1854
|
+
});
|
|
1855
|
+
const result2 = await this.client.chat.postMessage(
|
|
1856
|
+
this.withToken({
|
|
1857
|
+
channel,
|
|
1858
|
+
thread_ts: threadTs,
|
|
1859
|
+
text: tableResult.text,
|
|
1860
|
+
blocks: tableResult.blocks,
|
|
1861
|
+
unfurl_links: false,
|
|
1862
|
+
unfurl_media: false
|
|
1863
|
+
})
|
|
1864
|
+
);
|
|
1865
|
+
this.logger.debug("Slack API: chat.postMessage response", {
|
|
1866
|
+
messageId: result2.ts,
|
|
1867
|
+
ok: result2.ok
|
|
1868
|
+
});
|
|
1869
|
+
return {
|
|
1870
|
+
id: result2.ts,
|
|
1871
|
+
threadId,
|
|
1872
|
+
raw: result2
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1721
1875
|
const text = convertEmojiPlaceholders(
|
|
1722
1876
|
this.formatConverter.renderPostable(message),
|
|
1723
1877
|
"slack"
|
|
@@ -1782,6 +1936,34 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1782
1936
|
raw: result2
|
|
1783
1937
|
};
|
|
1784
1938
|
}
|
|
1939
|
+
const tableResult = this.renderWithTableBlocks(message);
|
|
1940
|
+
if (tableResult) {
|
|
1941
|
+
this.logger.debug("Slack API: chat.postEphemeral (table blocks)", {
|
|
1942
|
+
channel,
|
|
1943
|
+
threadTs,
|
|
1944
|
+
userId,
|
|
1945
|
+
blockCount: tableResult.blocks.length
|
|
1946
|
+
});
|
|
1947
|
+
const result2 = await this.client.chat.postEphemeral(
|
|
1948
|
+
this.withToken({
|
|
1949
|
+
channel,
|
|
1950
|
+
thread_ts: threadTs || void 0,
|
|
1951
|
+
user: userId,
|
|
1952
|
+
text: tableResult.text,
|
|
1953
|
+
blocks: tableResult.blocks
|
|
1954
|
+
})
|
|
1955
|
+
);
|
|
1956
|
+
this.logger.debug("Slack API: chat.postEphemeral response", {
|
|
1957
|
+
messageTs: result2.message_ts,
|
|
1958
|
+
ok: result2.ok
|
|
1959
|
+
});
|
|
1960
|
+
return {
|
|
1961
|
+
id: result2.message_ts || "",
|
|
1962
|
+
threadId,
|
|
1963
|
+
usedFallback: false,
|
|
1964
|
+
raw: result2
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1785
1967
|
const text = convertEmojiPlaceholders(
|
|
1786
1968
|
this.formatConverter.renderPostable(message),
|
|
1787
1969
|
"slack"
|
|
@@ -1958,6 +2140,31 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1958
2140
|
raw: result2
|
|
1959
2141
|
};
|
|
1960
2142
|
}
|
|
2143
|
+
const tableResult = this.renderWithTableBlocks(message);
|
|
2144
|
+
if (tableResult) {
|
|
2145
|
+
this.logger.debug("Slack API: chat.update (table blocks)", {
|
|
2146
|
+
channel,
|
|
2147
|
+
messageId,
|
|
2148
|
+
blockCount: tableResult.blocks.length
|
|
2149
|
+
});
|
|
2150
|
+
const result2 = await this.client.chat.update(
|
|
2151
|
+
this.withToken({
|
|
2152
|
+
channel,
|
|
2153
|
+
ts: messageId,
|
|
2154
|
+
text: tableResult.text,
|
|
2155
|
+
blocks: tableResult.blocks
|
|
2156
|
+
})
|
|
2157
|
+
);
|
|
2158
|
+
this.logger.debug("Slack API: chat.update response", {
|
|
2159
|
+
messageId: result2.ts,
|
|
2160
|
+
ok: result2.ok
|
|
2161
|
+
});
|
|
2162
|
+
return {
|
|
2163
|
+
id: result2.ts,
|
|
2164
|
+
threadId,
|
|
2165
|
+
raw: result2
|
|
2166
|
+
};
|
|
2167
|
+
}
|
|
1961
2168
|
const text = convertEmojiPlaceholders(
|
|
1962
2169
|
this.formatConverter.renderPostable(message),
|
|
1963
2170
|
"slack"
|
|
@@ -2764,13 +2971,22 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2764
2971
|
blocks: cardToBlockKit(card)
|
|
2765
2972
|
};
|
|
2766
2973
|
} else {
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2974
|
+
const tableResult = this.renderWithTableBlocks(message);
|
|
2975
|
+
if (tableResult) {
|
|
2976
|
+
payload = {
|
|
2977
|
+
replace_original: true,
|
|
2978
|
+
text: tableResult.text,
|
|
2979
|
+
blocks: tableResult.blocks
|
|
2980
|
+
};
|
|
2981
|
+
} else {
|
|
2982
|
+
payload = {
|
|
2983
|
+
replace_original: true,
|
|
2984
|
+
text: convertEmojiPlaceholders(
|
|
2985
|
+
this.formatConverter.renderPostable(message),
|
|
2986
|
+
"slack"
|
|
2987
|
+
)
|
|
2988
|
+
};
|
|
2989
|
+
}
|
|
2774
2990
|
}
|
|
2775
2991
|
if (options?.threadTs) {
|
|
2776
2992
|
payload.thread_ts = options.threadTs;
|