@chat-adapter/slack 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +19 -2
- package/dist/index.js +99 -15
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CardElement, BaseFormatConverter, AdapterPostableMessage, Root, Adapter, ChatInstance, WebhookOptions, RawMessage, EmojiValue, StreamOptions, FetchOptions,
|
|
1
|
+
import { CardElement, BaseFormatConverter, AdapterPostableMessage, Root, 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.
|
|
@@ -187,7 +187,24 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
|
|
|
187
187
|
* Returns a thread ID that can be used to post messages.
|
|
188
188
|
*/
|
|
189
189
|
openDM(userId: string): Promise<string>;
|
|
190
|
-
fetchMessages(threadId: string, options?: FetchOptions): Promise<
|
|
190
|
+
fetchMessages(threadId: string, options?: FetchOptions): Promise<FetchResult<unknown>>;
|
|
191
|
+
/**
|
|
192
|
+
* Fetch messages in forward direction (oldest first, efficient).
|
|
193
|
+
* Uses native Slack cursor pagination.
|
|
194
|
+
*/
|
|
195
|
+
private fetchMessagesForward;
|
|
196
|
+
/**
|
|
197
|
+
* Fetch messages in backward direction (most recent first).
|
|
198
|
+
*
|
|
199
|
+
* Slack's API returns oldest-first, so for backward direction we:
|
|
200
|
+
* 1. Use `latest` parameter to fetch messages before a timestamp (cursor)
|
|
201
|
+
* 2. Fetch up to 1000 messages (API limit) and take the last N
|
|
202
|
+
* 3. Return messages in chronological order (oldest first within the page)
|
|
203
|
+
*
|
|
204
|
+
* Note: For very large threads (>1000 messages), the first backward call
|
|
205
|
+
* may not return the absolute most recent messages. This is a Slack API limitation.
|
|
206
|
+
*/
|
|
207
|
+
private fetchMessagesBackward;
|
|
191
208
|
fetchThread(threadId: string): Promise<ThreadInfo>;
|
|
192
209
|
encodeThreadId(platformData: SlackThreadId): string;
|
|
193
210
|
/**
|
package/dist/index.js
CHANGED
|
@@ -955,28 +955,112 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
955
955
|
}
|
|
956
956
|
async fetchMessages(threadId, options = {}) {
|
|
957
957
|
const { channel, threadTs } = this.decodeThreadId(threadId);
|
|
958
|
+
const direction = options.direction ?? "backward";
|
|
959
|
+
const limit = options.limit || 100;
|
|
958
960
|
try {
|
|
959
|
-
|
|
961
|
+
if (direction === "forward") {
|
|
962
|
+
return this.fetchMessagesForward(
|
|
963
|
+
channel,
|
|
964
|
+
threadTs,
|
|
965
|
+
threadId,
|
|
966
|
+
limit,
|
|
967
|
+
options.cursor
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
return this.fetchMessagesBackward(
|
|
960
971
|
channel,
|
|
961
972
|
threadTs,
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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));
|
|
973
|
+
threadId,
|
|
974
|
+
limit,
|
|
975
|
+
options.cursor
|
|
976
|
+
);
|
|
976
977
|
} catch (error) {
|
|
977
978
|
this.handleSlackError(error);
|
|
978
979
|
}
|
|
979
980
|
}
|
|
981
|
+
/**
|
|
982
|
+
* Fetch messages in forward direction (oldest first, efficient).
|
|
983
|
+
* Uses native Slack cursor pagination.
|
|
984
|
+
*/
|
|
985
|
+
async fetchMessagesForward(channel, threadTs, threadId, limit, cursor) {
|
|
986
|
+
this.logger?.debug("Slack API: conversations.replies (forward)", {
|
|
987
|
+
channel,
|
|
988
|
+
threadTs,
|
|
989
|
+
limit,
|
|
990
|
+
cursor
|
|
991
|
+
});
|
|
992
|
+
const result = await this.client.conversations.replies({
|
|
993
|
+
channel,
|
|
994
|
+
ts: threadTs,
|
|
995
|
+
limit,
|
|
996
|
+
cursor
|
|
997
|
+
});
|
|
998
|
+
const slackMessages = result.messages || [];
|
|
999
|
+
const nextCursor = result.response_metadata?.next_cursor;
|
|
1000
|
+
this.logger?.debug("Slack API: conversations.replies response", {
|
|
1001
|
+
messageCount: slackMessages.length,
|
|
1002
|
+
ok: result.ok,
|
|
1003
|
+
hasNextCursor: !!nextCursor
|
|
1004
|
+
});
|
|
1005
|
+
const messages = await Promise.all(
|
|
1006
|
+
slackMessages.map((msg) => this.parseSlackMessage(msg, threadId))
|
|
1007
|
+
);
|
|
1008
|
+
return {
|
|
1009
|
+
messages,
|
|
1010
|
+
nextCursor: nextCursor || void 0
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Fetch messages in backward direction (most recent first).
|
|
1015
|
+
*
|
|
1016
|
+
* Slack's API returns oldest-first, so for backward direction we:
|
|
1017
|
+
* 1. Use `latest` parameter to fetch messages before a timestamp (cursor)
|
|
1018
|
+
* 2. Fetch up to 1000 messages (API limit) and take the last N
|
|
1019
|
+
* 3. Return messages in chronological order (oldest first within the page)
|
|
1020
|
+
*
|
|
1021
|
+
* Note: For very large threads (>1000 messages), the first backward call
|
|
1022
|
+
* may not return the absolute most recent messages. This is a Slack API limitation.
|
|
1023
|
+
*/
|
|
1024
|
+
async fetchMessagesBackward(channel, threadTs, threadId, limit, cursor) {
|
|
1025
|
+
const latest = cursor || void 0;
|
|
1026
|
+
this.logger?.debug("Slack API: conversations.replies (backward)", {
|
|
1027
|
+
channel,
|
|
1028
|
+
threadTs,
|
|
1029
|
+
limit,
|
|
1030
|
+
latest
|
|
1031
|
+
});
|
|
1032
|
+
const fetchLimit = Math.min(1e3, Math.max(limit * 2, 200));
|
|
1033
|
+
const result = await this.client.conversations.replies({
|
|
1034
|
+
channel,
|
|
1035
|
+
ts: threadTs,
|
|
1036
|
+
limit: fetchLimit,
|
|
1037
|
+
latest,
|
|
1038
|
+
inclusive: false
|
|
1039
|
+
// Don't include the cursor message itself
|
|
1040
|
+
});
|
|
1041
|
+
const slackMessages = result.messages || [];
|
|
1042
|
+
this.logger?.debug("Slack API: conversations.replies response (backward)", {
|
|
1043
|
+
messageCount: slackMessages.length,
|
|
1044
|
+
ok: result.ok,
|
|
1045
|
+
hasMore: result.has_more
|
|
1046
|
+
});
|
|
1047
|
+
const startIndex = Math.max(0, slackMessages.length - limit);
|
|
1048
|
+
const selectedMessages = slackMessages.slice(startIndex);
|
|
1049
|
+
const messages = await Promise.all(
|
|
1050
|
+
selectedMessages.map((msg) => this.parseSlackMessage(msg, threadId))
|
|
1051
|
+
);
|
|
1052
|
+
let nextCursor;
|
|
1053
|
+
if (startIndex > 0 || result.has_more) {
|
|
1054
|
+
const oldestSelected = selectedMessages[0];
|
|
1055
|
+
if (oldestSelected?.ts) {
|
|
1056
|
+
nextCursor = oldestSelected.ts;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return {
|
|
1060
|
+
messages,
|
|
1061
|
+
nextCursor
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
980
1064
|
async fetchThread(threadId) {
|
|
981
1065
|
const { channel, threadTs } = this.decodeThreadId(threadId);
|
|
982
1066
|
try {
|
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 { 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\";\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<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 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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chat-adapter/slack",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Slack adapter for chat",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@slack/web-api": "^7.8.0",
|
|
20
|
-
"chat": "4.
|
|
20
|
+
"chat": "4.2.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^22.10.2",
|