@chat-adapter/slack 4.1.0 → 4.3.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 CHANGED
@@ -37,29 +37,70 @@ chat.onNewMention(async (thread, message) => {
37
37
  | `botToken` | Yes | Slack bot token (starts with `xoxb-`) |
38
38
  | `signingSecret` | Yes | Slack signing secret for webhook verification |
39
39
 
40
- ## Required Slack Scopes
41
-
42
- Your Slack app needs these OAuth scopes:
43
-
44
- **Bot Token Scopes:**
45
- - `chat:write` - Send messages
46
- - `channels:history` - Read channel messages
47
- - `groups:history` - Read private channel messages
48
- - `im:history` - Read DM messages
49
- - `mpim:history` - Read group DM messages
50
- - `reactions:read` - Read reactions
51
- - `reactions:write` - Add reactions
52
- - `files:read` - Read file attachments
53
- - `users:read` - Read user info
54
-
55
- **Event Subscriptions:**
56
- - `message.channels` - Channel messages
57
- - `message.groups` - Private channel messages
58
- - `message.im` - Direct messages
59
- - `message.mpim` - Group DMs
60
- - `app_mention` - @mentions
61
- - `reaction_added` - Reaction events
62
- - `reaction_removed` - Reaction events
40
+ ## Environment Variables
41
+
42
+ ```bash
43
+ SLACK_BOT_TOKEN=xoxb-...
44
+ SLACK_SIGNING_SECRET=...
45
+ ```
46
+
47
+ ## Slack App Setup
48
+
49
+ ### 1. Create a Slack App
50
+
51
+ 1. Go to [api.slack.com/apps](https://api.slack.com/apps)
52
+ 2. Click **Create New App** → **From scratch**
53
+ 3. Enter app name and select workspace
54
+ 4. Click **Create App**
55
+
56
+ ### 2. Configure Bot Token Scopes
57
+
58
+ 1. Go to **OAuth & Permissions** in the sidebar
59
+ 2. Under **Scopes** → **Bot Token Scopes**, add:
60
+ - `app_mentions:read` - Receive @mention events
61
+ - `channels:history` - Read messages in public channels
62
+ - `channels:read` - View basic channel info
63
+ - `chat:write` - Send messages
64
+ - `groups:history` - Read messages in private channels
65
+ - `groups:read` - View basic private channel info
66
+ - `im:history` - Read direct messages
67
+ - `im:read` - View basic DM info
68
+ - `reactions:read` - View emoji reactions
69
+ - `reactions:write` - Add/remove emoji reactions
70
+ - `users:read` - View user info (for display names)
71
+
72
+ ### 3. Install App to Workspace
73
+
74
+ 1. Go to **OAuth & Permissions**
75
+ 2. Click **Install to Workspace**
76
+ 3. Authorize the app
77
+ 4. Copy the **Bot User OAuth Token** (starts with `xoxb-`) → `SLACK_BOT_TOKEN`
78
+
79
+ ### 4. Get Signing Secret
80
+
81
+ 1. Go to **Basic Information**
82
+ 2. Under **App Credentials**, copy **Signing Secret** → `SLACK_SIGNING_SECRET`
83
+
84
+ ### 5. Configure Event Subscriptions
85
+
86
+ 1. Go to **Event Subscriptions**
87
+ 2. Toggle **Enable Events** to On
88
+ 3. Set **Request URL** to: `https://your-domain.com/api/webhooks/slack`
89
+ - Slack will verify the URL immediately
90
+ 4. Under **Subscribe to bot events**, add:
91
+ - `app_mention` - When someone @mentions your bot
92
+ - `message.channels` - Messages in public channels
93
+ - `message.groups` - Messages in private channels
94
+ - `message.im` - Direct messages
95
+ 5. Click **Save Changes**
96
+
97
+ ### 6. (Optional) Enable Interactivity
98
+
99
+ If you want to use buttons, modals, or other interactive components:
100
+
101
+ 1. Go to **Interactivity & Shortcuts**
102
+ 2. Toggle **Interactivity** to On
103
+ 3. Set **Request URL** to: `https://your-domain.com/api/webhooks/slack`
63
104
 
64
105
  ## Features
65
106
 
@@ -71,6 +112,17 @@ Your Slack app needs these OAuth scopes:
71
112
  - Action callbacks (interactive components)
72
113
  - Direct messages
73
114
 
115
+ ## Troubleshooting
116
+
117
+ ### "Invalid signature" error
118
+ - Verify `SLACK_SIGNING_SECRET` is correct
119
+ - Check that the request timestamp is within 5 minutes (clock sync issue)
120
+
121
+ ### Bot not responding to messages
122
+ - Verify Event Subscriptions are configured
123
+ - Check that the bot has been added to the channel
124
+ - Ensure the webhook URL is correct and accessible
125
+
74
126
  ## License
75
127
 
76
128
  MIT
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CardElement, BaseFormatConverter, AdapterPostableMessage, Root, Adapter, ChatInstance, WebhookOptions, RawMessage, EmojiValue, StreamOptions, FetchOptions, Message, ThreadInfo, FormattedContent } from 'chat';
1
+ import { CardElement, BaseFormatConverter, AdapterPostableMessage, Root, Logger, Adapter, ChatInstance, WebhookOptions, RawMessage, EmojiValue, StreamOptions, FetchOptions, FetchResult, ThreadInfo, Message, FormattedContent } from 'chat';
2
2
 
3
3
  /**
4
4
  * Slack Block Kit converter for cross-platform cards.
@@ -60,6 +60,8 @@ interface SlackAdapterConfig {
60
60
  botToken: string;
61
61
  /** Signing secret for webhook verification */
62
62
  signingSecret: string;
63
+ /** Logger instance for error reporting */
64
+ logger: Logger;
63
65
  /** Override bot username (optional) */
64
66
  userName?: string;
65
67
  /** Bot user ID (will be fetched if not provided) */
@@ -157,14 +159,6 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
157
159
  */
158
160
  private createAttachment;
159
161
  postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
160
- /**
161
- * Extract card element from a AdapterPostableMessage if present.
162
- */
163
- private extractCard;
164
- /**
165
- * Extract files from a AdapterPostableMessage if present.
166
- */
167
- private extractFiles;
168
162
  /**
169
163
  * Upload files to Slack and share them to a channel.
170
164
  * Returns the file IDs of uploaded files.
@@ -187,7 +181,24 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
187
181
  * Returns a thread ID that can be used to post messages.
188
182
  */
189
183
  openDM(userId: string): Promise<string>;
190
- fetchMessages(threadId: string, options?: FetchOptions): Promise<Message<unknown>[]>;
184
+ fetchMessages(threadId: string, options?: FetchOptions): Promise<FetchResult<unknown>>;
185
+ /**
186
+ * Fetch messages in forward direction (oldest first, efficient).
187
+ * Uses native Slack cursor pagination.
188
+ */
189
+ private fetchMessagesForward;
190
+ /**
191
+ * Fetch messages in backward direction (most recent first).
192
+ *
193
+ * Slack's API returns oldest-first, so for backward direction we:
194
+ * 1. Use `latest` parameter to fetch messages before a timestamp (cursor)
195
+ * 2. Fetch up to 1000 messages (API limit) and take the last N
196
+ * 3. Return messages in chronological order (oldest first within the page)
197
+ *
198
+ * Note: For very large threads (>1000 messages), the first backward call
199
+ * may not return the absolute most recent messages. This is a Slack API limitation.
200
+ */
201
+ private fetchMessagesBackward;
191
202
  fetchThread(threadId: string): Promise<ThreadInfo>;
192
203
  encodeThreadId(platformData: SlackThreadId): string;
193
204
  /**
package/dist/index.js CHANGED
@@ -1,21 +1,27 @@
1
1
  // src/index.ts
2
2
  import { createHmac, timingSafeEqual } from "crypto";
3
+ import {
4
+ AdapterRateLimitError,
5
+ extractCard,
6
+ extractFiles,
7
+ NetworkError,
8
+ toBuffer,
9
+ ValidationError
10
+ } from "@chat-adapter/shared";
3
11
  import { WebClient } from "@slack/web-api";
4
12
  import {
5
13
  ChatError,
6
- convertEmojiPlaceholders as convertEmojiPlaceholders2,
7
- defaultEmojiResolver,
8
- isCardElement,
9
- RateLimitError
14
+ convertEmojiPlaceholders,
15
+ defaultEmojiResolver
10
16
  } from "chat";
11
17
 
12
18
  // src/cards.ts
13
19
  import {
14
- convertEmojiPlaceholders
15
- } from "chat";
16
- function convertEmoji(text) {
17
- return convertEmojiPlaceholders(text, "slack");
18
- }
20
+ createEmojiConverter,
21
+ mapButtonStyle,
22
+ cardToFallbackText as sharedCardToFallbackText
23
+ } from "@chat-adapter/shared";
24
+ var convertEmoji = createEmojiConverter("slack");
19
25
  function cardToBlockKit(card) {
20
26
  const blocks = [];
21
27
  if (card.title) {
@@ -121,10 +127,9 @@ function convertButtonToElement(button) {
121
127
  if (button.value) {
122
128
  element.value = button.value;
123
129
  }
124
- if (button.style === "primary") {
125
- element.style = "primary";
126
- } else if (button.style === "danger") {
127
- element.style = "danger";
130
+ const style = mapButtonStyle(button.style, "slack");
131
+ if (style) {
132
+ element.style = style;
128
133
  }
129
134
  return element;
130
135
  }
@@ -150,39 +155,29 @@ ${convertEmoji(field.value)}`
150
155
  };
151
156
  }
152
157
  function cardToFallbackText(card) {
153
- const parts = [];
154
- if (card.title) {
155
- parts.push(`*${convertEmoji(card.title)}*`);
156
- }
157
- if (card.subtitle) {
158
- parts.push(convertEmoji(card.subtitle));
159
- }
160
- for (const child of card.children) {
161
- const text = childToFallbackText(child);
162
- if (text) {
163
- parts.push(text);
164
- }
165
- }
166
- return parts.join("\n");
167
- }
168
- function childToFallbackText(child) {
169
- switch (child.type) {
170
- case "text":
171
- return convertEmoji(child.content);
172
- case "fields":
173
- return child.children.map((f) => `${convertEmoji(f.label)}: ${convertEmoji(f.value)}`).join("\n");
174
- case "actions":
175
- return `[${child.children.map((b) => convertEmoji(b.label)).join("] [")}]`;
176
- case "section":
177
- return child.children.map((c) => childToFallbackText(c)).filter(Boolean).join("\n");
178
- default:
179
- return null;
180
- }
158
+ return sharedCardToFallbackText(card, {
159
+ boldFormat: "*",
160
+ lineBreak: "\n",
161
+ platform: "slack"
162
+ });
181
163
  }
182
164
 
183
165
  // src/markdown.ts
184
166
  import {
185
167
  BaseFormatConverter,
168
+ getNodeChildren,
169
+ getNodeValue,
170
+ isBlockquoteNode,
171
+ isCodeNode,
172
+ isDeleteNode,
173
+ isEmphasisNode,
174
+ isInlineCodeNode,
175
+ isLinkNode,
176
+ isListItemNode,
177
+ isListNode,
178
+ isParagraphNode,
179
+ isStrongNode,
180
+ isTextNode,
186
181
  parseMarkdown
187
182
  } from "chat";
188
183
  var SlackFormatConverter = class extends BaseFormatConverter {
@@ -215,11 +210,10 @@ var SlackFormatConverter = class extends BaseFormatConverter {
215
210
  * Render an AST to Slack mrkdwn format.
216
211
  */
217
212
  fromAst(ast) {
218
- const parts = [];
219
- for (const node of ast.children) {
220
- parts.push(this.nodeToMrkdwn(node));
221
- }
222
- return parts.join("\n\n");
213
+ return this.fromAstWithNodeConverter(
214
+ ast,
215
+ (node) => this.nodeToMrkdwn(node)
216
+ );
223
217
  }
224
218
  /**
225
219
  * Parse Slack mrkdwn into an AST.
@@ -237,55 +231,60 @@ var SlackFormatConverter = class extends BaseFormatConverter {
237
231
  return parseMarkdown(markdown);
238
232
  }
239
233
  nodeToMrkdwn(node) {
240
- switch (node.type) {
241
- case "paragraph":
242
- return node.children.map((child) => this.nodeToMrkdwn(child)).join("");
243
- case "text": {
244
- const textValue = node.value;
245
- return textValue.replace(/@(\w+)/g, "<@$1>");
246
- }
247
- case "strong":
248
- return `*${node.children.map((child) => this.nodeToMrkdwn(child)).join("")}*`;
249
- case "emphasis":
250
- return `_${node.children.map((child) => this.nodeToMrkdwn(child)).join("")}_`;
251
- case "delete":
252
- return `~${node.children.map((child) => this.nodeToMrkdwn(child)).join("")}~`;
253
- case "inlineCode":
254
- return `\`${node.value}\``;
255
- case "code": {
256
- const codeNode = node;
257
- return `\`\`\`${codeNode.lang || ""}
258
- ${codeNode.value}
234
+ if (isParagraphNode(node)) {
235
+ return getNodeChildren(node).map((child) => this.nodeToMrkdwn(child)).join("");
236
+ }
237
+ if (isTextNode(node)) {
238
+ return node.value.replace(/@(\w+)/g, "<@$1>");
239
+ }
240
+ if (isStrongNode(node)) {
241
+ const content = getNodeChildren(node).map((child) => this.nodeToMrkdwn(child)).join("");
242
+ return `*${content}*`;
243
+ }
244
+ if (isEmphasisNode(node)) {
245
+ const content = getNodeChildren(node).map((child) => this.nodeToMrkdwn(child)).join("");
246
+ return `_${content}_`;
247
+ }
248
+ if (isDeleteNode(node)) {
249
+ const content = getNodeChildren(node).map((child) => this.nodeToMrkdwn(child)).join("");
250
+ return `~${content}~`;
251
+ }
252
+ if (isInlineCodeNode(node)) {
253
+ return `\`${node.value}\``;
254
+ }
255
+ if (isCodeNode(node)) {
256
+ return `\`\`\`${node.lang || ""}
257
+ ${node.value}
259
258
  \`\`\``;
260
- }
261
- case "link": {
262
- const linkNode = node;
263
- const linkText = linkNode.children.map((child) => this.nodeToMrkdwn(child)).join("");
264
- return `<${linkNode.url}|${linkText}>`;
265
- }
266
- case "blockquote":
267
- return node.children.map((child) => `> ${this.nodeToMrkdwn(child)}`).join("\n");
268
- case "list":
269
- return node.children.map((item, i) => {
270
- const prefix = node.ordered ? `${i + 1}.` : "\u2022";
271
- const content = item.children.map((child) => this.nodeToMrkdwn(child)).join("");
272
- return `${prefix} ${content}`;
273
- }).join("\n");
274
- case "listItem":
275
- return node.children.map((child) => this.nodeToMrkdwn(child)).join("");
276
- case "break":
277
- return "\n";
278
- case "thematicBreak":
279
- return "---";
280
- default:
281
- if ("children" in node && Array.isArray(node.children)) {
282
- return node.children.map((child) => this.nodeToMrkdwn(child)).join("");
283
- }
284
- if ("value" in node) {
285
- return String(node.value);
286
- }
287
- return "";
288
259
  }
260
+ if (isLinkNode(node)) {
261
+ const linkText = getNodeChildren(node).map((child) => this.nodeToMrkdwn(child)).join("");
262
+ return `<${node.url}|${linkText}>`;
263
+ }
264
+ if (isBlockquoteNode(node)) {
265
+ return getNodeChildren(node).map((child) => `> ${this.nodeToMrkdwn(child)}`).join("\n");
266
+ }
267
+ if (isListNode(node)) {
268
+ return getNodeChildren(node).map((item, i) => {
269
+ const prefix = node.ordered ? `${i + 1}.` : "\u2022";
270
+ const content = getNodeChildren(item).map((child) => this.nodeToMrkdwn(child)).join("");
271
+ return `${prefix} ${content}`;
272
+ }).join("\n");
273
+ }
274
+ if (isListItemNode(node)) {
275
+ return getNodeChildren(node).map((child) => this.nodeToMrkdwn(child)).join("");
276
+ }
277
+ if (node.type === "break") {
278
+ return "\n";
279
+ }
280
+ if (node.type === "thematicBreak") {
281
+ return "---";
282
+ }
283
+ const children = getNodeChildren(node);
284
+ if (children.length > 0) {
285
+ return children.map((child) => this.nodeToMrkdwn(child)).join("");
286
+ }
287
+ return getNodeValue(node);
289
288
  }
290
289
  };
291
290
 
@@ -297,7 +296,7 @@ var SlackAdapter = class _SlackAdapter {
297
296
  signingSecret;
298
297
  botToken;
299
298
  chat = null;
300
- logger = null;
299
+ logger;
301
300
  _botUserId = null;
302
301
  _botId = null;
303
302
  // Bot app ID (B_xxx) - different from user ID
@@ -312,12 +311,12 @@ var SlackAdapter = class _SlackAdapter {
312
311
  this.client = new WebClient(config.botToken);
313
312
  this.signingSecret = config.signingSecret;
314
313
  this.botToken = config.botToken;
314
+ this.logger = config.logger;
315
315
  this.userName = config.userName || "bot";
316
316
  this._botUserId = config.botUserId || null;
317
317
  }
318
318
  async initialize(chat) {
319
319
  this.chat = chat;
320
- this.logger = chat.getLogger(this.name);
321
320
  if (!this._botUserId) {
322
321
  try {
323
322
  const authResult = await this.client.auth.test();
@@ -359,20 +358,20 @@ var SlackAdapter = class _SlackAdapter {
359
358
  _SlackAdapter.USER_CACHE_TTL_MS
360
359
  );
361
360
  }
362
- this.logger?.debug("Fetched user info", {
361
+ this.logger.debug("Fetched user info", {
363
362
  userId,
364
363
  displayName,
365
364
  realName
366
365
  });
367
366
  return { displayName, realName };
368
367
  } catch (error) {
369
- this.logger?.warn("Could not fetch user info", { userId, error });
368
+ this.logger.warn("Could not fetch user info", { userId, error });
370
369
  return { displayName: userId, realName: userId };
371
370
  }
372
371
  }
373
372
  async handleWebhook(request, options) {
374
373
  const body = await request.text();
375
- this.logger?.debug("Slack webhook raw body", { body });
374
+ this.logger.debug("Slack webhook raw body", { body });
376
375
  const timestamp = request.headers.get("x-slack-request-timestamp");
377
376
  const signature = request.headers.get("x-slack-signature");
378
377
  if (!this.verifySignature(body, timestamp, signature)) {
@@ -429,14 +428,14 @@ var SlackAdapter = class _SlackAdapter {
429
428
  */
430
429
  handleBlockActions(payload, options) {
431
430
  if (!this.chat) {
432
- this.logger?.warn("Chat instance not initialized, ignoring action");
431
+ this.logger.warn("Chat instance not initialized, ignoring action");
433
432
  return;
434
433
  }
435
434
  const channel = payload.channel?.id || payload.container?.channel_id;
436
435
  const messageTs = payload.message?.ts || payload.container?.message_ts;
437
436
  const threadTs = payload.message?.thread_ts || messageTs;
438
437
  if (!channel || !messageTs) {
439
- this.logger?.warn("Missing channel or message_ts in block_actions", {
438
+ this.logger.warn("Missing channel or message_ts in block_actions", {
440
439
  channel,
441
440
  messageTs
442
441
  });
@@ -462,7 +461,7 @@ var SlackAdapter = class _SlackAdapter {
462
461
  adapter: this,
463
462
  raw: payload
464
463
  };
465
- this.logger?.debug("Processing Slack block action", {
464
+ this.logger.debug("Processing Slack block action", {
466
465
  actionId: action.action_id,
467
466
  value: action.value,
468
467
  messageId: messageTs,
@@ -496,17 +495,17 @@ var SlackAdapter = class _SlackAdapter {
496
495
  */
497
496
  handleMessageEvent(event, options) {
498
497
  if (!this.chat) {
499
- this.logger?.warn("Chat instance not initialized, ignoring event");
498
+ this.logger.warn("Chat instance not initialized, ignoring event");
500
499
  return;
501
500
  }
502
501
  if (event.subtype && event.subtype !== "bot_message") {
503
- this.logger?.debug("Ignoring message subtype", {
502
+ this.logger.debug("Ignoring message subtype", {
504
503
  subtype: event.subtype
505
504
  });
506
505
  return;
507
506
  }
508
507
  if (!event.channel || !event.ts) {
509
- this.logger?.debug("Ignoring event without channel or ts", {
508
+ this.logger.debug("Ignoring event without channel or ts", {
510
509
  channel: event.channel,
511
510
  ts: event.ts
512
511
  });
@@ -530,11 +529,11 @@ var SlackAdapter = class _SlackAdapter {
530
529
  */
531
530
  handleReactionEvent(event, options) {
532
531
  if (!this.chat) {
533
- this.logger?.warn("Chat instance not initialized, ignoring reaction");
532
+ this.logger.warn("Chat instance not initialized, ignoring reaction");
534
533
  return;
535
534
  }
536
535
  if (event.item.type !== "message") {
537
- this.logger?.debug("Ignoring reaction to non-message item", {
536
+ this.logger.debug("Ignoring reaction to non-message item", {
538
537
  itemType: event.item.type
539
538
  });
540
539
  return;
@@ -629,7 +628,8 @@ var SlackAdapter = class _SlackAdapter {
629
628
  }
630
629
  });
631
630
  if (!response.ok) {
632
- throw new Error(
631
+ throw new NetworkError(
632
+ "slack",
633
633
  `Failed to fetch file: ${response.status} ${response.statusText}`
634
634
  );
635
635
  }
@@ -641,11 +641,11 @@ var SlackAdapter = class _SlackAdapter {
641
641
  async postMessage(threadId, message) {
642
642
  const { channel, threadTs } = this.decodeThreadId(threadId);
643
643
  try {
644
- const files = this.extractFiles(message);
644
+ const files = extractFiles(message);
645
645
  if (files.length > 0) {
646
646
  await this.uploadFiles(files, channel, threadTs || void 0);
647
647
  const hasText = typeof message === "string" || typeof message === "object" && message !== null && ("raw" in message || "markdown" in message || "ast" in message);
648
- const card2 = this.extractCard(message);
648
+ const card2 = extractCard(message);
649
649
  if (!hasText && !card2) {
650
650
  return {
651
651
  id: `file-${Date.now()}`,
@@ -654,11 +654,11 @@ var SlackAdapter = class _SlackAdapter {
654
654
  };
655
655
  }
656
656
  }
657
- const card = this.extractCard(message);
657
+ const card = extractCard(message);
658
658
  if (card) {
659
659
  const blocks = cardToBlockKit(card);
660
660
  const fallbackText = cardToFallbackText(card);
661
- this.logger?.debug("Slack API: chat.postMessage (blocks)", {
661
+ this.logger.debug("Slack API: chat.postMessage (blocks)", {
662
662
  channel,
663
663
  threadTs,
664
664
  blockCount: blocks.length
@@ -672,7 +672,7 @@ var SlackAdapter = class _SlackAdapter {
672
672
  unfurl_links: false,
673
673
  unfurl_media: false
674
674
  });
675
- this.logger?.debug("Slack API: chat.postMessage response", {
675
+ this.logger.debug("Slack API: chat.postMessage response", {
676
676
  messageId: result2.ts,
677
677
  ok: result2.ok
678
678
  });
@@ -682,11 +682,11 @@ var SlackAdapter = class _SlackAdapter {
682
682
  raw: result2
683
683
  };
684
684
  }
685
- const text = convertEmojiPlaceholders2(
685
+ const text = convertEmojiPlaceholders(
686
686
  this.formatConverter.renderPostable(message),
687
687
  "slack"
688
688
  );
689
- this.logger?.debug("Slack API: chat.postMessage", {
689
+ this.logger.debug("Slack API: chat.postMessage", {
690
690
  channel,
691
691
  threadTs,
692
692
  textLength: text.length
@@ -698,7 +698,7 @@ var SlackAdapter = class _SlackAdapter {
698
698
  unfurl_links: false,
699
699
  unfurl_media: false
700
700
  });
701
- this.logger?.debug("Slack API: chat.postMessage response", {
701
+ this.logger.debug("Slack API: chat.postMessage response", {
702
702
  messageId: result.ts,
703
703
  ok: result.ok
704
704
  });
@@ -711,27 +711,6 @@ var SlackAdapter = class _SlackAdapter {
711
711
  this.handleSlackError(error);
712
712
  }
713
713
  }
714
- /**
715
- * Extract card element from a AdapterPostableMessage if present.
716
- */
717
- extractCard(message) {
718
- if (isCardElement(message)) {
719
- return message;
720
- }
721
- if (typeof message === "object" && message !== null && "card" in message) {
722
- return message.card;
723
- }
724
- return null;
725
- }
726
- /**
727
- * Extract files from a AdapterPostableMessage if present.
728
- */
729
- extractFiles(message) {
730
- if (typeof message === "object" && message !== null && "files" in message) {
731
- return message.files ?? [];
732
- }
733
- return [];
734
- }
735
714
  /**
736
715
  * Upload files to Slack and share them to a channel.
737
716
  * Returns the file IDs of uploaded files.
@@ -740,18 +719,11 @@ var SlackAdapter = class _SlackAdapter {
740
719
  const fileIds = [];
741
720
  for (const file of files) {
742
721
  try {
743
- let fileBuffer;
744
- if (Buffer.isBuffer(file.data)) {
745
- fileBuffer = file.data;
746
- } else if (file.data instanceof ArrayBuffer) {
747
- fileBuffer = Buffer.from(file.data);
748
- } else if (file.data instanceof Blob) {
749
- const arrayBuffer = await file.data.arrayBuffer();
750
- fileBuffer = Buffer.from(arrayBuffer);
751
- } else {
752
- throw new Error("Unsupported file data type");
722
+ const fileBuffer = await toBuffer(file.data, { platform: "slack" });
723
+ if (!fileBuffer) {
724
+ continue;
753
725
  }
754
- this.logger?.debug("Slack API: files.uploadV2", {
726
+ this.logger.debug("Slack API: files.uploadV2", {
755
727
  filename: file.filename,
756
728
  size: fileBuffer.length,
757
729
  mimeType: file.mimeType
@@ -765,7 +737,7 @@ var SlackAdapter = class _SlackAdapter {
765
737
  uploadArgs.thread_ts = threadTs;
766
738
  }
767
739
  const result = await this.client.files.uploadV2(uploadArgs);
768
- this.logger?.debug("Slack API: files.uploadV2 response", {
740
+ this.logger.debug("Slack API: files.uploadV2 response", {
769
741
  ok: result.ok
770
742
  });
771
743
  if (result.files && Array.isArray(result.files)) {
@@ -776,7 +748,7 @@ var SlackAdapter = class _SlackAdapter {
776
748
  }
777
749
  }
778
750
  } catch (error) {
779
- this.logger?.error("Failed to upload file", {
751
+ this.logger.error("Failed to upload file", {
780
752
  filename: file.filename,
781
753
  error
782
754
  });
@@ -788,11 +760,11 @@ var SlackAdapter = class _SlackAdapter {
788
760
  async editMessage(threadId, messageId, message) {
789
761
  const { channel } = this.decodeThreadId(threadId);
790
762
  try {
791
- const card = this.extractCard(message);
763
+ const card = extractCard(message);
792
764
  if (card) {
793
765
  const blocks = cardToBlockKit(card);
794
766
  const fallbackText = cardToFallbackText(card);
795
- this.logger?.debug("Slack API: chat.update (blocks)", {
767
+ this.logger.debug("Slack API: chat.update (blocks)", {
796
768
  channel,
797
769
  messageId,
798
770
  blockCount: blocks.length
@@ -803,7 +775,7 @@ var SlackAdapter = class _SlackAdapter {
803
775
  text: fallbackText,
804
776
  blocks
805
777
  });
806
- this.logger?.debug("Slack API: chat.update response", {
778
+ this.logger.debug("Slack API: chat.update response", {
807
779
  messageId: result2.ts,
808
780
  ok: result2.ok
809
781
  });
@@ -813,11 +785,11 @@ var SlackAdapter = class _SlackAdapter {
813
785
  raw: result2
814
786
  };
815
787
  }
816
- const text = convertEmojiPlaceholders2(
788
+ const text = convertEmojiPlaceholders(
817
789
  this.formatConverter.renderPostable(message),
818
790
  "slack"
819
791
  );
820
- this.logger?.debug("Slack API: chat.update", {
792
+ this.logger.debug("Slack API: chat.update", {
821
793
  channel,
822
794
  messageId,
823
795
  textLength: text.length
@@ -827,7 +799,7 @@ var SlackAdapter = class _SlackAdapter {
827
799
  ts: messageId,
828
800
  text
829
801
  });
830
- this.logger?.debug("Slack API: chat.update response", {
802
+ this.logger.debug("Slack API: chat.update response", {
831
803
  messageId: result.ts,
832
804
  ok: result.ok
833
805
  });
@@ -843,12 +815,12 @@ var SlackAdapter = class _SlackAdapter {
843
815
  async deleteMessage(threadId, messageId) {
844
816
  const { channel } = this.decodeThreadId(threadId);
845
817
  try {
846
- this.logger?.debug("Slack API: chat.delete", { channel, messageId });
818
+ this.logger.debug("Slack API: chat.delete", { channel, messageId });
847
819
  await this.client.chat.delete({
848
820
  channel,
849
821
  ts: messageId
850
822
  });
851
- this.logger?.debug("Slack API: chat.delete response", { ok: true });
823
+ this.logger.debug("Slack API: chat.delete response", { ok: true });
852
824
  } catch (error) {
853
825
  this.handleSlackError(error);
854
826
  }
@@ -858,7 +830,7 @@ var SlackAdapter = class _SlackAdapter {
858
830
  const slackEmoji = defaultEmojiResolver.toSlack(emoji);
859
831
  const name = slackEmoji.replace(/:/g, "");
860
832
  try {
861
- this.logger?.debug("Slack API: reactions.add", {
833
+ this.logger.debug("Slack API: reactions.add", {
862
834
  channel,
863
835
  messageId,
864
836
  emoji: name
@@ -868,7 +840,7 @@ var SlackAdapter = class _SlackAdapter {
868
840
  timestamp: messageId,
869
841
  name
870
842
  });
871
- this.logger?.debug("Slack API: reactions.add response", { ok: true });
843
+ this.logger.debug("Slack API: reactions.add response", { ok: true });
872
844
  } catch (error) {
873
845
  this.handleSlackError(error);
874
846
  }
@@ -878,7 +850,7 @@ var SlackAdapter = class _SlackAdapter {
878
850
  const slackEmoji = defaultEmojiResolver.toSlack(emoji);
879
851
  const name = slackEmoji.replace(/:/g, "");
880
852
  try {
881
- this.logger?.debug("Slack API: reactions.remove", {
853
+ this.logger.debug("Slack API: reactions.remove", {
882
854
  channel,
883
855
  messageId,
884
856
  emoji: name
@@ -888,7 +860,7 @@ var SlackAdapter = class _SlackAdapter {
888
860
  timestamp: messageId,
889
861
  name
890
862
  });
891
- this.logger?.debug("Slack API: reactions.remove response", { ok: true });
863
+ this.logger.debug("Slack API: reactions.remove response", { ok: true });
892
864
  } catch (error) {
893
865
  this.handleSlackError(error);
894
866
  }
@@ -909,7 +881,7 @@ var SlackAdapter = class _SlackAdapter {
909
881
  );
910
882
  }
911
883
  const { channel, threadTs } = this.decodeThreadId(threadId);
912
- this.logger?.debug("Slack: starting stream", { channel, threadTs });
884
+ this.logger.debug("Slack: starting stream", { channel, threadTs });
913
885
  const streamer = this.client.chatStream({
914
886
  channel,
915
887
  thread_ts: threadTs,
@@ -921,7 +893,7 @@ var SlackAdapter = class _SlackAdapter {
921
893
  }
922
894
  const result = await streamer.stop();
923
895
  const messageTs = result.message?.ts ?? result.ts;
924
- this.logger?.debug("Slack: stream complete", { messageId: messageTs });
896
+ this.logger.debug("Slack: stream complete", { messageId: messageTs });
925
897
  return {
926
898
  id: messageTs,
927
899
  threadId,
@@ -934,13 +906,16 @@ var SlackAdapter = class _SlackAdapter {
934
906
  */
935
907
  async openDM(userId) {
936
908
  try {
937
- this.logger?.debug("Slack API: conversations.open", { userId });
909
+ this.logger.debug("Slack API: conversations.open", { userId });
938
910
  const result = await this.client.conversations.open({ users: userId });
939
911
  if (!result.channel?.id) {
940
- throw new Error("Failed to open DM - no channel returned");
912
+ throw new NetworkError(
913
+ "slack",
914
+ "Failed to open DM - no channel returned"
915
+ );
941
916
  }
942
917
  const channelId = result.channel.id;
943
- this.logger?.debug("Slack API: conversations.open response", {
918
+ this.logger.debug("Slack API: conversations.open response", {
944
919
  channelId,
945
920
  ok: result.ok
946
921
  });
@@ -955,35 +930,119 @@ var SlackAdapter = class _SlackAdapter {
955
930
  }
956
931
  async fetchMessages(threadId, options = {}) {
957
932
  const { channel, threadTs } = this.decodeThreadId(threadId);
933
+ const direction = options.direction ?? "backward";
934
+ const limit = options.limit || 100;
958
935
  try {
959
- this.logger?.debug("Slack API: conversations.replies", {
936
+ if (direction === "forward") {
937
+ return this.fetchMessagesForward(
938
+ channel,
939
+ threadTs,
940
+ threadId,
941
+ limit,
942
+ options.cursor
943
+ );
944
+ }
945
+ return this.fetchMessagesBackward(
960
946
  channel,
961
947
  threadTs,
962
- limit: options.limit || 100
963
- });
964
- const result = await this.client.conversations.replies({
965
- channel,
966
- ts: threadTs,
967
- limit: options.limit || 100,
968
- cursor: options.before
969
- });
970
- const messages = result.messages || [];
971
- this.logger?.debug("Slack API: conversations.replies response", {
972
- messageCount: messages.length,
973
- ok: result.ok
974
- });
975
- return messages.map((msg) => this.parseSlackMessageSync(msg, threadId));
948
+ threadId,
949
+ limit,
950
+ options.cursor
951
+ );
976
952
  } catch (error) {
977
953
  this.handleSlackError(error);
978
954
  }
979
955
  }
956
+ /**
957
+ * Fetch messages in forward direction (oldest first, efficient).
958
+ * Uses native Slack cursor pagination.
959
+ */
960
+ async fetchMessagesForward(channel, threadTs, threadId, limit, cursor) {
961
+ this.logger.debug("Slack API: conversations.replies (forward)", {
962
+ channel,
963
+ threadTs,
964
+ limit,
965
+ cursor
966
+ });
967
+ const result = await this.client.conversations.replies({
968
+ channel,
969
+ ts: threadTs,
970
+ limit,
971
+ cursor
972
+ });
973
+ const slackMessages = result.messages || [];
974
+ const nextCursor = result.response_metadata?.next_cursor;
975
+ this.logger.debug("Slack API: conversations.replies response", {
976
+ messageCount: slackMessages.length,
977
+ ok: result.ok,
978
+ hasNextCursor: !!nextCursor
979
+ });
980
+ const messages = await Promise.all(
981
+ slackMessages.map((msg) => this.parseSlackMessage(msg, threadId))
982
+ );
983
+ return {
984
+ messages,
985
+ nextCursor: nextCursor || void 0
986
+ };
987
+ }
988
+ /**
989
+ * Fetch messages in backward direction (most recent first).
990
+ *
991
+ * Slack's API returns oldest-first, so for backward direction we:
992
+ * 1. Use `latest` parameter to fetch messages before a timestamp (cursor)
993
+ * 2. Fetch up to 1000 messages (API limit) and take the last N
994
+ * 3. Return messages in chronological order (oldest first within the page)
995
+ *
996
+ * Note: For very large threads (>1000 messages), the first backward call
997
+ * may not return the absolute most recent messages. This is a Slack API limitation.
998
+ */
999
+ async fetchMessagesBackward(channel, threadTs, threadId, limit, cursor) {
1000
+ const latest = cursor || void 0;
1001
+ this.logger.debug("Slack API: conversations.replies (backward)", {
1002
+ channel,
1003
+ threadTs,
1004
+ limit,
1005
+ latest
1006
+ });
1007
+ const fetchLimit = Math.min(1e3, Math.max(limit * 2, 200));
1008
+ const result = await this.client.conversations.replies({
1009
+ channel,
1010
+ ts: threadTs,
1011
+ limit: fetchLimit,
1012
+ latest,
1013
+ inclusive: false
1014
+ // Don't include the cursor message itself
1015
+ });
1016
+ const slackMessages = result.messages || [];
1017
+ this.logger.debug("Slack API: conversations.replies response (backward)", {
1018
+ messageCount: slackMessages.length,
1019
+ ok: result.ok,
1020
+ hasMore: result.has_more
1021
+ });
1022
+ const startIndex = Math.max(0, slackMessages.length - limit);
1023
+ const selectedMessages = slackMessages.slice(startIndex);
1024
+ const messages = await Promise.all(
1025
+ selectedMessages.map((msg) => this.parseSlackMessage(msg, threadId))
1026
+ );
1027
+ let nextCursor;
1028
+ if (startIndex > 0 || result.has_more) {
1029
+ const oldestSelected = selectedMessages[0];
1030
+ if (oldestSelected?.ts) {
1031
+ nextCursor = oldestSelected.ts;
1032
+ }
1033
+ }
1034
+ return {
1035
+ messages,
1036
+ nextCursor
1037
+ };
1038
+ }
980
1039
  async fetchThread(threadId) {
981
1040
  const { channel, threadTs } = this.decodeThreadId(threadId);
982
1041
  try {
983
- this.logger?.debug("Slack API: conversations.info", { channel });
1042
+ this.logger.debug("Slack API: conversations.info", { channel });
984
1043
  const result = await this.client.conversations.info({ channel });
985
1044
  const channelInfo = result.channel;
986
- this.logger?.debug("Slack API: conversations.info response", {
1045
+ this.logger.debug("Slack API: conversations.info response", {
987
1046
  channelName: channelInfo?.name,
988
1047
  ok: result.ok
989
1048
  });
@@ -1014,7 +1073,10 @@ var SlackAdapter = class _SlackAdapter {
1014
1073
  decodeThreadId(threadId) {
1015
1074
  const parts = threadId.split(":");
1016
1075
  if (parts.length !== 3 || parts[0] !== "slack") {
1017
- throw new Error(`Invalid Slack thread ID: ${threadId}`);
1076
+ throw new ValidationError(
1077
+ "slack",
1078
+ `Invalid Slack thread ID: ${threadId}`
1079
+ );
1018
1080
  }
1019
1081
  return {
1020
1082
  channel: parts[1],
@@ -1089,7 +1151,7 @@ var SlackAdapter = class _SlackAdapter {
1089
1151
  const slackError = error;
1090
1152
  if (slackError.code === "slack_webapi_platform_error") {
1091
1153
  if (slackError.data?.error === "ratelimited") {
1092
- throw new RateLimitError("Slack rate limit exceeded", void 0, error);
1154
+ throw new AdapterRateLimitError("slack");
1093
1155
  }
1094
1156
  }
1095
1157
  throw error;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/cards.ts","../src/markdown.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { WebClient } from \"@slack/web-api\";\nimport type {\n ActionEvent,\n Adapter,\n AdapterPostableMessage,\n Attachment,\n ChatInstance,\n EmojiValue,\n FetchOptions,\n FileUpload,\n FormattedContent,\n Logger,\n Message,\n RawMessage,\n ReactionEvent,\n StreamOptions,\n ThreadInfo,\n WebhookOptions,\n} from \"chat\";\nimport {\n ChatError,\n convertEmojiPlaceholders,\n defaultEmojiResolver,\n isCardElement,\n RateLimitError,\n} from \"chat\";\nimport { cardToBlockKit, cardToFallbackText } from \"./cards\";\nimport { SlackFormatConverter } from \"./markdown\";\n\nexport interface SlackAdapterConfig {\n /** Bot token (xoxb-...) */\n botToken: string;\n /** Signing secret for webhook verification */\n signingSecret: string;\n /** Override bot username (optional) */\n userName?: string;\n /** Bot user ID (will be fetched if not provided) */\n botUserId?: string;\n}\n\n/** Slack-specific thread ID data */\nexport interface SlackThreadId {\n channel: string;\n threadTs: string;\n}\n\n/** Slack event payload (raw message format) */\nexport interface SlackEvent {\n type: string;\n user?: string;\n bot_id?: string;\n channel?: string;\n text?: string;\n ts?: string;\n thread_ts?: string;\n subtype?: string;\n username?: string;\n edited?: { ts: string };\n /** Channel type: \"channel\", \"group\", \"mpim\", or \"im\" (DM) */\n channel_type?: string;\n files?: Array<{\n id?: string;\n mimetype?: string;\n url_private?: string;\n name?: string;\n size?: number;\n original_w?: number;\n original_h?: number;\n }>;\n}\n\n/** Slack reaction event payload */\nexport interface SlackReactionEvent {\n type: \"reaction_added\" | \"reaction_removed\";\n user: string;\n reaction: string;\n item_user?: string;\n item: {\n type: string;\n channel: string;\n ts: string;\n };\n event_ts: string;\n}\n\n/** Slack webhook payload envelope */\ninterface SlackWebhookPayload {\n type: string;\n challenge?: string;\n event?: SlackEvent | SlackReactionEvent;\n event_id?: string;\n event_time?: number;\n}\n\n/** Slack interactive payload (block_actions) for button clicks */\ninterface SlackBlockActionsPayload {\n type: \"block_actions\";\n user: {\n id: string;\n username: string;\n name?: string;\n };\n container: {\n type: string;\n message_ts: string;\n channel_id: string;\n is_ephemeral?: boolean;\n };\n channel: {\n id: string;\n name: string;\n };\n message: {\n ts: string;\n thread_ts?: string;\n };\n actions: Array<{\n type: string;\n action_id: string;\n block_id?: string;\n value?: string;\n action_ts?: string;\n }>;\n response_url?: string;\n}\n\n/** Cached user info */\ninterface CachedUser {\n displayName: string;\n realName: string;\n}\n\nexport class SlackAdapter implements Adapter<SlackThreadId, unknown> {\n readonly name = \"slack\";\n readonly userName: string;\n\n private client: WebClient;\n private signingSecret: string;\n private botToken: string;\n private chat: ChatInstance | null = null;\n private logger: Logger | null = null;\n private _botUserId: string | null = null;\n private _botId: string | null = null; // Bot app ID (B_xxx) - different from user ID\n private formatConverter = new SlackFormatConverter();\n private static USER_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\n\n /** Bot user ID (e.g., U_BOT_123) used for mention detection */\n get botUserId(): string | undefined {\n return this._botUserId || undefined;\n }\n\n constructor(config: SlackAdapterConfig) {\n this.client = new WebClient(config.botToken);\n this.signingSecret = config.signingSecret;\n this.botToken = config.botToken;\n this.userName = config.userName || \"bot\";\n this._botUserId = config.botUserId || null;\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n this.logger = chat.getLogger(this.name);\n\n // Fetch bot user ID and bot ID if not provided\n if (!this._botUserId) {\n try {\n const authResult = await this.client.auth.test();\n this._botUserId = authResult.user_id as string;\n this._botId = (authResult.bot_id as string) || null;\n if (authResult.user) {\n (this as { userName: string }).userName = authResult.user as string;\n }\n this.logger.info(\"Slack auth completed\", {\n botUserId: this._botUserId,\n botId: this._botId,\n });\n } catch (error) {\n this.logger.warn(\"Could not fetch bot user ID\", { error });\n }\n }\n }\n\n /**\n * Look up user info from Slack API with caching via state adapter.\n * Returns display name and real name, or falls back to user ID.\n */\n private async lookupUser(\n userId: string,\n ): Promise<{ displayName: string; realName: string }> {\n const cacheKey = `slack:user:${userId}`;\n\n // Check cache first (via state adapter for serverless compatibility)\n if (this.chat) {\n const cached = await this.chat.getState().get<CachedUser>(cacheKey);\n if (cached) {\n return { displayName: cached.displayName, realName: cached.realName };\n }\n }\n\n try {\n const result = await this.client.users.info({ user: userId });\n const user = result.user as {\n name?: string;\n real_name?: string;\n profile?: { display_name?: string; real_name?: string };\n };\n\n // Slack user naming: profile.display_name > profile.real_name > real_name > name > userId\n const displayName =\n user?.profile?.display_name ||\n user?.profile?.real_name ||\n user?.real_name ||\n user?.name ||\n userId;\n const realName =\n user?.real_name || user?.profile?.real_name || displayName;\n\n // Cache the result via state adapter\n if (this.chat) {\n await this.chat\n .getState()\n .set<CachedUser>(\n cacheKey,\n { displayName, realName },\n SlackAdapter.USER_CACHE_TTL_MS,\n );\n }\n\n this.logger?.debug(\"Fetched user info\", {\n userId,\n displayName,\n realName,\n });\n return { displayName, realName };\n } catch (error) {\n this.logger?.warn(\"Could not fetch user info\", { userId, error });\n // Fall back to user ID\n return { displayName: userId, realName: userId };\n }\n }\n\n async handleWebhook(\n request: Request,\n options?: WebhookOptions,\n ): Promise<Response> {\n const body = await request.text();\n this.logger?.debug(\"Slack webhook raw body\", { body });\n\n // Verify request signature\n const timestamp = request.headers.get(\"x-slack-request-timestamp\");\n const signature = request.headers.get(\"x-slack-signature\");\n\n if (!this.verifySignature(body, timestamp, signature)) {\n return new Response(\"Invalid signature\", { status: 401 });\n }\n\n // Check if this is a form-urlencoded interactive payload\n const contentType = request.headers.get(\"content-type\") || \"\";\n if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n return this.handleInteractivePayload(body, options);\n }\n\n // Parse the JSON payload\n let payload: SlackWebhookPayload;\n try {\n payload = JSON.parse(body);\n } catch {\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n // Handle URL verification challenge\n if (payload.type === \"url_verification\" && payload.challenge) {\n return new Response(JSON.stringify({ challenge: payload.challenge }), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n // Handle events\n if (payload.type === \"event_callback\" && payload.event) {\n // Respond immediately to avoid timeout\n const event = payload.event;\n\n // Process event asynchronously\n if (event.type === \"message\" || event.type === \"app_mention\") {\n this.handleMessageEvent(event as SlackEvent, options);\n } else if (\n event.type === \"reaction_added\" ||\n event.type === \"reaction_removed\"\n ) {\n this.handleReactionEvent(event as SlackReactionEvent, options);\n }\n }\n\n return new Response(\"ok\", { status: 200 });\n }\n\n /**\n * Handle Slack interactive payloads (button clicks, etc.).\n * These are sent as form-urlencoded with a `payload` JSON field.\n */\n private handleInteractivePayload(\n body: string,\n options?: WebhookOptions,\n ): Response {\n // Parse form-urlencoded body\n const params = new URLSearchParams(body);\n const payloadStr = params.get(\"payload\");\n\n if (!payloadStr) {\n return new Response(\"Missing payload\", { status: 400 });\n }\n\n let payload: SlackBlockActionsPayload;\n try {\n payload = JSON.parse(payloadStr);\n } catch {\n return new Response(\"Invalid payload JSON\", { status: 400 });\n }\n\n // Handle block_actions (button clicks)\n if (payload.type === \"block_actions\") {\n this.handleBlockActions(payload, options);\n }\n\n // Respond immediately - Slack requires fast responses for interactions\n return new Response(\"\", { status: 200 });\n }\n\n /**\n * Handle block_actions payload (button clicks in Block Kit).\n */\n private handleBlockActions(\n payload: SlackBlockActionsPayload,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n this.logger?.warn(\"Chat instance not initialized, ignoring action\");\n return;\n }\n\n const channel = payload.channel?.id || payload.container?.channel_id;\n const messageTs = payload.message?.ts || payload.container?.message_ts;\n const threadTs = payload.message?.thread_ts || messageTs;\n\n if (!channel || !messageTs) {\n this.logger?.warn(\"Missing channel or message_ts in block_actions\", {\n channel,\n messageTs,\n });\n return;\n }\n\n const threadId = this.encodeThreadId({\n channel,\n threadTs: threadTs || messageTs,\n });\n\n // Process each action (usually just one, but can be multiple)\n for (const action of payload.actions) {\n const actionEvent: Omit<ActionEvent, \"thread\"> & {\n adapter: SlackAdapter;\n } = {\n actionId: action.action_id,\n value: action.value,\n user: {\n userId: payload.user.id,\n userName: payload.user.username || payload.user.name || \"unknown\",\n fullName: payload.user.name || payload.user.username || \"unknown\",\n isBot: false,\n isMe: false,\n },\n messageId: messageTs,\n threadId,\n adapter: this,\n raw: payload,\n };\n\n this.logger?.debug(\"Processing Slack block action\", {\n actionId: action.action_id,\n value: action.value,\n messageId: messageTs,\n threadId,\n });\n\n this.chat.processAction(actionEvent, options);\n }\n }\n\n private verifySignature(\n body: string,\n timestamp: string | null,\n signature: string | null,\n ): boolean {\n if (!timestamp || !signature) {\n return false;\n }\n\n // Check timestamp is recent (within 5 minutes)\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - parseInt(timestamp, 10)) > 300) {\n return false;\n }\n\n // Compute expected signature\n const sigBasestring = `v0:${timestamp}:${body}`;\n const expectedSignature =\n \"v0=\" +\n createHmac(\"sha256\", this.signingSecret)\n .update(sigBasestring)\n .digest(\"hex\");\n\n // Compare signatures using timing-safe comparison\n try {\n return timingSafeEqual(\n Buffer.from(signature),\n Buffer.from(expectedSignature),\n );\n } catch {\n return false;\n }\n }\n\n /**\n * Handle message events from Slack.\n * Bot message filtering (isMe) is handled centrally by the Chat class.\n */\n private handleMessageEvent(\n event: SlackEvent,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n this.logger?.warn(\"Chat instance not initialized, ignoring event\");\n return;\n }\n\n // Skip message subtypes we don't handle (edits, deletes, etc.)\n // Note: bot_message subtype is allowed through - Chat class filters via isMe\n if (event.subtype && event.subtype !== \"bot_message\") {\n this.logger?.debug(\"Ignoring message subtype\", {\n subtype: event.subtype,\n });\n return;\n }\n\n if (!event.channel || !event.ts) {\n this.logger?.debug(\"Ignoring event without channel or ts\", {\n channel: event.channel,\n ts: event.ts,\n });\n return;\n }\n\n // For DMs (channel_type: \"im\"), use empty threadTs so all messages in the DM\n // match the DM subscription created by openDM(). This treats the entire DM\n // conversation as a single \"thread\" for subscription purposes.\n const isDM = event.channel_type === \"im\";\n const threadTs = isDM ? \"\" : event.thread_ts || event.ts;\n const threadId = this.encodeThreadId({\n channel: event.channel,\n threadTs,\n });\n\n // Let Chat class handle async processing, waitUntil, and isMe filtering\n // Use factory function since parseSlackMessage is async (user lookup)\n this.chat.processMessage(\n this,\n threadId,\n () => this.parseSlackMessage(event, threadId),\n options,\n );\n }\n\n /**\n * Handle reaction events from Slack (reaction_added, reaction_removed).\n */\n private handleReactionEvent(\n event: SlackReactionEvent,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n this.logger?.warn(\"Chat instance not initialized, ignoring reaction\");\n return;\n }\n\n // Only handle reactions to messages (not files, etc.)\n if (event.item.type !== \"message\") {\n this.logger?.debug(\"Ignoring reaction to non-message item\", {\n itemType: event.item.type,\n });\n return;\n }\n\n // Build thread ID from the reacted message\n const threadId = this.encodeThreadId({\n channel: event.item.channel,\n threadTs: event.item.ts,\n });\n\n // Message ID is just the timestamp (Slack uses ts as message ID)\n const messageId = event.item.ts;\n\n // Normalize emoji\n const rawEmoji = event.reaction;\n const normalizedEmoji = defaultEmojiResolver.fromSlack(rawEmoji);\n\n // Check if reaction is from this bot\n const isMe =\n (this._botUserId !== null && event.user === this._botUserId) ||\n (this._botId !== null && event.user === this._botId);\n\n // Build reaction event\n const reactionEvent: Omit<ReactionEvent, \"adapter\" | \"thread\"> = {\n emoji: normalizedEmoji,\n rawEmoji,\n added: event.type === \"reaction_added\",\n user: {\n userId: event.user,\n userName: event.user, // Will be resolved below if possible\n fullName: event.user,\n isBot: false, // Users add reactions, not bots typically\n isMe,\n },\n messageId,\n threadId,\n raw: event,\n };\n\n // Process reaction\n this.chat.processReaction({ ...reactionEvent, adapter: this }, options);\n }\n\n private async parseSlackMessage(\n event: SlackEvent,\n threadId: string,\n ): Promise<Message<unknown>> {\n const isMe = this.isMessageFromSelf(event);\n\n const text = event.text || \"\";\n\n // Get user info - for human users we need to look up the display name\n // since Slack events only include the user ID, not the username\n let userName = event.username || \"unknown\";\n let fullName = event.username || \"unknown\";\n\n // If we have a user ID but no username, look up the user info\n if (event.user && !event.username) {\n const userInfo = await this.lookupUser(event.user);\n userName = userInfo.displayName;\n fullName = userInfo.realName;\n }\n\n return {\n id: event.ts || \"\",\n threadId,\n text: this.formatConverter.extractPlainText(text),\n formatted: this.formatConverter.toAst(text),\n raw: event,\n author: {\n userId: event.user || event.bot_id || \"unknown\",\n userName,\n fullName,\n isBot: !!event.bot_id,\n isMe,\n },\n metadata: {\n dateSent: new Date(parseFloat(event.ts || \"0\") * 1000),\n edited: !!event.edited,\n editedAt: event.edited\n ? new Date(parseFloat(event.edited.ts) * 1000)\n : undefined,\n },\n attachments: (event.files || []).map((file) =>\n this.createAttachment(file),\n ),\n };\n }\n\n /**\n * Create an Attachment object from a Slack file.\n * Includes a fetchData method that uses the bot token for auth.\n */\n private createAttachment(file: {\n id?: string;\n mimetype?: string;\n url_private?: string;\n name?: string;\n size?: number;\n original_w?: number;\n original_h?: number;\n }): Attachment {\n const url = file.url_private;\n const botToken = this.botToken;\n\n // Determine type based on mimetype\n let type: Attachment[\"type\"] = \"file\";\n if (file.mimetype?.startsWith(\"image/\")) {\n type = \"image\";\n } else if (file.mimetype?.startsWith(\"video/\")) {\n type = \"video\";\n } else if (file.mimetype?.startsWith(\"audio/\")) {\n type = \"audio\";\n }\n\n return {\n type,\n url,\n name: file.name,\n mimeType: file.mimetype,\n size: file.size,\n width: file.original_w,\n height: file.original_h,\n fetchData: url\n ? async () => {\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${botToken}`,\n },\n });\n if (!response.ok) {\n throw new Error(\n `Failed to fetch file: ${response.status} ${response.statusText}`,\n );\n }\n const arrayBuffer = await response.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n : undefined,\n };\n }\n\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<unknown>> {\n const { channel, threadTs } = this.decodeThreadId(threadId);\n\n try {\n // Check for files to upload\n const files = this.extractFiles(message);\n if (files.length > 0) {\n // Upload files first (they're shared to the channel automatically)\n await this.uploadFiles(files, channel, threadTs || undefined);\n\n // If message only has files (no text/card), return early\n const hasText =\n typeof message === \"string\" ||\n (typeof message === \"object\" &&\n message !== null &&\n (\"raw\" in message || \"markdown\" in message || \"ast\" in message));\n const card = this.extractCard(message);\n\n if (!hasText && !card) {\n // Return a synthetic message ID since files.uploadV2 handles sharing\n return {\n id: `file-${Date.now()}`,\n threadId,\n raw: { files },\n };\n }\n }\n\n // Check if message contains a card\n const card = this.extractCard(message);\n\n if (card) {\n // Render card as Block Kit\n const blocks = cardToBlockKit(card);\n const fallbackText = cardToFallbackText(card);\n\n this.logger?.debug(\"Slack API: chat.postMessage (blocks)\", {\n channel,\n threadTs,\n blockCount: blocks.length,\n });\n\n const result = await this.client.chat.postMessage({\n channel,\n thread_ts: threadTs,\n text: fallbackText, // Fallback for notifications\n blocks,\n unfurl_links: false,\n unfurl_media: false,\n });\n\n this.logger?.debug(\"Slack API: chat.postMessage response\", {\n messageId: result.ts,\n ok: result.ok,\n });\n\n return {\n id: result.ts as string,\n threadId,\n raw: result,\n };\n }\n\n // Regular text message\n const text = convertEmojiPlaceholders(\n this.formatConverter.renderPostable(message),\n \"slack\",\n );\n\n this.logger?.debug(\"Slack API: chat.postMessage\", {\n channel,\n threadTs,\n textLength: text.length,\n });\n\n const result = await this.client.chat.postMessage({\n channel,\n thread_ts: threadTs,\n text,\n unfurl_links: false,\n unfurl_media: false,\n });\n\n this.logger?.debug(\"Slack API: chat.postMessage response\", {\n messageId: result.ts,\n ok: result.ok,\n });\n\n return {\n id: result.ts as string,\n threadId,\n raw: result,\n };\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n /**\n * Extract card element from a AdapterPostableMessage if present.\n */\n private extractCard(\n message: AdapterPostableMessage,\n ): import(\"chat\").CardElement | null {\n if (isCardElement(message)) {\n return message;\n }\n if (typeof message === \"object\" && message !== null && \"card\" in message) {\n return message.card;\n }\n return null;\n }\n\n /**\n * Extract files from a AdapterPostableMessage if present.\n */\n private extractFiles(message: AdapterPostableMessage): FileUpload[] {\n if (typeof message === \"object\" && message !== null && \"files\" in message) {\n return (message as { files?: FileUpload[] }).files ?? [];\n }\n return [];\n }\n\n /**\n * Upload files to Slack and share them to a channel.\n * Returns the file IDs of uploaded files.\n */\n private async uploadFiles(\n files: FileUpload[],\n channel: string,\n threadTs?: string,\n ): Promise<string[]> {\n const fileIds: string[] = [];\n\n for (const file of files) {\n try {\n // Convert data to Buffer if needed\n let fileBuffer: Buffer;\n if (Buffer.isBuffer(file.data)) {\n fileBuffer = file.data;\n } else if (file.data instanceof ArrayBuffer) {\n fileBuffer = Buffer.from(file.data);\n } else if (file.data instanceof Blob) {\n // Convert Blob to Buffer\n const arrayBuffer = await file.data.arrayBuffer();\n fileBuffer = Buffer.from(arrayBuffer);\n } else {\n throw new Error(\"Unsupported file data type\");\n }\n\n this.logger?.debug(\"Slack API: files.uploadV2\", {\n filename: file.filename,\n size: fileBuffer.length,\n mimeType: file.mimeType,\n });\n\n // biome-ignore lint/suspicious/noExplicitAny: Slack API types don't match actual usage\n const uploadArgs: any = {\n channel_id: channel,\n filename: file.filename,\n file: fileBuffer,\n };\n if (threadTs) {\n uploadArgs.thread_ts = threadTs;\n }\n\n const result = (await this.client.files.uploadV2(uploadArgs)) as {\n ok: boolean;\n files?: Array<{ id?: string }>;\n };\n\n this.logger?.debug(\"Slack API: files.uploadV2 response\", {\n ok: result.ok,\n });\n\n // Extract file IDs from the response\n if (result.files && Array.isArray(result.files)) {\n for (const uploadedFile of result.files) {\n if (uploadedFile.id) {\n fileIds.push(uploadedFile.id);\n }\n }\n }\n } catch (error) {\n this.logger?.error(\"Failed to upload file\", {\n filename: file.filename,\n error,\n });\n throw error;\n }\n }\n\n return fileIds;\n }\n\n async editMessage(\n threadId: string,\n messageId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<unknown>> {\n const { channel } = this.decodeThreadId(threadId);\n\n try {\n // Check if message contains a card\n const card = this.extractCard(message);\n\n if (card) {\n // Render card as Block Kit\n const blocks = cardToBlockKit(card);\n const fallbackText = cardToFallbackText(card);\n\n this.logger?.debug(\"Slack API: chat.update (blocks)\", {\n channel,\n messageId,\n blockCount: blocks.length,\n });\n\n const result = await this.client.chat.update({\n channel,\n ts: messageId,\n text: fallbackText,\n blocks,\n });\n\n this.logger?.debug(\"Slack API: chat.update response\", {\n messageId: result.ts,\n ok: result.ok,\n });\n\n return {\n id: result.ts as string,\n threadId,\n raw: result,\n };\n }\n\n // Regular text message\n const text = convertEmojiPlaceholders(\n this.formatConverter.renderPostable(message),\n \"slack\",\n );\n\n this.logger?.debug(\"Slack API: chat.update\", {\n channel,\n messageId,\n textLength: text.length,\n });\n\n const result = await this.client.chat.update({\n channel,\n ts: messageId,\n text,\n });\n\n this.logger?.debug(\"Slack API: chat.update response\", {\n messageId: result.ts,\n ok: result.ok,\n });\n\n return {\n id: result.ts as string,\n threadId,\n raw: result,\n };\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n async deleteMessage(threadId: string, messageId: string): Promise<void> {\n const { channel } = this.decodeThreadId(threadId);\n\n try {\n this.logger?.debug(\"Slack API: chat.delete\", { channel, messageId });\n\n await this.client.chat.delete({\n channel,\n ts: messageId,\n });\n\n this.logger?.debug(\"Slack API: chat.delete response\", { ok: true });\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n async addReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string,\n ): Promise<void> {\n const { channel } = this.decodeThreadId(threadId);\n // Convert emoji (EmojiValue or string) to Slack format, strip colons\n const slackEmoji = defaultEmojiResolver.toSlack(emoji);\n const name = slackEmoji.replace(/:/g, \"\");\n\n try {\n this.logger?.debug(\"Slack API: reactions.add\", {\n channel,\n messageId,\n emoji: name,\n });\n\n await this.client.reactions.add({\n channel,\n timestamp: messageId,\n name,\n });\n\n this.logger?.debug(\"Slack API: reactions.add response\", { ok: true });\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n async removeReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string,\n ): Promise<void> {\n const { channel } = this.decodeThreadId(threadId);\n // Convert emoji (EmojiValue or string) to Slack format, strip colons\n const slackEmoji = defaultEmojiResolver.toSlack(emoji);\n const name = slackEmoji.replace(/:/g, \"\");\n\n try {\n this.logger?.debug(\"Slack API: reactions.remove\", {\n channel,\n messageId,\n emoji: name,\n });\n\n await this.client.reactions.remove({\n channel,\n timestamp: messageId,\n name,\n });\n\n this.logger?.debug(\"Slack API: reactions.remove response\", { ok: true });\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n async startTyping(_threadId: string): Promise<void> {\n // Slack doesn't have a direct typing indicator API for bots\n }\n\n /**\n * Stream a message using Slack's native streaming API.\n *\n * Consumes an async iterable of text chunks and streams them to Slack.\n * Requires `recipientUserId` and `recipientTeamId` in options.\n */\n async stream(\n threadId: string,\n textStream: AsyncIterable<string>,\n options?: StreamOptions,\n ): Promise<RawMessage<unknown>> {\n if (!options?.recipientUserId || !options?.recipientTeamId) {\n throw new ChatError(\n \"Slack streaming requires recipientUserId and recipientTeamId in options\",\n \"MISSING_STREAM_OPTIONS\",\n );\n }\n const { channel, threadTs } = this.decodeThreadId(threadId);\n this.logger?.debug(\"Slack: starting stream\", { channel, threadTs });\n\n const streamer = this.client.chatStream({\n channel,\n thread_ts: threadTs,\n recipient_user_id: options.recipientUserId,\n recipient_team_id: options.recipientTeamId,\n });\n\n for await (const chunk of textStream) {\n await streamer.append({ markdown_text: chunk });\n }\n const result = await streamer.stop();\n const messageTs = (result.message?.ts ?? result.ts) as string;\n\n this.logger?.debug(\"Slack: stream complete\", { messageId: messageTs });\n\n return {\n id: messageTs,\n threadId,\n raw: result,\n };\n }\n\n /**\n * Open a direct message conversation with a user.\n * Returns a thread ID that can be used to post messages.\n */\n async openDM(userId: string): Promise<string> {\n try {\n this.logger?.debug(\"Slack API: conversations.open\", { userId });\n\n const result = await this.client.conversations.open({ users: userId });\n\n if (!result.channel?.id) {\n throw new Error(\"Failed to open DM - no channel returned\");\n }\n\n const channelId = result.channel.id;\n\n this.logger?.debug(\"Slack API: conversations.open response\", {\n channelId,\n ok: result.ok,\n });\n\n // Encode as thread ID (no threadTs for new DM - messages will start new threads)\n return this.encodeThreadId({\n channel: channelId,\n threadTs: \"\", // Empty threadTs indicates top-level channel messages\n });\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n async fetchMessages(\n threadId: string,\n options: FetchOptions = {},\n ): Promise<Message<unknown>[]> {\n const { channel, threadTs } = this.decodeThreadId(threadId);\n\n try {\n this.logger?.debug(\"Slack API: conversations.replies\", {\n channel,\n threadTs,\n limit: options.limit || 100,\n });\n\n const result = await this.client.conversations.replies({\n channel,\n ts: threadTs,\n limit: options.limit || 100,\n cursor: options.before,\n });\n\n const messages = (result.messages || []) as SlackEvent[];\n\n this.logger?.debug(\"Slack API: conversations.replies response\", {\n messageCount: messages.length,\n ok: result.ok,\n });\n\n // Use sync version to avoid N API calls for user lookup\n return messages.map((msg) => this.parseSlackMessageSync(msg, threadId));\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n const { channel, threadTs } = this.decodeThreadId(threadId);\n\n try {\n this.logger?.debug(\"Slack API: conversations.info\", { channel });\n\n const result = await this.client.conversations.info({ channel });\n const channelInfo = result.channel as { name?: string } | undefined;\n\n this.logger?.debug(\"Slack API: conversations.info response\", {\n channelName: channelInfo?.name,\n ok: result.ok,\n });\n\n return {\n id: threadId,\n channelId: channel,\n channelName: channelInfo?.name,\n metadata: {\n threadTs,\n channel: result.channel,\n },\n };\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n encodeThreadId(platformData: SlackThreadId): string {\n return `slack:${platformData.channel}:${platformData.threadTs}`;\n }\n\n /**\n * Check if a thread is a direct message conversation.\n * Slack DM channel IDs start with 'D'.\n */\n isDM(threadId: string): boolean {\n const { channel } = this.decodeThreadId(threadId);\n return channel.startsWith(\"D\");\n }\n\n decodeThreadId(threadId: string): SlackThreadId {\n const parts = threadId.split(\":\");\n if (parts.length !== 3 || parts[0] !== \"slack\") {\n throw new Error(`Invalid Slack thread ID: ${threadId}`);\n }\n return {\n channel: parts[1] as string,\n threadTs: parts[2] as string,\n };\n }\n\n parseMessage(raw: SlackEvent): Message<unknown> {\n const event = raw;\n const threadTs = event.thread_ts || event.ts || \"\";\n const threadId = this.encodeThreadId({\n channel: event.channel || \"\",\n threadTs,\n });\n // Use synchronous version without user lookup for interface compliance\n return this.parseSlackMessageSync(event, threadId);\n }\n\n /**\n * Synchronous message parsing without user lookup.\n * Used for parseMessage interface - falls back to user ID for username.\n */\n private parseSlackMessageSync(\n event: SlackEvent,\n threadId: string,\n ): Message<unknown> {\n const isMe = this.isMessageFromSelf(event);\n\n const text = event.text || \"\";\n // Without async lookup, fall back to user ID for human users\n const userName = event.username || event.user || \"unknown\";\n const fullName = event.username || event.user || \"unknown\";\n\n return {\n id: event.ts || \"\",\n threadId,\n text: this.formatConverter.extractPlainText(text),\n formatted: this.formatConverter.toAst(text),\n raw: event,\n author: {\n userId: event.user || event.bot_id || \"unknown\",\n userName,\n fullName,\n isBot: !!event.bot_id,\n isMe,\n },\n metadata: {\n dateSent: new Date(parseFloat(event.ts || \"0\") * 1000),\n edited: !!event.edited,\n editedAt: event.edited\n ? new Date(parseFloat(event.edited.ts) * 1000)\n : undefined,\n },\n attachments: (event.files || []).map((file) =>\n this.createAttachment(file),\n ),\n };\n }\n\n renderFormatted(content: FormattedContent): string {\n return this.formatConverter.fromAst(content);\n }\n\n /**\n * Check if a Slack event is from this bot.\n *\n * Slack messages can come from:\n * - User messages: have `user` field (U_xxx format)\n * - Bot messages: have `bot_id` field (B_xxx format)\n *\n * We check both because:\n * - _botUserId is the user ID (U_xxx) - matches event.user\n * - _botId is the bot ID (B_xxx) - matches event.bot_id\n */\n private isMessageFromSelf(event: SlackEvent): boolean {\n // Primary check: user ID match (for messages sent as the bot user)\n if (this._botUserId && event.user === this._botUserId) {\n return true;\n }\n\n // Secondary check: bot ID match (for bot_message subtypes)\n if (this._botId && event.bot_id === this._botId) {\n return true;\n }\n\n return false;\n }\n\n private handleSlackError(error: unknown): never {\n const slackError = error as { data?: { error?: string }; code?: string };\n\n if (slackError.code === \"slack_webapi_platform_error\") {\n if (slackError.data?.error === \"ratelimited\") {\n throw new RateLimitError(\"Slack rate limit exceeded\", undefined, error);\n }\n }\n\n throw error;\n }\n}\n\nexport function createSlackAdapter(config: SlackAdapterConfig): SlackAdapter {\n return new SlackAdapter(config);\n}\n\n// Re-export card converter for advanced use\nexport { cardToBlockKit, cardToFallbackText } from \"./cards\";\n// Re-export format converter for advanced use\nexport {\n SlackFormatConverter,\n SlackFormatConverter as SlackMarkdownConverter,\n} from \"./markdown\";\n","/**\n * Slack Block Kit converter for cross-platform cards.\n *\n * Converts CardElement to Slack Block Kit blocks.\n * @see https://api.slack.com/block-kit\n */\n\nimport {\n type ActionsElement,\n type ButtonElement,\n type CardChild,\n type CardElement,\n convertEmojiPlaceholders,\n type DividerElement,\n type FieldsElement,\n type ImageElement,\n type SectionElement,\n type TextElement,\n} from \"chat\";\n\n/**\n * Convert emoji placeholders in text to Slack format.\n */\nfunction convertEmoji(text: string): string {\n return convertEmojiPlaceholders(text, \"slack\");\n}\n\n// Slack Block Kit types (simplified)\nexport interface SlackBlock {\n type: string;\n block_id?: string;\n [key: string]: unknown;\n}\n\ninterface SlackTextObject {\n type: \"plain_text\" | \"mrkdwn\";\n text: string;\n emoji?: boolean;\n}\n\ninterface SlackButtonElement {\n type: \"button\";\n text: SlackTextObject;\n action_id: string;\n value?: string;\n style?: \"primary\" | \"danger\";\n}\n\n/**\n * Convert a CardElement to Slack Block Kit blocks.\n */\nexport function cardToBlockKit(card: CardElement): SlackBlock[] {\n const blocks: SlackBlock[] = [];\n\n // Add header if title is present\n if (card.title) {\n blocks.push({\n type: \"header\",\n text: {\n type: \"plain_text\",\n text: convertEmoji(card.title),\n emoji: true,\n },\n });\n }\n\n // Add subtitle as context if present\n if (card.subtitle) {\n blocks.push({\n type: \"context\",\n elements: [\n {\n type: \"mrkdwn\",\n text: convertEmoji(card.subtitle),\n },\n ],\n });\n }\n\n // Add header image if present\n if (card.imageUrl) {\n blocks.push({\n type: \"image\",\n image_url: card.imageUrl,\n alt_text: card.title || \"Card image\",\n });\n }\n\n // Convert children\n for (const child of card.children) {\n const childBlocks = convertChildToBlocks(child);\n blocks.push(...childBlocks);\n }\n\n return blocks;\n}\n\n/**\n * Convert a card child element to Slack blocks.\n */\nfunction convertChildToBlocks(child: CardChild): SlackBlock[] {\n switch (child.type) {\n case \"text\":\n return [convertTextToBlock(child)];\n case \"image\":\n return [convertImageToBlock(child)];\n case \"divider\":\n return [convertDividerToBlock(child)];\n case \"actions\":\n return [convertActionsToBlock(child)];\n case \"section\":\n return convertSectionToBlocks(child);\n case \"fields\":\n return [convertFieldsToBlock(child)];\n default:\n return [];\n }\n}\n\nfunction convertTextToBlock(element: TextElement): SlackBlock {\n const text = convertEmoji(element.content);\n let formattedText = text;\n\n // Apply style\n if (element.style === \"bold\") {\n formattedText = `*${text}*`;\n } else if (element.style === \"muted\") {\n // Slack doesn't have a muted style, use context block\n return {\n type: \"context\",\n elements: [{ type: \"mrkdwn\", text }],\n };\n }\n\n return {\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: formattedText,\n },\n };\n}\n\nfunction convertImageToBlock(element: ImageElement): SlackBlock {\n return {\n type: \"image\",\n image_url: element.url,\n alt_text: element.alt || \"Image\",\n };\n}\n\nfunction convertDividerToBlock(_element: DividerElement): SlackBlock {\n return { type: \"divider\" };\n}\n\nfunction convertActionsToBlock(element: ActionsElement): SlackBlock {\n const elements: SlackButtonElement[] = element.children.map((button) =>\n convertButtonToElement(button),\n );\n\n return {\n type: \"actions\",\n elements,\n };\n}\n\nfunction convertButtonToElement(button: ButtonElement): SlackButtonElement {\n const element: SlackButtonElement = {\n type: \"button\",\n text: {\n type: \"plain_text\",\n text: convertEmoji(button.label),\n emoji: true,\n },\n action_id: button.id,\n };\n\n if (button.value) {\n element.value = button.value;\n }\n\n if (button.style === \"primary\") {\n element.style = \"primary\";\n } else if (button.style === \"danger\") {\n element.style = \"danger\";\n }\n\n return element;\n}\n\nfunction convertSectionToBlocks(element: SectionElement): SlackBlock[] {\n // Flatten section children into blocks\n const blocks: SlackBlock[] = [];\n for (const child of element.children) {\n blocks.push(...convertChildToBlocks(child));\n }\n return blocks;\n}\n\nfunction convertFieldsToBlock(element: FieldsElement): SlackBlock {\n const fields: SlackTextObject[] = [];\n\n for (const field of element.children) {\n // Add label and value as separate field items\n fields.push({\n type: \"mrkdwn\",\n text: `*${convertEmoji(field.label)}*\\n${convertEmoji(field.value)}`,\n });\n }\n\n return {\n type: \"section\",\n fields,\n };\n}\n\n/**\n * Generate fallback text from a card element.\n * Used when blocks aren't supported or for notifications.\n */\nexport function cardToFallbackText(card: CardElement): string {\n const parts: string[] = [];\n\n if (card.title) {\n parts.push(`*${convertEmoji(card.title)}*`);\n }\n\n if (card.subtitle) {\n parts.push(convertEmoji(card.subtitle));\n }\n\n for (const child of card.children) {\n const text = childToFallbackText(child);\n if (text) {\n parts.push(text);\n }\n }\n\n return parts.join(\"\\n\");\n}\n\nfunction childToFallbackText(child: CardChild): string | null {\n switch (child.type) {\n case \"text\":\n return convertEmoji(child.content);\n case \"fields\":\n return child.children\n .map((f) => `${convertEmoji(f.label)}: ${convertEmoji(f.value)}`)\n .join(\"\\n\");\n case \"actions\":\n return `[${child.children.map((b) => convertEmoji(b.label)).join(\"] [\")}]`;\n case \"section\":\n return child.children\n .map((c) => childToFallbackText(c))\n .filter(Boolean)\n .join(\"\\n\");\n default:\n return null;\n }\n}\n","/**\n * Slack-specific format conversion using AST-based parsing.\n *\n * Slack uses \"mrkdwn\" format which is similar but not identical to markdown:\n * - Bold: *text* (not **text**)\n * - Italic: _text_ (same)\n * - Strikethrough: ~text~ (not ~~text~~)\n * - Links: <url|text> (not [text](url))\n * - User mentions: <@U123>\n * - Channel mentions: <#C123|name>\n */\n\nimport {\n type AdapterPostableMessage,\n BaseFormatConverter,\n type Code,\n type Content,\n type Delete,\n type Emphasis,\n type InlineCode,\n type Link,\n type Paragraph,\n parseMarkdown,\n type Root,\n type Strong,\n type Text,\n} from \"chat\";\n\nexport class SlackFormatConverter extends BaseFormatConverter {\n /**\n * Convert @mentions to Slack format in plain text.\n * @name → <@name>\n */\n private convertMentionsToSlack(text: string): string {\n return text.replace(/@(\\w+)/g, \"<@$1>\");\n }\n\n /**\n * Override renderPostable to convert @mentions in plain strings.\n */\n override renderPostable(message: AdapterPostableMessage): string {\n if (typeof message === \"string\") {\n return this.convertMentionsToSlack(message);\n }\n if (\"raw\" in message) {\n return this.convertMentionsToSlack(message.raw);\n }\n if (\"markdown\" in message) {\n return this.fromAst(parseMarkdown(message.markdown));\n }\n if (\"ast\" in message) {\n return this.fromAst(message.ast);\n }\n return \"\";\n }\n\n /**\n * Render an AST to Slack mrkdwn format.\n */\n fromAst(ast: Root): string {\n const parts: string[] = [];\n\n for (const node of ast.children) {\n parts.push(this.nodeToMrkdwn(node as Content));\n }\n\n return parts.join(\"\\n\\n\");\n }\n\n /**\n * Parse Slack mrkdwn into an AST.\n */\n toAst(mrkdwn: string): Root {\n // Convert Slack mrkdwn to standard markdown string, then parse\n let markdown = mrkdwn;\n\n // User mentions: <@U123|name> -> @name or <@U123> -> @U123\n markdown = markdown.replace(/<@([^|>]+)\\|([^>]+)>/g, \"@$2\");\n markdown = markdown.replace(/<@([^>]+)>/g, \"@$1\");\n\n // Channel mentions: <#C123|name> -> #name\n markdown = markdown.replace(/<#[^|>]+\\|([^>]+)>/g, \"#$1\");\n markdown = markdown.replace(/<#([^>]+)>/g, \"#$1\");\n\n // Links: <url|text> -> [text](url)\n markdown = markdown.replace(/<(https?:\\/\\/[^|>]+)\\|([^>]+)>/g, \"[$2]($1)\");\n\n // Bare links: <url> -> url\n markdown = markdown.replace(/<(https?:\\/\\/[^>]+)>/g, \"$1\");\n\n // Bold: *text* -> **text** (but be careful with emphasis)\n // This is tricky because Slack uses * for bold, not emphasis\n markdown = markdown.replace(/(?<![_*\\\\])\\*([^*\\n]+)\\*(?![_*])/g, \"**$1**\");\n\n // Strikethrough: ~text~ -> ~~text~~\n markdown = markdown.replace(/(?<!~)~([^~\\n]+)~(?!~)/g, \"~~$1~~\");\n\n return parseMarkdown(markdown);\n }\n\n private nodeToMrkdwn(node: Content): string {\n switch (node.type) {\n case \"paragraph\":\n return (node as Paragraph).children\n .map((child) => this.nodeToMrkdwn(child as Content))\n .join(\"\");\n\n case \"text\": {\n // Convert @mentions to Slack format <@mention>\n const textValue = (node as Text).value;\n return textValue.replace(/@(\\w+)/g, \"<@$1>\");\n }\n\n case \"strong\":\n // Markdown **text** -> Slack *text*\n return `*${(node as Strong).children\n .map((child) => this.nodeToMrkdwn(child as Content))\n .join(\"\")}*`;\n\n case \"emphasis\":\n // Both use _text_\n return `_${(node as Emphasis).children\n .map((child) => this.nodeToMrkdwn(child as Content))\n .join(\"\")}_`;\n\n case \"delete\":\n // Markdown ~~text~~ -> Slack ~text~\n return `~${(node as Delete).children\n .map((child) => this.nodeToMrkdwn(child as Content))\n .join(\"\")}~`;\n\n case \"inlineCode\":\n return `\\`${(node as InlineCode).value}\\``;\n\n case \"code\": {\n const codeNode = node as Code;\n return `\\`\\`\\`${codeNode.lang || \"\"}\\n${codeNode.value}\\n\\`\\`\\``;\n }\n\n case \"link\": {\n const linkNode = node as Link;\n const linkText = linkNode.children\n .map((child) => this.nodeToMrkdwn(child as Content))\n .join(\"\");\n // Markdown [text](url) -> Slack <url|text>\n return `<${linkNode.url}|${linkText}>`;\n }\n\n case \"blockquote\":\n return node.children\n .map((child) => `> ${this.nodeToMrkdwn(child as Content)}`)\n .join(\"\\n\");\n\n case \"list\":\n return node.children\n .map((item, i) => {\n const prefix = node.ordered ? `${i + 1}.` : \"•\";\n const content = item.children\n .map((child) => this.nodeToMrkdwn(child as Content))\n .join(\"\");\n return `${prefix} ${content}`;\n })\n .join(\"\\n\");\n\n case \"listItem\":\n return node.children\n .map((child) => this.nodeToMrkdwn(child as Content))\n .join(\"\");\n\n case \"break\":\n return \"\\n\";\n\n case \"thematicBreak\":\n return \"---\";\n\n default:\n // For unsupported nodes, try to extract text\n if (\"children\" in node && Array.isArray(node.children)) {\n return node.children\n .map((child) => this.nodeToMrkdwn(child as Content))\n .join(\"\");\n }\n if (\"value\" in node) {\n return String(node.value);\n }\n return \"\";\n }\n }\n}\n\n// Backwards compatibility alias\nexport { SlackFormatConverter as SlackMarkdownConverter };\n"],"mappings":"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","names":["convertEmojiPlaceholders","card","result","convertEmojiPlaceholders"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/cards.ts","../src/markdown.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport {\n AdapterRateLimitError,\n extractCard,\n extractFiles,\n NetworkError,\n toBuffer,\n ValidationError,\n} from \"@chat-adapter/shared\";\nimport { WebClient } from \"@slack/web-api\";\nimport type {\n ActionEvent,\n Adapter,\n AdapterPostableMessage,\n Attachment,\n ChatInstance,\n EmojiValue,\n FetchOptions,\n FetchResult,\n FileUpload,\n FormattedContent,\n Logger,\n Message,\n RawMessage,\n ReactionEvent,\n StreamOptions,\n ThreadInfo,\n WebhookOptions,\n} from \"chat\";\n\nimport {\n ChatError,\n convertEmojiPlaceholders,\n defaultEmojiResolver,\n} from \"chat\";\nimport { cardToBlockKit, cardToFallbackText } from \"./cards\";\nimport { SlackFormatConverter } from \"./markdown\";\n\nexport interface SlackAdapterConfig {\n /** Bot token (xoxb-...) */\n botToken: string;\n /** Signing secret for webhook verification */\n signingSecret: string;\n /** Logger instance for error reporting */\n logger: Logger;\n /** Override bot username (optional) */\n userName?: string;\n /** Bot user ID (will be fetched if not provided) */\n botUserId?: string;\n}\n\n/** Slack-specific thread ID data */\nexport interface SlackThreadId {\n channel: string;\n threadTs: string;\n}\n\n/** Slack event payload (raw message format) */\nexport interface SlackEvent {\n type: string;\n user?: string;\n bot_id?: string;\n channel?: string;\n text?: string;\n ts?: string;\n thread_ts?: string;\n subtype?: string;\n username?: string;\n edited?: { ts: string };\n /** Channel type: \"channel\", \"group\", \"mpim\", or \"im\" (DM) */\n channel_type?: string;\n files?: Array<{\n id?: string;\n mimetype?: string;\n url_private?: string;\n name?: string;\n size?: number;\n original_w?: number;\n original_h?: number;\n }>;\n}\n\n/** Slack reaction event payload */\nexport interface SlackReactionEvent {\n type: \"reaction_added\" | \"reaction_removed\";\n user: string;\n reaction: string;\n item_user?: string;\n item: {\n type: string;\n channel: string;\n ts: string;\n };\n event_ts: string;\n}\n\n/** Slack webhook payload envelope */\ninterface SlackWebhookPayload {\n type: string;\n challenge?: string;\n event?: SlackEvent | SlackReactionEvent;\n event_id?: string;\n event_time?: number;\n}\n\n/** Slack interactive payload (block_actions) for button clicks */\ninterface SlackBlockActionsPayload {\n type: \"block_actions\";\n user: {\n id: string;\n username: string;\n name?: string;\n };\n container: {\n type: string;\n message_ts: string;\n channel_id: string;\n is_ephemeral?: boolean;\n };\n channel: {\n id: string;\n name: string;\n };\n message: {\n ts: string;\n thread_ts?: string;\n };\n actions: Array<{\n type: string;\n action_id: string;\n block_id?: string;\n value?: string;\n action_ts?: string;\n }>;\n response_url?: string;\n}\n\n/** Cached user info */\ninterface CachedUser {\n displayName: string;\n realName: string;\n}\n\nexport class SlackAdapter implements Adapter<SlackThreadId, unknown> {\n readonly name = \"slack\";\n readonly userName: string;\n\n private client: WebClient;\n private signingSecret: string;\n private botToken: string;\n private chat: ChatInstance | null = null;\n private logger: Logger;\n private _botUserId: string | null = null;\n private _botId: string | null = null; // Bot app ID (B_xxx) - different from user ID\n private formatConverter = new SlackFormatConverter();\n private static USER_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\n\n /** Bot user ID (e.g., U_BOT_123) used for mention detection */\n get botUserId(): string | undefined {\n return this._botUserId || undefined;\n }\n\n constructor(config: SlackAdapterConfig) {\n this.client = new WebClient(config.botToken);\n this.signingSecret = config.signingSecret;\n this.botToken = config.botToken;\n this.logger = config.logger;\n this.userName = config.userName || \"bot\";\n this._botUserId = config.botUserId || null;\n }\n\n async initialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n\n // Fetch bot user ID and bot ID if not provided\n if (!this._botUserId) {\n try {\n const authResult = await this.client.auth.test();\n this._botUserId = authResult.user_id as string;\n this._botId = (authResult.bot_id as string) || null;\n if (authResult.user) {\n (this as { userName: string }).userName = authResult.user as string;\n }\n this.logger.info(\"Slack auth completed\", {\n botUserId: this._botUserId,\n botId: this._botId,\n });\n } catch (error) {\n this.logger.warn(\"Could not fetch bot user ID\", { error });\n }\n }\n }\n\n /**\n * Look up user info from Slack API with caching via state adapter.\n * Returns display name and real name, or falls back to user ID.\n */\n private async lookupUser(\n userId: string,\n ): Promise<{ displayName: string; realName: string }> {\n const cacheKey = `slack:user:${userId}`;\n\n // Check cache first (via state adapter for serverless compatibility)\n if (this.chat) {\n const cached = await this.chat.getState().get<CachedUser>(cacheKey);\n if (cached) {\n return { displayName: cached.displayName, realName: cached.realName };\n }\n }\n\n try {\n const result = await this.client.users.info({ user: userId });\n const user = result.user as {\n name?: string;\n real_name?: string;\n profile?: { display_name?: string; real_name?: string };\n };\n\n // Slack user naming: profile.display_name > profile.real_name > real_name > name > userId\n const displayName =\n user?.profile?.display_name ||\n user?.profile?.real_name ||\n user?.real_name ||\n user?.name ||\n userId;\n const realName =\n user?.real_name || user?.profile?.real_name || displayName;\n\n // Cache the result via state adapter\n if (this.chat) {\n await this.chat\n .getState()\n .set<CachedUser>(\n cacheKey,\n { displayName, realName },\n SlackAdapter.USER_CACHE_TTL_MS,\n );\n }\n\n this.logger.debug(\"Fetched user info\", {\n userId,\n displayName,\n realName,\n });\n return { displayName, realName };\n } catch (error) {\n this.logger.warn(\"Could not fetch user info\", { userId, error });\n // Fall back to user ID\n return { displayName: userId, realName: userId };\n }\n }\n\n async handleWebhook(\n request: Request,\n options?: WebhookOptions,\n ): Promise<Response> {\n const body = await request.text();\n this.logger.debug(\"Slack webhook raw body\", { body });\n\n // Verify request signature\n const timestamp = request.headers.get(\"x-slack-request-timestamp\");\n const signature = request.headers.get(\"x-slack-signature\");\n\n if (!this.verifySignature(body, timestamp, signature)) {\n return new Response(\"Invalid signature\", { status: 401 });\n }\n\n // Check if this is a form-urlencoded interactive payload\n const contentType = request.headers.get(\"content-type\") || \"\";\n if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n return this.handleInteractivePayload(body, options);\n }\n\n // Parse the JSON payload\n let payload: SlackWebhookPayload;\n try {\n payload = JSON.parse(body);\n } catch {\n return new Response(\"Invalid JSON\", { status: 400 });\n }\n\n // Handle URL verification challenge\n if (payload.type === \"url_verification\" && payload.challenge) {\n return new Response(JSON.stringify({ challenge: payload.challenge }), {\n headers: { \"Content-Type\": \"application/json\" },\n });\n }\n\n // Handle events\n if (payload.type === \"event_callback\" && payload.event) {\n // Respond immediately to avoid timeout\n const event = payload.event;\n\n // Process event asynchronously\n if (event.type === \"message\" || event.type === \"app_mention\") {\n this.handleMessageEvent(event as SlackEvent, options);\n } else if (\n event.type === \"reaction_added\" ||\n event.type === \"reaction_removed\"\n ) {\n this.handleReactionEvent(event as SlackReactionEvent, options);\n }\n }\n\n return new Response(\"ok\", { status: 200 });\n }\n\n /**\n * Handle Slack interactive payloads (button clicks, etc.).\n * These are sent as form-urlencoded with a `payload` JSON field.\n */\n private handleInteractivePayload(\n body: string,\n options?: WebhookOptions,\n ): Response {\n // Parse form-urlencoded body\n const params = new URLSearchParams(body);\n const payloadStr = params.get(\"payload\");\n\n if (!payloadStr) {\n return new Response(\"Missing payload\", { status: 400 });\n }\n\n let payload: SlackBlockActionsPayload;\n try {\n payload = JSON.parse(payloadStr);\n } catch {\n return new Response(\"Invalid payload JSON\", { status: 400 });\n }\n\n // Handle block_actions (button clicks)\n if (payload.type === \"block_actions\") {\n this.handleBlockActions(payload, options);\n }\n\n // Respond immediately - Slack requires fast responses for interactions\n return new Response(\"\", { status: 200 });\n }\n\n /**\n * Handle block_actions payload (button clicks in Block Kit).\n */\n private handleBlockActions(\n payload: SlackBlockActionsPayload,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n this.logger.warn(\"Chat instance not initialized, ignoring action\");\n return;\n }\n\n const channel = payload.channel?.id || payload.container?.channel_id;\n const messageTs = payload.message?.ts || payload.container?.message_ts;\n const threadTs = payload.message?.thread_ts || messageTs;\n\n if (!channel || !messageTs) {\n this.logger.warn(\"Missing channel or message_ts in block_actions\", {\n channel,\n messageTs,\n });\n return;\n }\n\n const threadId = this.encodeThreadId({\n channel,\n threadTs: threadTs || messageTs,\n });\n\n // Process each action (usually just one, but can be multiple)\n for (const action of payload.actions) {\n const actionEvent: Omit<ActionEvent, \"thread\"> & {\n adapter: SlackAdapter;\n } = {\n actionId: action.action_id,\n value: action.value,\n user: {\n userId: payload.user.id,\n userName: payload.user.username || payload.user.name || \"unknown\",\n fullName: payload.user.name || payload.user.username || \"unknown\",\n isBot: false,\n isMe: false,\n },\n messageId: messageTs,\n threadId,\n adapter: this,\n raw: payload,\n };\n\n this.logger.debug(\"Processing Slack block action\", {\n actionId: action.action_id,\n value: action.value,\n messageId: messageTs,\n threadId,\n });\n\n this.chat.processAction(actionEvent, options);\n }\n }\n\n private verifySignature(\n body: string,\n timestamp: string | null,\n signature: string | null,\n ): boolean {\n if (!timestamp || !signature) {\n return false;\n }\n\n // Check timestamp is recent (within 5 minutes)\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - parseInt(timestamp, 10)) > 300) {\n return false;\n }\n\n // Compute expected signature\n const sigBasestring = `v0:${timestamp}:${body}`;\n const expectedSignature =\n \"v0=\" +\n createHmac(\"sha256\", this.signingSecret)\n .update(sigBasestring)\n .digest(\"hex\");\n\n // Compare signatures using timing-safe comparison\n try {\n return timingSafeEqual(\n Buffer.from(signature),\n Buffer.from(expectedSignature),\n );\n } catch {\n return false;\n }\n }\n\n /**\n * Handle message events from Slack.\n * Bot message filtering (isMe) is handled centrally by the Chat class.\n */\n private handleMessageEvent(\n event: SlackEvent,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n this.logger.warn(\"Chat instance not initialized, ignoring event\");\n return;\n }\n\n // Skip message subtypes we don't handle (edits, deletes, etc.)\n // Note: bot_message subtype is allowed through - Chat class filters via isMe\n if (event.subtype && event.subtype !== \"bot_message\") {\n this.logger.debug(\"Ignoring message subtype\", {\n subtype: event.subtype,\n });\n return;\n }\n\n if (!event.channel || !event.ts) {\n this.logger.debug(\"Ignoring event without channel or ts\", {\n channel: event.channel,\n ts: event.ts,\n });\n return;\n }\n\n // For DMs (channel_type: \"im\"), use empty threadTs so all messages in the DM\n // match the DM subscription created by openDM(). This treats the entire DM\n // conversation as a single \"thread\" for subscription purposes.\n const isDM = event.channel_type === \"im\";\n const threadTs = isDM ? \"\" : event.thread_ts || event.ts;\n const threadId = this.encodeThreadId({\n channel: event.channel,\n threadTs,\n });\n\n // Let Chat class handle async processing, waitUntil, and isMe filtering\n // Use factory function since parseSlackMessage is async (user lookup)\n this.chat.processMessage(\n this,\n threadId,\n () => this.parseSlackMessage(event, threadId),\n options,\n );\n }\n\n /**\n * Handle reaction events from Slack (reaction_added, reaction_removed).\n */\n private handleReactionEvent(\n event: SlackReactionEvent,\n options?: WebhookOptions,\n ): void {\n if (!this.chat) {\n this.logger.warn(\"Chat instance not initialized, ignoring reaction\");\n return;\n }\n\n // Only handle reactions to messages (not files, etc.)\n if (event.item.type !== \"message\") {\n this.logger.debug(\"Ignoring reaction to non-message item\", {\n itemType: event.item.type,\n });\n return;\n }\n\n // Build thread ID from the reacted message\n const threadId = this.encodeThreadId({\n channel: event.item.channel,\n threadTs: event.item.ts,\n });\n\n // Message ID is just the timestamp (Slack uses ts as message ID)\n const messageId = event.item.ts;\n\n // Normalize emoji\n const rawEmoji = event.reaction;\n const normalizedEmoji = defaultEmojiResolver.fromSlack(rawEmoji);\n\n // Check if reaction is from this bot\n const isMe =\n (this._botUserId !== null && event.user === this._botUserId) ||\n (this._botId !== null && event.user === this._botId);\n\n // Build reaction event\n const reactionEvent: Omit<ReactionEvent, \"adapter\" | \"thread\"> = {\n emoji: normalizedEmoji,\n rawEmoji,\n added: event.type === \"reaction_added\",\n user: {\n userId: event.user,\n userName: event.user, // Will be resolved below if possible\n fullName: event.user,\n isBot: false, // Users add reactions, not bots typically\n isMe,\n },\n messageId,\n threadId,\n raw: event,\n };\n\n // Process reaction\n this.chat.processReaction({ ...reactionEvent, adapter: this }, options);\n }\n\n private async parseSlackMessage(\n event: SlackEvent,\n threadId: string,\n ): Promise<Message<unknown>> {\n const isMe = this.isMessageFromSelf(event);\n\n const text = event.text || \"\";\n\n // Get user info - for human users we need to look up the display name\n // since Slack events only include the user ID, not the username\n let userName = event.username || \"unknown\";\n let fullName = event.username || \"unknown\";\n\n // If we have a user ID but no username, look up the user info\n if (event.user && !event.username) {\n const userInfo = await this.lookupUser(event.user);\n userName = userInfo.displayName;\n fullName = userInfo.realName;\n }\n\n return {\n id: event.ts || \"\",\n threadId,\n text: this.formatConverter.extractPlainText(text),\n formatted: this.formatConverter.toAst(text),\n raw: event,\n author: {\n userId: event.user || event.bot_id || \"unknown\",\n userName,\n fullName,\n isBot: !!event.bot_id,\n isMe,\n },\n metadata: {\n dateSent: new Date(parseFloat(event.ts || \"0\") * 1000),\n edited: !!event.edited,\n editedAt: event.edited\n ? new Date(parseFloat(event.edited.ts) * 1000)\n : undefined,\n },\n attachments: (event.files || []).map((file) =>\n this.createAttachment(file),\n ),\n };\n }\n\n /**\n * Create an Attachment object from a Slack file.\n * Includes a fetchData method that uses the bot token for auth.\n */\n private createAttachment(file: {\n id?: string;\n mimetype?: string;\n url_private?: string;\n name?: string;\n size?: number;\n original_w?: number;\n original_h?: number;\n }): Attachment {\n const url = file.url_private;\n const botToken = this.botToken;\n\n // Determine type based on mimetype\n let type: Attachment[\"type\"] = \"file\";\n if (file.mimetype?.startsWith(\"image/\")) {\n type = \"image\";\n } else if (file.mimetype?.startsWith(\"video/\")) {\n type = \"video\";\n } else if (file.mimetype?.startsWith(\"audio/\")) {\n type = \"audio\";\n }\n\n return {\n type,\n url,\n name: file.name,\n mimeType: file.mimetype,\n size: file.size,\n width: file.original_w,\n height: file.original_h,\n fetchData: url\n ? async () => {\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${botToken}`,\n },\n });\n if (!response.ok) {\n throw new NetworkError(\n \"slack\",\n `Failed to fetch file: ${response.status} ${response.statusText}`,\n );\n }\n const arrayBuffer = await response.arrayBuffer();\n return Buffer.from(arrayBuffer);\n }\n : undefined,\n };\n }\n\n async postMessage(\n threadId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<unknown>> {\n const { channel, threadTs } = this.decodeThreadId(threadId);\n\n try {\n // Check for files to upload\n const files = extractFiles(message);\n if (files.length > 0) {\n // Upload files first (they're shared to the channel automatically)\n await this.uploadFiles(files, channel, threadTs || undefined);\n\n // If message only has files (no text/card), return early\n const hasText =\n typeof message === \"string\" ||\n (typeof message === \"object\" &&\n message !== null &&\n (\"raw\" in message || \"markdown\" in message || \"ast\" in message));\n const card = extractCard(message);\n\n if (!hasText && !card) {\n // Return a synthetic message ID since files.uploadV2 handles sharing\n return {\n id: `file-${Date.now()}`,\n threadId,\n raw: { files },\n };\n }\n }\n\n // Check if message contains a card\n const card = extractCard(message);\n\n if (card) {\n // Render card as Block Kit\n const blocks = cardToBlockKit(card);\n const fallbackText = cardToFallbackText(card);\n\n this.logger.debug(\"Slack API: chat.postMessage (blocks)\", {\n channel,\n threadTs,\n blockCount: blocks.length,\n });\n\n const result = await this.client.chat.postMessage({\n channel,\n thread_ts: threadTs,\n text: fallbackText, // Fallback for notifications\n blocks,\n unfurl_links: false,\n unfurl_media: false,\n });\n\n this.logger.debug(\"Slack API: chat.postMessage response\", {\n messageId: result.ts,\n ok: result.ok,\n });\n\n return {\n id: result.ts as string,\n threadId,\n raw: result,\n };\n }\n\n // Regular text message\n const text = convertEmojiPlaceholders(\n this.formatConverter.renderPostable(message),\n \"slack\",\n );\n\n this.logger.debug(\"Slack API: chat.postMessage\", {\n channel,\n threadTs,\n textLength: text.length,\n });\n\n const result = await this.client.chat.postMessage({\n channel,\n thread_ts: threadTs,\n text,\n unfurl_links: false,\n unfurl_media: false,\n });\n\n this.logger.debug(\"Slack API: chat.postMessage response\", {\n messageId: result.ts,\n ok: result.ok,\n });\n\n return {\n id: result.ts as string,\n threadId,\n raw: result,\n };\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n /**\n * Upload files to Slack and share them to a channel.\n * Returns the file IDs of uploaded files.\n */\n private async uploadFiles(\n files: FileUpload[],\n channel: string,\n threadTs?: string,\n ): Promise<string[]> {\n const fileIds: string[] = [];\n\n for (const file of files) {\n try {\n // Convert data to Buffer using shared utility\n const fileBuffer = await toBuffer(file.data, { platform: \"slack\" });\n if (!fileBuffer) {\n continue;\n }\n\n this.logger.debug(\"Slack API: files.uploadV2\", {\n filename: file.filename,\n size: fileBuffer.length,\n mimeType: file.mimeType,\n });\n\n // biome-ignore lint/suspicious/noExplicitAny: Slack API types don't match actual usage\n const uploadArgs: any = {\n channel_id: channel,\n filename: file.filename,\n file: fileBuffer,\n };\n if (threadTs) {\n uploadArgs.thread_ts = threadTs;\n }\n\n const result = (await this.client.files.uploadV2(uploadArgs)) as {\n ok: boolean;\n files?: Array<{ id?: string }>;\n };\n\n this.logger.debug(\"Slack API: files.uploadV2 response\", {\n ok: result.ok,\n });\n\n // Extract file IDs from the response\n if (result.files && Array.isArray(result.files)) {\n for (const uploadedFile of result.files) {\n if (uploadedFile.id) {\n fileIds.push(uploadedFile.id);\n }\n }\n }\n } catch (error) {\n this.logger.error(\"Failed to upload file\", {\n filename: file.filename,\n error,\n });\n throw error;\n }\n }\n\n return fileIds;\n }\n\n async editMessage(\n threadId: string,\n messageId: string,\n message: AdapterPostableMessage,\n ): Promise<RawMessage<unknown>> {\n const { channel } = this.decodeThreadId(threadId);\n\n try {\n // Check if message contains a card\n const card = extractCard(message);\n\n if (card) {\n // Render card as Block Kit\n const blocks = cardToBlockKit(card);\n const fallbackText = cardToFallbackText(card);\n\n this.logger.debug(\"Slack API: chat.update (blocks)\", {\n channel,\n messageId,\n blockCount: blocks.length,\n });\n\n const result = await this.client.chat.update({\n channel,\n ts: messageId,\n text: fallbackText,\n blocks,\n });\n\n this.logger.debug(\"Slack API: chat.update response\", {\n messageId: result.ts,\n ok: result.ok,\n });\n\n return {\n id: result.ts as string,\n threadId,\n raw: result,\n };\n }\n\n // Regular text message\n const text = convertEmojiPlaceholders(\n this.formatConverter.renderPostable(message),\n \"slack\",\n );\n\n this.logger.debug(\"Slack API: chat.update\", {\n channel,\n messageId,\n textLength: text.length,\n });\n\n const result = await this.client.chat.update({\n channel,\n ts: messageId,\n text,\n });\n\n this.logger.debug(\"Slack API: chat.update response\", {\n messageId: result.ts,\n ok: result.ok,\n });\n\n return {\n id: result.ts as string,\n threadId,\n raw: result,\n };\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n async deleteMessage(threadId: string, messageId: string): Promise<void> {\n const { channel } = this.decodeThreadId(threadId);\n\n try {\n this.logger.debug(\"Slack API: chat.delete\", { channel, messageId });\n\n await this.client.chat.delete({\n channel,\n ts: messageId,\n });\n\n this.logger.debug(\"Slack API: chat.delete response\", { ok: true });\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n async addReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string,\n ): Promise<void> {\n const { channel } = this.decodeThreadId(threadId);\n // Convert emoji (EmojiValue or string) to Slack format, strip colons\n const slackEmoji = defaultEmojiResolver.toSlack(emoji);\n const name = slackEmoji.replace(/:/g, \"\");\n\n try {\n this.logger.debug(\"Slack API: reactions.add\", {\n channel,\n messageId,\n emoji: name,\n });\n\n await this.client.reactions.add({\n channel,\n timestamp: messageId,\n name,\n });\n\n this.logger.debug(\"Slack API: reactions.add response\", { ok: true });\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n async removeReaction(\n threadId: string,\n messageId: string,\n emoji: EmojiValue | string,\n ): Promise<void> {\n const { channel } = this.decodeThreadId(threadId);\n // Convert emoji (EmojiValue or string) to Slack format, strip colons\n const slackEmoji = defaultEmojiResolver.toSlack(emoji);\n const name = slackEmoji.replace(/:/g, \"\");\n\n try {\n this.logger.debug(\"Slack API: reactions.remove\", {\n channel,\n messageId,\n emoji: name,\n });\n\n await this.client.reactions.remove({\n channel,\n timestamp: messageId,\n name,\n });\n\n this.logger.debug(\"Slack API: reactions.remove response\", { ok: true });\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n async startTyping(_threadId: string): Promise<void> {\n // Slack doesn't have a direct typing indicator API for bots\n }\n\n /**\n * Stream a message using Slack's native streaming API.\n *\n * Consumes an async iterable of text chunks and streams them to Slack.\n * Requires `recipientUserId` and `recipientTeamId` in options.\n */\n async stream(\n threadId: string,\n textStream: AsyncIterable<string>,\n options?: StreamOptions,\n ): Promise<RawMessage<unknown>> {\n if (!options?.recipientUserId || !options?.recipientTeamId) {\n throw new ChatError(\n \"Slack streaming requires recipientUserId and recipientTeamId in options\",\n \"MISSING_STREAM_OPTIONS\",\n );\n }\n const { channel, threadTs } = this.decodeThreadId(threadId);\n this.logger.debug(\"Slack: starting stream\", { channel, threadTs });\n\n const streamer = this.client.chatStream({\n channel,\n thread_ts: threadTs,\n recipient_user_id: options.recipientUserId,\n recipient_team_id: options.recipientTeamId,\n });\n\n for await (const chunk of textStream) {\n await streamer.append({ markdown_text: chunk });\n }\n const result = await streamer.stop();\n const messageTs = (result.message?.ts ?? result.ts) as string;\n\n this.logger.debug(\"Slack: stream complete\", { messageId: messageTs });\n\n return {\n id: messageTs,\n threadId,\n raw: result,\n };\n }\n\n /**\n * Open a direct message conversation with a user.\n * Returns a thread ID that can be used to post messages.\n */\n async openDM(userId: string): Promise<string> {\n try {\n this.logger.debug(\"Slack API: conversations.open\", { userId });\n\n const result = await this.client.conversations.open({ users: userId });\n\n if (!result.channel?.id) {\n throw new NetworkError(\n \"slack\",\n \"Failed to open DM - no channel returned\",\n );\n }\n\n const channelId = result.channel.id;\n\n this.logger.debug(\"Slack API: conversations.open response\", {\n channelId,\n ok: result.ok,\n });\n\n // Encode as thread ID (no threadTs for new DM - messages will start new threads)\n return this.encodeThreadId({\n channel: channelId,\n threadTs: \"\", // Empty threadTs indicates top-level channel messages\n });\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n async fetchMessages(\n threadId: string,\n options: FetchOptions = {},\n ): Promise<FetchResult<unknown>> {\n const { channel, threadTs } = this.decodeThreadId(threadId);\n const direction = options.direction ?? \"backward\";\n const limit = options.limit || 100;\n\n try {\n if (direction === \"forward\") {\n // Forward direction: fetch oldest messages first, cursor moves to newer\n // Uses native Slack cursor pagination which is efficient\n return this.fetchMessagesForward(\n channel,\n threadTs,\n threadId,\n limit,\n options.cursor,\n );\n }\n // Backward direction: fetch most recent messages first, cursor moves to older\n // Slack API returns oldest-first, so we need to work around this\n return this.fetchMessagesBackward(\n channel,\n threadTs,\n threadId,\n limit,\n options.cursor,\n );\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n /**\n * Fetch messages in forward direction (oldest first, efficient).\n * Uses native Slack cursor pagination.\n */\n private async fetchMessagesForward(\n channel: string,\n threadTs: string,\n threadId: string,\n limit: number,\n cursor?: string,\n ): Promise<FetchResult<unknown>> {\n this.logger.debug(\"Slack API: conversations.replies (forward)\", {\n channel,\n threadTs,\n limit,\n cursor,\n });\n\n const result = await this.client.conversations.replies({\n channel,\n ts: threadTs,\n limit,\n cursor,\n });\n\n const slackMessages = (result.messages || []) as SlackEvent[];\n const nextCursor = (\n result as { response_metadata?: { next_cursor?: string } }\n ).response_metadata?.next_cursor;\n\n this.logger.debug(\"Slack API: conversations.replies response\", {\n messageCount: slackMessages.length,\n ok: result.ok,\n hasNextCursor: !!nextCursor,\n });\n\n const messages = await Promise.all(\n slackMessages.map((msg) => this.parseSlackMessage(msg, threadId)),\n );\n\n return {\n messages,\n nextCursor: nextCursor || undefined,\n };\n }\n\n /**\n * Fetch messages in backward direction (most recent first).\n *\n * Slack's API returns oldest-first, so for backward direction we:\n * 1. Use `latest` parameter to fetch messages before a timestamp (cursor)\n * 2. Fetch up to 1000 messages (API limit) and take the last N\n * 3. Return messages in chronological order (oldest first within the page)\n *\n * Note: For very large threads (>1000 messages), the first backward call\n * may not return the absolute most recent messages. This is a Slack API limitation.\n */\n private async fetchMessagesBackward(\n channel: string,\n threadTs: string,\n threadId: string,\n limit: number,\n cursor?: string,\n ): Promise<FetchResult<unknown>> {\n // Cursor is a timestamp - fetch messages before this time\n // For the initial call (no cursor), we want the most recent messages\n const latest = cursor || undefined;\n\n this.logger.debug(\"Slack API: conversations.replies (backward)\", {\n channel,\n threadTs,\n limit,\n latest,\n });\n\n // Fetch a larger batch to ensure we can return the last `limit` messages\n // Slack API max is 1000 messages per request\n const fetchLimit = Math.min(1000, Math.max(limit * 2, 200));\n\n const result = await this.client.conversations.replies({\n channel,\n ts: threadTs,\n limit: fetchLimit,\n latest,\n inclusive: false, // Don't include the cursor message itself\n });\n\n const slackMessages = (result.messages || []) as SlackEvent[];\n\n this.logger.debug(\"Slack API: conversations.replies response (backward)\", {\n messageCount: slackMessages.length,\n ok: result.ok,\n hasMore: result.has_more,\n });\n\n // If we have more messages than requested, take the last `limit`\n // This gives us the most recent messages\n const startIndex = Math.max(0, slackMessages.length - limit);\n const selectedMessages = slackMessages.slice(startIndex);\n\n const messages = await Promise.all(\n selectedMessages.map((msg) => this.parseSlackMessage(msg, threadId)),\n );\n\n // For backward pagination, nextCursor points to older messages\n // Use the timestamp of the oldest message we're NOT returning\n let nextCursor: string | undefined;\n if (startIndex > 0 || result.has_more) {\n // There are more (older) messages available\n // Use the timestamp of the oldest message in our selection as the cursor\n const oldestSelected = selectedMessages[0];\n if (oldestSelected?.ts) {\n nextCursor = oldestSelected.ts;\n }\n }\n\n return {\n messages,\n nextCursor,\n };\n }\n\n async fetchThread(threadId: string): Promise<ThreadInfo> {\n const { channel, threadTs } = this.decodeThreadId(threadId);\n\n try {\n this.logger.debug(\"Slack API: conversations.info\", { channel });\n\n const result = await this.client.conversations.info({ channel });\n const channelInfo = result.channel as { name?: string } | undefined;\n\n this.logger.debug(\"Slack API: conversations.info response\", {\n channelName: channelInfo?.name,\n ok: result.ok,\n });\n\n return {\n id: threadId,\n channelId: channel,\n channelName: channelInfo?.name,\n metadata: {\n threadTs,\n channel: result.channel,\n },\n };\n } catch (error) {\n this.handleSlackError(error);\n }\n }\n\n encodeThreadId(platformData: SlackThreadId): string {\n return `slack:${platformData.channel}:${platformData.threadTs}`;\n }\n\n /**\n * Check if a thread is a direct message conversation.\n * Slack DM channel IDs start with 'D'.\n */\n isDM(threadId: string): boolean {\n const { channel } = this.decodeThreadId(threadId);\n return channel.startsWith(\"D\");\n }\n\n decodeThreadId(threadId: string): SlackThreadId {\n const parts = threadId.split(\":\");\n if (parts.length !== 3 || parts[0] !== \"slack\") {\n throw new ValidationError(\n \"slack\",\n `Invalid Slack thread ID: ${threadId}`,\n );\n }\n return {\n channel: parts[1] as string,\n threadTs: parts[2] as string,\n };\n }\n\n parseMessage(raw: SlackEvent): Message<unknown> {\n const event = raw;\n const threadTs = event.thread_ts || event.ts || \"\";\n const threadId = this.encodeThreadId({\n channel: event.channel || \"\",\n threadTs,\n });\n // Use synchronous version without user lookup for interface compliance\n return this.parseSlackMessageSync(event, threadId);\n }\n\n /**\n * Synchronous message parsing without user lookup.\n * Used for parseMessage interface - falls back to user ID for username.\n */\n private parseSlackMessageSync(\n event: SlackEvent,\n threadId: string,\n ): Message<unknown> {\n const isMe = this.isMessageFromSelf(event);\n\n const text = event.text || \"\";\n // Without async lookup, fall back to user ID for human users\n const userName = event.username || event.user || \"unknown\";\n const fullName = event.username || event.user || \"unknown\";\n\n return {\n id: event.ts || \"\",\n threadId,\n text: this.formatConverter.extractPlainText(text),\n formatted: this.formatConverter.toAst(text),\n raw: event,\n author: {\n userId: event.user || event.bot_id || \"unknown\",\n userName,\n fullName,\n isBot: !!event.bot_id,\n isMe,\n },\n metadata: {\n dateSent: new Date(parseFloat(event.ts || \"0\") * 1000),\n edited: !!event.edited,\n editedAt: event.edited\n ? new Date(parseFloat(event.edited.ts) * 1000)\n : undefined,\n },\n attachments: (event.files || []).map((file) =>\n this.createAttachment(file),\n ),\n };\n }\n\n renderFormatted(content: FormattedContent): string {\n return this.formatConverter.fromAst(content);\n }\n\n /**\n * Check if a Slack event is from this bot.\n *\n * Slack messages can come from:\n * - User messages: have `user` field (U_xxx format)\n * - Bot messages: have `bot_id` field (B_xxx format)\n *\n * We check both because:\n * - _botUserId is the user ID (U_xxx) - matches event.user\n * - _botId is the bot ID (B_xxx) - matches event.bot_id\n */\n private isMessageFromSelf(event: SlackEvent): boolean {\n // Primary check: user ID match (for messages sent as the bot user)\n if (this._botUserId && event.user === this._botUserId) {\n return true;\n }\n\n // Secondary check: bot ID match (for bot_message subtypes)\n if (this._botId && event.bot_id === this._botId) {\n return true;\n }\n\n return false;\n }\n\n private handleSlackError(error: unknown): never {\n const slackError = error as { data?: { error?: string }; code?: string };\n\n if (slackError.code === \"slack_webapi_platform_error\") {\n if (slackError.data?.error === \"ratelimited\") {\n throw new AdapterRateLimitError(\"slack\");\n }\n }\n\n throw error;\n }\n}\n\nexport function createSlackAdapter(config: SlackAdapterConfig): SlackAdapter {\n return new SlackAdapter(config);\n}\n\n// Re-export card converter for advanced use\nexport { cardToBlockKit, cardToFallbackText } from \"./cards\";\n// Re-export format converter for advanced use\nexport {\n SlackFormatConverter,\n SlackFormatConverter as SlackMarkdownConverter,\n} from \"./markdown\";\n","/**\n * Slack Block Kit converter for cross-platform cards.\n *\n * Converts CardElement to Slack Block Kit blocks.\n * @see https://api.slack.com/block-kit\n */\n\nimport {\n createEmojiConverter,\n mapButtonStyle,\n cardToFallbackText as sharedCardToFallbackText,\n} from \"@chat-adapter/shared\";\nimport type {\n ActionsElement,\n ButtonElement,\n CardChild,\n CardElement,\n DividerElement,\n FieldsElement,\n ImageElement,\n SectionElement,\n TextElement,\n} from \"chat\";\n\n/**\n * Convert emoji placeholders in text to Slack format.\n */\nconst convertEmoji = createEmojiConverter(\"slack\");\n\n// Slack Block Kit types (simplified)\nexport interface SlackBlock {\n type: string;\n block_id?: string;\n [key: string]: unknown;\n}\n\ninterface SlackTextObject {\n type: \"plain_text\" | \"mrkdwn\";\n text: string;\n emoji?: boolean;\n}\n\ninterface SlackButtonElement {\n type: \"button\";\n text: SlackTextObject;\n action_id: string;\n value?: string;\n style?: \"primary\" | \"danger\";\n}\n\n/**\n * Convert a CardElement to Slack Block Kit blocks.\n */\nexport function cardToBlockKit(card: CardElement): SlackBlock[] {\n const blocks: SlackBlock[] = [];\n\n // Add header if title is present\n if (card.title) {\n blocks.push({\n type: \"header\",\n text: {\n type: \"plain_text\",\n text: convertEmoji(card.title),\n emoji: true,\n },\n });\n }\n\n // Add subtitle as context if present\n if (card.subtitle) {\n blocks.push({\n type: \"context\",\n elements: [\n {\n type: \"mrkdwn\",\n text: convertEmoji(card.subtitle),\n },\n ],\n });\n }\n\n // Add header image if present\n if (card.imageUrl) {\n blocks.push({\n type: \"image\",\n image_url: card.imageUrl,\n alt_text: card.title || \"Card image\",\n });\n }\n\n // Convert children\n for (const child of card.children) {\n const childBlocks = convertChildToBlocks(child);\n blocks.push(...childBlocks);\n }\n\n return blocks;\n}\n\n/**\n * Convert a card child element to Slack blocks.\n */\nfunction convertChildToBlocks(child: CardChild): SlackBlock[] {\n switch (child.type) {\n case \"text\":\n return [convertTextToBlock(child)];\n case \"image\":\n return [convertImageToBlock(child)];\n case \"divider\":\n return [convertDividerToBlock(child)];\n case \"actions\":\n return [convertActionsToBlock(child)];\n case \"section\":\n return convertSectionToBlocks(child);\n case \"fields\":\n return [convertFieldsToBlock(child)];\n default:\n return [];\n }\n}\n\nfunction convertTextToBlock(element: TextElement): SlackBlock {\n const text = convertEmoji(element.content);\n let formattedText = text;\n\n // Apply style\n if (element.style === \"bold\") {\n formattedText = `*${text}*`;\n } else if (element.style === \"muted\") {\n // Slack doesn't have a muted style, use context block\n return {\n type: \"context\",\n elements: [{ type: \"mrkdwn\", text }],\n };\n }\n\n return {\n type: \"section\",\n text: {\n type: \"mrkdwn\",\n text: formattedText,\n },\n };\n}\n\nfunction convertImageToBlock(element: ImageElement): SlackBlock {\n return {\n type: \"image\",\n image_url: element.url,\n alt_text: element.alt || \"Image\",\n };\n}\n\nfunction convertDividerToBlock(_element: DividerElement): SlackBlock {\n return { type: \"divider\" };\n}\n\nfunction convertActionsToBlock(element: ActionsElement): SlackBlock {\n const elements: SlackButtonElement[] = element.children.map((button) =>\n convertButtonToElement(button),\n );\n\n return {\n type: \"actions\",\n elements,\n };\n}\n\nfunction convertButtonToElement(button: ButtonElement): SlackButtonElement {\n const element: SlackButtonElement = {\n type: \"button\",\n text: {\n type: \"plain_text\",\n text: convertEmoji(button.label),\n emoji: true,\n },\n action_id: button.id,\n };\n\n if (button.value) {\n element.value = button.value;\n }\n\n const style = mapButtonStyle(button.style, \"slack\");\n if (style) {\n element.style = style as \"primary\" | \"danger\";\n }\n\n return element;\n}\n\nfunction convertSectionToBlocks(element: SectionElement): SlackBlock[] {\n // Flatten section children into blocks\n const blocks: SlackBlock[] = [];\n for (const child of element.children) {\n blocks.push(...convertChildToBlocks(child));\n }\n return blocks;\n}\n\nfunction convertFieldsToBlock(element: FieldsElement): SlackBlock {\n const fields: SlackTextObject[] = [];\n\n for (const field of element.children) {\n // Add label and value as separate field items\n fields.push({\n type: \"mrkdwn\",\n text: `*${convertEmoji(field.label)}*\\n${convertEmoji(field.value)}`,\n });\n }\n\n return {\n type: \"section\",\n fields,\n };\n}\n\n/**\n * Generate fallback text from a card element.\n * Used when blocks aren't supported or for notifications.\n */\nexport function cardToFallbackText(card: CardElement): string {\n return sharedCardToFallbackText(card, {\n boldFormat: \"*\",\n lineBreak: \"\\n\",\n platform: \"slack\",\n });\n}\n","/**\n * Slack-specific format conversion using AST-based parsing.\n *\n * Slack uses \"mrkdwn\" format which is similar but not identical to markdown:\n * - Bold: *text* (not **text**)\n * - Italic: _text_ (same)\n * - Strikethrough: ~text~ (not ~~text~~)\n * - Links: <url|text> (not [text](url))\n * - User mentions: <@U123>\n * - Channel mentions: <#C123|name>\n */\n\nimport {\n type AdapterPostableMessage,\n BaseFormatConverter,\n type Content,\n getNodeChildren,\n getNodeValue,\n isBlockquoteNode,\n isCodeNode,\n isDeleteNode,\n isEmphasisNode,\n isInlineCodeNode,\n isLinkNode,\n isListItemNode,\n isListNode,\n isParagraphNode,\n isStrongNode,\n isTextNode,\n parseMarkdown,\n type Root,\n} from \"chat\";\n\nexport class SlackFormatConverter extends BaseFormatConverter {\n /**\n * Convert @mentions to Slack format in plain text.\n * @name → <@name>\n */\n private convertMentionsToSlack(text: string): string {\n return text.replace(/@(\\w+)/g, \"<@$1>\");\n }\n\n /**\n * Override renderPostable to convert @mentions in plain strings.\n */\n override renderPostable(message: AdapterPostableMessage): string {\n if (typeof message === \"string\") {\n return this.convertMentionsToSlack(message);\n }\n if (\"raw\" in message) {\n return this.convertMentionsToSlack(message.raw);\n }\n if (\"markdown\" in message) {\n return this.fromAst(parseMarkdown(message.markdown));\n }\n if (\"ast\" in message) {\n return this.fromAst(message.ast);\n }\n return \"\";\n }\n\n /**\n * Render an AST to Slack mrkdwn format.\n */\n fromAst(ast: Root): string {\n return this.fromAstWithNodeConverter(ast, (node) =>\n this.nodeToMrkdwn(node),\n );\n }\n\n /**\n * Parse Slack mrkdwn into an AST.\n */\n toAst(mrkdwn: string): Root {\n // Convert Slack mrkdwn to standard markdown string, then parse\n let markdown = mrkdwn;\n\n // User mentions: <@U123|name> -> @name or <@U123> -> @U123\n markdown = markdown.replace(/<@([^|>]+)\\|([^>]+)>/g, \"@$2\");\n markdown = markdown.replace(/<@([^>]+)>/g, \"@$1\");\n\n // Channel mentions: <#C123|name> -> #name\n markdown = markdown.replace(/<#[^|>]+\\|([^>]+)>/g, \"#$1\");\n markdown = markdown.replace(/<#([^>]+)>/g, \"#$1\");\n\n // Links: <url|text> -> [text](url)\n markdown = markdown.replace(/<(https?:\\/\\/[^|>]+)\\|([^>]+)>/g, \"[$2]($1)\");\n\n // Bare links: <url> -> url\n markdown = markdown.replace(/<(https?:\\/\\/[^>]+)>/g, \"$1\");\n\n // Bold: *text* -> **text** (but be careful with emphasis)\n // This is tricky because Slack uses * for bold, not emphasis\n markdown = markdown.replace(/(?<![_*\\\\])\\*([^*\\n]+)\\*(?![_*])/g, \"**$1**\");\n\n // Strikethrough: ~text~ -> ~~text~~\n markdown = markdown.replace(/(?<!~)~([^~\\n]+)~(?!~)/g, \"~~$1~~\");\n\n return parseMarkdown(markdown);\n }\n\n private nodeToMrkdwn(node: Content): string {\n // Use type guards for type-safe node handling\n if (isParagraphNode(node)) {\n return getNodeChildren(node)\n .map((child) => this.nodeToMrkdwn(child))\n .join(\"\");\n }\n\n if (isTextNode(node)) {\n // Convert @mentions to Slack format <@mention>\n return node.value.replace(/@(\\w+)/g, \"<@$1>\");\n }\n\n if (isStrongNode(node)) {\n // Markdown **text** -> Slack *text*\n const content = getNodeChildren(node)\n .map((child) => this.nodeToMrkdwn(child))\n .join(\"\");\n return `*${content}*`;\n }\n\n if (isEmphasisNode(node)) {\n // Both use _text_\n const content = getNodeChildren(node)\n .map((child) => this.nodeToMrkdwn(child))\n .join(\"\");\n return `_${content}_`;\n }\n\n if (isDeleteNode(node)) {\n // Markdown ~~text~~ -> Slack ~text~\n const content = getNodeChildren(node)\n .map((child) => this.nodeToMrkdwn(child))\n .join(\"\");\n return `~${content}~`;\n }\n\n if (isInlineCodeNode(node)) {\n return `\\`${node.value}\\``;\n }\n\n if (isCodeNode(node)) {\n return `\\`\\`\\`${node.lang || \"\"}\\n${node.value}\\n\\`\\`\\``;\n }\n\n if (isLinkNode(node)) {\n const linkText = getNodeChildren(node)\n .map((child) => this.nodeToMrkdwn(child))\n .join(\"\");\n // Markdown [text](url) -> Slack <url|text>\n return `<${node.url}|${linkText}>`;\n }\n\n if (isBlockquoteNode(node)) {\n return getNodeChildren(node)\n .map((child) => `> ${this.nodeToMrkdwn(child)}`)\n .join(\"\\n\");\n }\n\n if (isListNode(node)) {\n return getNodeChildren(node)\n .map((item, i) => {\n const prefix = node.ordered ? `${i + 1}.` : \"•\";\n const content = getNodeChildren(item)\n .map((child) => this.nodeToMrkdwn(child))\n .join(\"\");\n return `${prefix} ${content}`;\n })\n .join(\"\\n\");\n }\n\n if (isListItemNode(node)) {\n return getNodeChildren(node)\n .map((child) => this.nodeToMrkdwn(child))\n .join(\"\");\n }\n\n if (node.type === \"break\") {\n return \"\\n\";\n }\n\n if (node.type === \"thematicBreak\") {\n return \"---\";\n }\n\n // For unsupported nodes, try to extract text\n const children = getNodeChildren(node);\n if (children.length > 0) {\n return children.map((child) => this.nodeToMrkdwn(child)).join(\"\");\n }\n return getNodeValue(node);\n }\n}\n\n// Backwards compatibility alias\nexport { SlackFormatConverter as SlackMarkdownConverter };\n"],"mappings":"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","names":["card","result"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chat-adapter/slack",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "description": "Slack adapter for chat",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,8 @@
17
17
  ],
18
18
  "dependencies": {
19
19
  "@slack/web-api": "^7.8.0",
20
- "chat": "4.1.0"
20
+ "@chat-adapter/shared": "4.3.0",
21
+ "chat": "4.3.0"
21
22
  },
22
23
  "devDependencies": {
23
24
  "@types/node": "^22.10.2",