chat 3.0.0 → 4.0.1

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.js ADDED
@@ -0,0 +1,1433 @@
1
+ import {
2
+ Actions,
3
+ Button,
4
+ Card,
5
+ CardText,
6
+ Divider,
7
+ Field,
8
+ Fields,
9
+ Image,
10
+ Section,
11
+ cardToFallbackText,
12
+ fromReactElement,
13
+ isCardElement,
14
+ isJSX,
15
+ toCardElement
16
+ } from "./chunk-ACQNDPTB.js";
17
+
18
+ // src/markdown.ts
19
+ import { toString } from "mdast-util-to-string";
20
+ import remarkGfm from "remark-gfm";
21
+ import remarkParse from "remark-parse";
22
+ import remarkStringify from "remark-stringify";
23
+ import { unified } from "unified";
24
+ function parseMarkdown(markdown) {
25
+ const processor = unified().use(remarkParse).use(remarkGfm);
26
+ return processor.parse(markdown);
27
+ }
28
+ function stringifyMarkdown(ast) {
29
+ const processor = unified().use(remarkStringify).use(remarkGfm);
30
+ return processor.stringify(ast);
31
+ }
32
+ function toPlainText(ast) {
33
+ return toString(ast);
34
+ }
35
+ function markdownToPlainText(markdown) {
36
+ const ast = parseMarkdown(markdown);
37
+ return toString(ast);
38
+ }
39
+ function walkAst(node, visitor) {
40
+ if ("children" in node && Array.isArray(node.children)) {
41
+ node.children = node.children.map((child) => {
42
+ const result = visitor(child);
43
+ if (result === null) return null;
44
+ return walkAst(result, visitor);
45
+ }).filter((n) => n !== null);
46
+ }
47
+ return node;
48
+ }
49
+ function text(value) {
50
+ return { type: "text", value };
51
+ }
52
+ function strong(children) {
53
+ return { type: "strong", children };
54
+ }
55
+ function emphasis(children) {
56
+ return { type: "emphasis", children };
57
+ }
58
+ function strikethrough(children) {
59
+ return { type: "delete", children };
60
+ }
61
+ function inlineCode(value) {
62
+ return { type: "inlineCode", value };
63
+ }
64
+ function codeBlock(value, lang) {
65
+ return { type: "code", value, lang };
66
+ }
67
+ function link(url, children, title) {
68
+ return { type: "link", url, children, title };
69
+ }
70
+ function blockquote(children) {
71
+ return { type: "blockquote", children };
72
+ }
73
+ function paragraph(children) {
74
+ return { type: "paragraph", children };
75
+ }
76
+ function root(children) {
77
+ return { type: "root", children };
78
+ }
79
+ var BaseFormatConverter = class {
80
+ extractPlainText(platformText) {
81
+ return toPlainText(this.toAst(platformText));
82
+ }
83
+ // Convenience methods for markdown string I/O
84
+ fromMarkdown(markdown) {
85
+ return this.fromAst(parseMarkdown(markdown));
86
+ }
87
+ toMarkdown(platformText) {
88
+ return stringifyMarkdown(this.toAst(platformText));
89
+ }
90
+ /** @deprecated Use extractPlainText instead */
91
+ toPlainText(platformText) {
92
+ return this.extractPlainText(platformText);
93
+ }
94
+ /**
95
+ * Convert a PostableMessage to platform format (text only).
96
+ * - string: passed through as raw text (no conversion)
97
+ * - { raw: string }: passed through as raw text (no conversion)
98
+ * - { markdown: string }: converted from markdown to platform format
99
+ * - { ast: Root }: converted from AST to platform format
100
+ * - { card: CardElement }: returns fallback text (cards should be handled by adapter)
101
+ * - CardElement: returns fallback text (cards should be handled by adapter)
102
+ *
103
+ * Note: For cards, adapters should check for card content first and render
104
+ * them using platform-specific card APIs, using this method only for fallback.
105
+ */
106
+ renderPostable(message) {
107
+ if (typeof message === "string") {
108
+ return message;
109
+ }
110
+ if ("raw" in message) {
111
+ return message.raw;
112
+ }
113
+ if ("markdown" in message) {
114
+ return this.fromMarkdown(message.markdown);
115
+ }
116
+ if ("ast" in message) {
117
+ return this.fromAst(message.ast);
118
+ }
119
+ if ("card" in message) {
120
+ return message.fallbackText || this.cardToFallbackText(message.card);
121
+ }
122
+ if ("type" in message && message.type === "card") {
123
+ return this.cardToFallbackText(message);
124
+ }
125
+ throw new Error("Invalid PostableMessage format");
126
+ }
127
+ /**
128
+ * Generate fallback text from a card element.
129
+ * Override in subclasses for platform-specific formatting.
130
+ */
131
+ cardToFallbackText(card) {
132
+ const parts = [];
133
+ if (card.title) {
134
+ parts.push(`**${card.title}**`);
135
+ }
136
+ if (card.subtitle) {
137
+ parts.push(card.subtitle);
138
+ }
139
+ for (const child of card.children) {
140
+ const text2 = this.cardChildToFallbackText(child);
141
+ if (text2) {
142
+ parts.push(text2);
143
+ }
144
+ }
145
+ return parts.join("\n");
146
+ }
147
+ /**
148
+ * Convert card child element to fallback text.
149
+ */
150
+ cardChildToFallbackText(child) {
151
+ switch (child.type) {
152
+ case "text":
153
+ return child.content;
154
+ case "fields":
155
+ return child.children.map((f) => `**${f.label}**: ${f.value}`).join("\n");
156
+ case "actions":
157
+ return `[${child.children.map((b) => b.label).join("] [")}]`;
158
+ case "section":
159
+ return child.children.map((c) => this.cardChildToFallbackText(c)).filter(Boolean).join("\n");
160
+ default:
161
+ return null;
162
+ }
163
+ }
164
+ };
165
+
166
+ // src/thread.ts
167
+ var ThreadImpl = class {
168
+ id;
169
+ adapter;
170
+ channelId;
171
+ isDM;
172
+ state;
173
+ _recentMessages = [];
174
+ _isSubscribedContext;
175
+ constructor(config) {
176
+ this.id = config.id;
177
+ this.adapter = config.adapter;
178
+ this.channelId = config.channelId;
179
+ this.isDM = config.isDM ?? false;
180
+ this.state = config.state;
181
+ this._isSubscribedContext = config.isSubscribedContext ?? false;
182
+ if (config.initialMessage) {
183
+ this._recentMessages = [config.initialMessage];
184
+ }
185
+ }
186
+ get recentMessages() {
187
+ return this._recentMessages;
188
+ }
189
+ set recentMessages(messages) {
190
+ this._recentMessages = messages;
191
+ }
192
+ get allMessages() {
193
+ const adapter = this.adapter;
194
+ const threadId = this.id;
195
+ return {
196
+ async *[Symbol.asyncIterator]() {
197
+ let before;
198
+ let hasMore = true;
199
+ while (hasMore) {
200
+ const messages = await adapter.fetchMessages(threadId, {
201
+ limit: 100,
202
+ before
203
+ });
204
+ if (messages.length === 0) {
205
+ hasMore = false;
206
+ break;
207
+ }
208
+ for (const message of messages) {
209
+ yield message;
210
+ }
211
+ before = messages[messages.length - 1]?.id;
212
+ if (messages.length < 100) {
213
+ hasMore = false;
214
+ }
215
+ }
216
+ }
217
+ };
218
+ }
219
+ async isSubscribed() {
220
+ if (this._isSubscribedContext) {
221
+ return true;
222
+ }
223
+ return this.state.isSubscribed(this.id);
224
+ }
225
+ async subscribe() {
226
+ await this.state.subscribe(this.id);
227
+ if (this.adapter.onThreadSubscribe) {
228
+ await this.adapter.onThreadSubscribe(this.id);
229
+ }
230
+ }
231
+ async unsubscribe() {
232
+ await this.state.unsubscribe(this.id);
233
+ }
234
+ async post(message) {
235
+ let postable = message;
236
+ if (isJSX(message)) {
237
+ const card = toCardElement(message);
238
+ if (!card) {
239
+ throw new Error("Invalid JSX element: must be a Card element");
240
+ }
241
+ postable = card;
242
+ }
243
+ const rawMessage = await this.adapter.postMessage(this.id, postable);
244
+ return this.createSentMessage(rawMessage.id, postable);
245
+ }
246
+ async startTyping() {
247
+ await this.adapter.startTyping(this.id);
248
+ }
249
+ async refresh() {
250
+ const messages = await this.adapter.fetchMessages(this.id, { limit: 50 });
251
+ this._recentMessages = messages;
252
+ }
253
+ mentionUser(userId) {
254
+ return `<@${userId}>`;
255
+ }
256
+ createSentMessage(messageId, postable) {
257
+ const adapter = this.adapter;
258
+ const threadId = this.id;
259
+ const self = this;
260
+ const { plainText, formatted, attachments } = extractMessageContent(postable);
261
+ const sentMessage = {
262
+ id: messageId,
263
+ threadId,
264
+ text: plainText,
265
+ formatted,
266
+ raw: null,
267
+ // Will be populated if needed
268
+ author: {
269
+ userId: "self",
270
+ userName: adapter.userName,
271
+ fullName: adapter.userName,
272
+ isBot: true,
273
+ isMe: true
274
+ },
275
+ metadata: {
276
+ dateSent: /* @__PURE__ */ new Date(),
277
+ edited: false
278
+ },
279
+ attachments,
280
+ async edit(newContent) {
281
+ let postable2 = newContent;
282
+ if (isJSX(newContent)) {
283
+ const card = toCardElement(newContent);
284
+ if (!card) {
285
+ throw new Error("Invalid JSX element: must be a Card element");
286
+ }
287
+ postable2 = card;
288
+ }
289
+ await adapter.editMessage(threadId, messageId, postable2);
290
+ return self.createSentMessage(messageId, postable2);
291
+ },
292
+ async delete() {
293
+ await adapter.deleteMessage(threadId, messageId);
294
+ },
295
+ async addReaction(emoji2) {
296
+ await adapter.addReaction(threadId, messageId, emoji2);
297
+ },
298
+ async removeReaction(emoji2) {
299
+ await adapter.removeReaction(threadId, messageId, emoji2);
300
+ }
301
+ };
302
+ return sentMessage;
303
+ }
304
+ };
305
+ function extractMessageContent(message) {
306
+ if (typeof message === "string") {
307
+ return {
308
+ plainText: message,
309
+ formatted: root([paragraph([text(message)])]),
310
+ attachments: []
311
+ };
312
+ }
313
+ if ("raw" in message) {
314
+ return {
315
+ plainText: message.raw,
316
+ formatted: root([paragraph([text(message.raw)])]),
317
+ attachments: message.attachments || []
318
+ };
319
+ }
320
+ if ("markdown" in message) {
321
+ const ast = parseMarkdown(message.markdown);
322
+ return {
323
+ plainText: toPlainText(ast),
324
+ formatted: ast,
325
+ attachments: message.attachments || []
326
+ };
327
+ }
328
+ if ("ast" in message) {
329
+ return {
330
+ plainText: toPlainText(message.ast),
331
+ formatted: message.ast,
332
+ attachments: message.attachments || []
333
+ };
334
+ }
335
+ if ("card" in message) {
336
+ const fallbackText = message.fallbackText || cardToFallbackText(message.card);
337
+ return {
338
+ plainText: fallbackText,
339
+ formatted: root([paragraph([text(fallbackText)])]),
340
+ attachments: []
341
+ };
342
+ }
343
+ if ("type" in message && message.type === "card") {
344
+ const fallbackText = cardToFallbackText(message);
345
+ return {
346
+ plainText: fallbackText,
347
+ formatted: root([paragraph([text(fallbackText)])]),
348
+ attachments: []
349
+ };
350
+ }
351
+ throw new Error("Invalid PostableMessage format");
352
+ }
353
+
354
+ // src/types.ts
355
+ var ConsoleLogger = class _ConsoleLogger {
356
+ constructor(level = "info", prefix = "chat-sdk") {
357
+ this.level = level;
358
+ this.prefix = prefix;
359
+ }
360
+ prefix;
361
+ shouldLog(level) {
362
+ const levels = ["debug", "info", "warn", "error", "silent"];
363
+ return levels.indexOf(level) >= levels.indexOf(this.level);
364
+ }
365
+ child(prefix) {
366
+ return new _ConsoleLogger(this.level, `${this.prefix}:${prefix}`);
367
+ }
368
+ // eslint-disable-next-line no-console
369
+ debug(message, ...args) {
370
+ if (this.shouldLog("debug"))
371
+ console.debug(`[${this.prefix}] ${message}`, ...args);
372
+ }
373
+ // eslint-disable-next-line no-console
374
+ info(message, ...args) {
375
+ if (this.shouldLog("info"))
376
+ console.info(`[${this.prefix}] ${message}`, ...args);
377
+ }
378
+ // eslint-disable-next-line no-console
379
+ warn(message, ...args) {
380
+ if (this.shouldLog("warn"))
381
+ console.warn(`[${this.prefix}] ${message}`, ...args);
382
+ }
383
+ // eslint-disable-next-line no-console
384
+ error(message, ...args) {
385
+ if (this.shouldLog("error"))
386
+ console.error(`[${this.prefix}] ${message}`, ...args);
387
+ }
388
+ };
389
+ var ChatError = class extends Error {
390
+ constructor(message, code, cause) {
391
+ super(message);
392
+ this.code = code;
393
+ this.cause = cause;
394
+ this.name = "ChatError";
395
+ }
396
+ };
397
+ var RateLimitError = class extends ChatError {
398
+ constructor(message, retryAfterMs, cause) {
399
+ super(message, "RATE_LIMITED", cause);
400
+ this.retryAfterMs = retryAfterMs;
401
+ this.name = "RateLimitError";
402
+ }
403
+ };
404
+ var LockError = class extends ChatError {
405
+ constructor(message, cause) {
406
+ super(message, "LOCK_FAILED", cause);
407
+ this.name = "LockError";
408
+ }
409
+ };
410
+ var NotImplementedError = class extends ChatError {
411
+ constructor(message, feature, cause) {
412
+ super(message, "NOT_IMPLEMENTED", cause);
413
+ this.feature = feature;
414
+ this.name = "NotImplementedError";
415
+ }
416
+ };
417
+
418
+ // src/chat.ts
419
+ var DEFAULT_LOCK_TTL_MS = 3e4;
420
+ var DEDUPE_TTL_MS = 6e4;
421
+ var Chat = class {
422
+ adapters;
423
+ state;
424
+ userName;
425
+ logger;
426
+ mentionHandlers = [];
427
+ messagePatterns = [];
428
+ subscribedMessageHandlers = [];
429
+ reactionHandlers = [];
430
+ actionHandlers = [];
431
+ /** Initialization state */
432
+ initPromise = null;
433
+ initialized = false;
434
+ /**
435
+ * Type-safe webhook handlers keyed by adapter name.
436
+ * @example
437
+ * chat.webhooks.slack(request, { backgroundTask: waitUntil });
438
+ */
439
+ webhooks;
440
+ constructor(config) {
441
+ this.userName = config.userName;
442
+ this.state = config.state;
443
+ this.adapters = /* @__PURE__ */ new Map();
444
+ if (!config.logger) {
445
+ this.logger = new ConsoleLogger("info");
446
+ } else if (typeof config.logger === "string") {
447
+ this.logger = new ConsoleLogger(config.logger);
448
+ } else {
449
+ this.logger = config.logger;
450
+ }
451
+ const webhooks = {};
452
+ for (const [name, adapter] of Object.entries(config.adapters)) {
453
+ this.adapters.set(name, adapter);
454
+ webhooks[name] = (request, options) => this.handleWebhook(name, request, options);
455
+ }
456
+ this.webhooks = webhooks;
457
+ this.logger.debug("Chat instance created", {
458
+ adapters: Object.keys(config.adapters)
459
+ });
460
+ }
461
+ /**
462
+ * Handle a webhook request for a specific adapter.
463
+ * Automatically initializes adapters on first call.
464
+ */
465
+ async handleWebhook(adapterName, request, options) {
466
+ await this.ensureInitialized();
467
+ const adapter = this.adapters.get(adapterName);
468
+ if (!adapter) {
469
+ return new Response(`Unknown adapter: ${adapterName}`, { status: 404 });
470
+ }
471
+ return adapter.handleWebhook(request, options);
472
+ }
473
+ /**
474
+ * Ensure the chat instance is initialized.
475
+ * This is called automatically before handling webhooks.
476
+ */
477
+ async ensureInitialized() {
478
+ if (this.initialized) {
479
+ return;
480
+ }
481
+ if (!this.initPromise) {
482
+ this.initPromise = this.doInitialize();
483
+ }
484
+ await this.initPromise;
485
+ }
486
+ async doInitialize() {
487
+ this.logger.info("Initializing chat instance...");
488
+ await this.state.connect();
489
+ this.logger.debug("State connected");
490
+ const initPromises = Array.from(this.adapters.values()).map(
491
+ async (adapter) => {
492
+ this.logger.debug("Initializing adapter", adapter.name);
493
+ const result = await adapter.initialize(this);
494
+ this.logger.debug("Adapter initialized", adapter.name);
495
+ return result;
496
+ }
497
+ );
498
+ await Promise.all(initPromises);
499
+ this.initialized = true;
500
+ this.logger.info("Chat instance initialized", {
501
+ adapters: Array.from(this.adapters.keys())
502
+ });
503
+ }
504
+ /**
505
+ * Gracefully shut down the chat instance.
506
+ */
507
+ async shutdown() {
508
+ this.logger.info("Shutting down chat instance...");
509
+ await this.state.disconnect();
510
+ this.initialized = false;
511
+ this.initPromise = null;
512
+ this.logger.info("Chat instance shut down");
513
+ }
514
+ /**
515
+ * Register a handler for new @-mentions of the bot.
516
+ *
517
+ * **Important**: This handler is ONLY called for mentions in **unsubscribed** threads.
518
+ * Once a thread is subscribed (via `thread.subscribe()`), subsequent messages
519
+ * including @-mentions go to `onSubscribedMessage` handlers instead.
520
+ *
521
+ * To detect mentions in subscribed threads, check `message.isMention`:
522
+ *
523
+ * @example
524
+ * ```typescript
525
+ * // Handle new mentions (unsubscribed threads only)
526
+ * chat.onNewMention(async (thread, message) => {
527
+ * await thread.subscribe(); // Subscribe to follow-up messages
528
+ * await thread.post("Hello! I'll be watching this thread.");
529
+ * });
530
+ *
531
+ * // Handle all messages in subscribed threads
532
+ * chat.onSubscribedMessage(async (thread, message) => {
533
+ * if (message.isMention) {
534
+ * // User @-mentioned us in a thread we're already watching
535
+ * await thread.post("You mentioned me again!");
536
+ * }
537
+ * });
538
+ * ```
539
+ */
540
+ onNewMention(handler) {
541
+ this.mentionHandlers.push(handler);
542
+ this.logger.debug("Registered mention handler");
543
+ }
544
+ /**
545
+ * Register a handler for messages matching a regex pattern.
546
+ *
547
+ * @param pattern - Regular expression to match against message text
548
+ * @param handler - Handler called when pattern matches
549
+ *
550
+ * @example
551
+ * ```typescript
552
+ * // Match messages starting with "!help"
553
+ * chat.onNewMessage(/^!help/, async (thread, message) => {
554
+ * await thread.post("Available commands: !help, !status, !ping");
555
+ * });
556
+ * ```
557
+ */
558
+ onNewMessage(pattern, handler) {
559
+ this.messagePatterns.push({ pattern, handler });
560
+ this.logger.debug("Registered message pattern handler", {
561
+ pattern: pattern.toString()
562
+ });
563
+ }
564
+ /**
565
+ * Register a handler for messages in subscribed threads.
566
+ *
567
+ * Called for all messages in threads that have been subscribed via `thread.subscribe()`.
568
+ * This includes:
569
+ * - Follow-up messages from users
570
+ * - Messages that @-mention the bot (check `message.isMention`)
571
+ *
572
+ * Does NOT fire for:
573
+ * - The message that triggered the subscription (e.g., the initial @mention)
574
+ * - Messages sent by the bot itself
575
+ *
576
+ * @example
577
+ * ```typescript
578
+ * chat.onSubscribedMessage(async (thread, message) => {
579
+ * // Handle all follow-up messages
580
+ * if (message.isMention) {
581
+ * // User @-mentioned us in a subscribed thread
582
+ * }
583
+ * await thread.post(`Got your message: ${message.text}`);
584
+ * });
585
+ * ```
586
+ */
587
+ onSubscribedMessage(handler) {
588
+ this.subscribedMessageHandlers.push(handler);
589
+ this.logger.debug("Registered subscribed message handler");
590
+ }
591
+ onReaction(emojiOrHandler, handler) {
592
+ if (typeof emojiOrHandler === "function") {
593
+ this.reactionHandlers.push({ emoji: [], handler: emojiOrHandler });
594
+ this.logger.debug("Registered reaction handler for all emoji");
595
+ } else if (handler) {
596
+ this.reactionHandlers.push({ emoji: emojiOrHandler, handler });
597
+ this.logger.debug("Registered reaction handler", {
598
+ emoji: emojiOrHandler.map((e) => typeof e === "string" ? e : e.name)
599
+ });
600
+ }
601
+ }
602
+ onAction(actionIdOrHandler, handler) {
603
+ if (typeof actionIdOrHandler === "function") {
604
+ this.actionHandlers.push({ actionIds: [], handler: actionIdOrHandler });
605
+ this.logger.debug("Registered action handler for all actions");
606
+ } else if (handler) {
607
+ const actionIds = Array.isArray(actionIdOrHandler) ? actionIdOrHandler : [actionIdOrHandler];
608
+ this.actionHandlers.push({ actionIds, handler });
609
+ this.logger.debug("Registered action handler", { actionIds });
610
+ }
611
+ }
612
+ /**
613
+ * Get an adapter by name with type safety.
614
+ */
615
+ getAdapter(name) {
616
+ return this.adapters.get(name);
617
+ }
618
+ // ChatInstance interface implementations
619
+ /**
620
+ * Process an incoming message from an adapter.
621
+ * Handles waitUntil registration and error catching internally.
622
+ * Adapters should call this instead of handleIncomingMessage directly.
623
+ */
624
+ processMessage(adapter, threadId, messageOrFactory, options) {
625
+ const task = (async () => {
626
+ const message = typeof messageOrFactory === "function" ? await messageOrFactory() : messageOrFactory;
627
+ await this.handleIncomingMessage(adapter, threadId, message);
628
+ })().catch((err) => {
629
+ this.logger.error("Message processing error", { error: err, threadId });
630
+ });
631
+ if (options?.waitUntil) {
632
+ options.waitUntil(task);
633
+ }
634
+ }
635
+ /**
636
+ * Process an incoming reaction event from an adapter.
637
+ * Handles waitUntil registration and error catching internally.
638
+ */
639
+ processReaction(event, options) {
640
+ const task = this.handleReactionEvent(event).catch((err) => {
641
+ this.logger.error("Reaction processing error", {
642
+ error: err,
643
+ emoji: event.emoji,
644
+ messageId: event.messageId
645
+ });
646
+ });
647
+ if (options?.waitUntil) {
648
+ options.waitUntil(task);
649
+ }
650
+ }
651
+ /**
652
+ * Process an incoming action event (button click) from an adapter.
653
+ * Handles waitUntil registration and error catching internally.
654
+ */
655
+ processAction(event, options) {
656
+ const task = this.handleActionEvent(event).catch((err) => {
657
+ this.logger.error("Action processing error", {
658
+ error: err,
659
+ actionId: event.actionId,
660
+ messageId: event.messageId
661
+ });
662
+ });
663
+ if (options?.waitUntil) {
664
+ options.waitUntil(task);
665
+ }
666
+ }
667
+ /**
668
+ * Handle an action event internally.
669
+ */
670
+ async handleActionEvent(event) {
671
+ this.logger.debug("Incoming action", {
672
+ adapter: event.adapter.name,
673
+ actionId: event.actionId,
674
+ value: event.value,
675
+ user: event.user.userName,
676
+ messageId: event.messageId,
677
+ threadId: event.threadId
678
+ });
679
+ if (event.user.isMe) {
680
+ this.logger.debug("Skipping action from self", {
681
+ actionId: event.actionId
682
+ });
683
+ return;
684
+ }
685
+ const isSubscribed = await this.state.isSubscribed(event.threadId);
686
+ const thread = await this.createThread(
687
+ event.adapter,
688
+ event.threadId,
689
+ {},
690
+ isSubscribed
691
+ );
692
+ const fullEvent = {
693
+ ...event,
694
+ thread
695
+ };
696
+ this.logger.debug("Checking action handlers", {
697
+ handlerCount: this.actionHandlers.length,
698
+ actionId: event.actionId
699
+ });
700
+ for (const { actionIds, handler } of this.actionHandlers) {
701
+ if (actionIds.length === 0) {
702
+ this.logger.debug("Running catch-all action handler");
703
+ await handler(fullEvent);
704
+ continue;
705
+ }
706
+ if (actionIds.includes(event.actionId)) {
707
+ this.logger.debug("Running matched action handler", {
708
+ actionId: event.actionId
709
+ });
710
+ await handler(fullEvent);
711
+ }
712
+ }
713
+ }
714
+ /**
715
+ * Handle a reaction event internally.
716
+ */
717
+ async handleReactionEvent(event) {
718
+ this.logger.debug("Incoming reaction", {
719
+ adapter: event.adapter?.name,
720
+ emoji: event.emoji,
721
+ rawEmoji: event.rawEmoji,
722
+ added: event.added,
723
+ user: event.user.userName,
724
+ messageId: event.messageId,
725
+ threadId: event.threadId
726
+ });
727
+ if (event.user.isMe) {
728
+ this.logger.debug("Skipping reaction from self", {
729
+ emoji: event.emoji
730
+ });
731
+ return;
732
+ }
733
+ if (!event.adapter) {
734
+ this.logger.error("Reaction event missing adapter");
735
+ return;
736
+ }
737
+ const isSubscribed = await this.state.isSubscribed(event.threadId);
738
+ const thread = await this.createThread(
739
+ event.adapter,
740
+ event.threadId,
741
+ event.message ?? {},
742
+ isSubscribed
743
+ );
744
+ const fullEvent = {
745
+ ...event,
746
+ adapter: event.adapter,
747
+ thread
748
+ };
749
+ this.logger.debug("Checking reaction handlers", {
750
+ handlerCount: this.reactionHandlers.length,
751
+ emoji: event.emoji.name,
752
+ rawEmoji: event.rawEmoji
753
+ });
754
+ for (const { emoji: emojiFilter, handler } of this.reactionHandlers) {
755
+ if (emojiFilter.length === 0) {
756
+ this.logger.debug("Running catch-all reaction handler");
757
+ await handler(fullEvent);
758
+ continue;
759
+ }
760
+ const matches = emojiFilter.some((filter) => {
761
+ if (filter === fullEvent.emoji) return true;
762
+ const filterName = typeof filter === "string" ? filter : filter.name;
763
+ return filterName === fullEvent.emoji.name || filterName === fullEvent.rawEmoji;
764
+ });
765
+ this.logger.debug("Reaction filter check", {
766
+ filterEmoji: emojiFilter.map(
767
+ (e) => typeof e === "string" ? e : e.name
768
+ ),
769
+ eventEmoji: fullEvent.emoji.name,
770
+ matches
771
+ });
772
+ if (matches) {
773
+ this.logger.debug("Running matched reaction handler");
774
+ await handler(fullEvent);
775
+ }
776
+ }
777
+ }
778
+ getState() {
779
+ return this.state;
780
+ }
781
+ getUserName() {
782
+ return this.userName;
783
+ }
784
+ getLogger(prefix) {
785
+ if (prefix) {
786
+ return this.logger.child(prefix);
787
+ }
788
+ return this.logger;
789
+ }
790
+ /**
791
+ * Open a direct message conversation with a user.
792
+ *
793
+ * Accepts either a user ID string or an Author object (from message.author or event.user).
794
+ *
795
+ * The adapter is automatically inferred from the userId format:
796
+ * - Slack: `U...` (e.g., "U03STHCA1JM")
797
+ * - Teams: `29:...` (e.g., "29:198PbJuw...")
798
+ * - Google Chat: `users/...` (e.g., "users/117994873354375860089")
799
+ *
800
+ * @param user - Platform-specific user ID string, or an Author object
801
+ * @returns A Thread that can be used to post messages
802
+ *
803
+ * @example
804
+ * ```ts
805
+ * // Using user ID directly
806
+ * const dmThread = await chat.openDM("U123456");
807
+ * await dmThread.post("Hello via DM!");
808
+ *
809
+ * // Using Author object from a message
810
+ * chat.onSubscribedMessage(async (thread, message) => {
811
+ * const dmThread = await chat.openDM(message.author);
812
+ * await dmThread.post("Hello via DM!");
813
+ * });
814
+ * ```
815
+ */
816
+ async openDM(user) {
817
+ const userId = typeof user === "string" ? user : user.userId;
818
+ const adapter = this.inferAdapterFromUserId(userId);
819
+ if (!adapter.openDM) {
820
+ throw new ChatError(
821
+ `Adapter "${adapter.name}" does not support openDM`,
822
+ "NOT_SUPPORTED"
823
+ );
824
+ }
825
+ const threadId = await adapter.openDM(userId);
826
+ return this.createThread(adapter, threadId, {}, false);
827
+ }
828
+ /**
829
+ * Infer which adapter to use based on the userId format.
830
+ */
831
+ inferAdapterFromUserId(userId) {
832
+ if (userId.startsWith("users/")) {
833
+ const adapter = this.adapters.get("gchat");
834
+ if (adapter) return adapter;
835
+ }
836
+ if (userId.startsWith("29:")) {
837
+ const adapter = this.adapters.get("teams");
838
+ if (adapter) return adapter;
839
+ }
840
+ if (/^U[A-Z0-9]+$/i.test(userId)) {
841
+ const adapter = this.adapters.get("slack");
842
+ if (adapter) return adapter;
843
+ }
844
+ throw new ChatError(
845
+ `Cannot infer adapter from userId "${userId}". Expected format: Slack (U...), Teams (29:...), or Google Chat (users/...).`,
846
+ "UNKNOWN_USER_ID_FORMAT"
847
+ );
848
+ }
849
+ /**
850
+ * Handle an incoming message from an adapter.
851
+ * This is called by adapters when they receive a webhook.
852
+ *
853
+ * The Chat class handles common concerns centrally:
854
+ * - Deduplication: Same message may arrive multiple times (e.g., Slack sends
855
+ * both `message` and `app_mention` events, GChat sends direct webhook + Pub/Sub)
856
+ * - Bot filtering: Messages from the bot itself are skipped
857
+ * - Locking: Only one instance processes a thread at a time
858
+ */
859
+ async handleIncomingMessage(adapter, threadId, message) {
860
+ this.logger.debug("Incoming message", {
861
+ adapter: adapter.name,
862
+ threadId,
863
+ messageId: message.id,
864
+ text: message.text,
865
+ author: message.author.userName,
866
+ authorUserId: message.author.userId,
867
+ isBot: message.author.isBot,
868
+ isMe: message.author.isMe
869
+ });
870
+ if (message.author.isMe) {
871
+ this.logger.debug("Skipping message from self (isMe=true)", {
872
+ adapter: adapter.name,
873
+ threadId,
874
+ author: message.author.userName
875
+ });
876
+ return;
877
+ }
878
+ const dedupeKey = `dedupe:${adapter.name}:${message.id}`;
879
+ const alreadyProcessed = await this.state.get(dedupeKey);
880
+ if (alreadyProcessed) {
881
+ this.logger.debug("Skipping duplicate message", {
882
+ adapter: adapter.name,
883
+ messageId: message.id
884
+ });
885
+ return;
886
+ }
887
+ await this.state.set(dedupeKey, true, DEDUPE_TTL_MS);
888
+ const lock = await this.state.acquireLock(threadId, DEFAULT_LOCK_TTL_MS);
889
+ if (!lock) {
890
+ this.logger.warn("Could not acquire lock on thread", { threadId });
891
+ throw new LockError(
892
+ `Could not acquire lock on thread ${threadId}. Another instance may be processing.`
893
+ );
894
+ }
895
+ this.logger.debug("Lock acquired", { threadId, token: lock.token });
896
+ try {
897
+ message.isMention = this.detectMention(adapter, message);
898
+ const isSubscribed = await this.state.isSubscribed(threadId);
899
+ this.logger.debug("Subscription check", {
900
+ threadId,
901
+ isSubscribed,
902
+ subscribedHandlerCount: this.subscribedMessageHandlers.length
903
+ });
904
+ const thread = await this.createThread(
905
+ adapter,
906
+ threadId,
907
+ message,
908
+ isSubscribed
909
+ );
910
+ if (isSubscribed) {
911
+ this.logger.debug("Message in subscribed thread - calling handlers", {
912
+ threadId,
913
+ handlerCount: this.subscribedMessageHandlers.length
914
+ });
915
+ await this.runHandlers(this.subscribedMessageHandlers, thread, message);
916
+ return;
917
+ }
918
+ if (message.isMention) {
919
+ this.logger.debug("Bot mentioned", {
920
+ threadId,
921
+ text: message.text.slice(0, 100)
922
+ });
923
+ await this.runHandlers(this.mentionHandlers, thread, message);
924
+ return;
925
+ }
926
+ this.logger.debug("Checking message patterns", {
927
+ patternCount: this.messagePatterns.length,
928
+ patterns: this.messagePatterns.map((p) => p.pattern.toString()),
929
+ messageText: message.text
930
+ });
931
+ let matchedPattern = false;
932
+ for (const { pattern, handler } of this.messagePatterns) {
933
+ const matches = pattern.test(message.text);
934
+ this.logger.debug("Pattern test", {
935
+ pattern: pattern.toString(),
936
+ text: message.text,
937
+ matches
938
+ });
939
+ if (matches) {
940
+ this.logger.debug("Message matched pattern - calling handler", {
941
+ pattern: pattern.toString()
942
+ });
943
+ matchedPattern = true;
944
+ await handler(thread, message);
945
+ }
946
+ }
947
+ if (!matchedPattern) {
948
+ this.logger.debug("No handlers matched message", {
949
+ threadId,
950
+ text: message.text.slice(0, 100)
951
+ });
952
+ }
953
+ } finally {
954
+ await this.state.releaseLock(lock);
955
+ this.logger.debug("Lock released", { threadId });
956
+ }
957
+ }
958
+ async createThread(adapter, threadId, initialMessage, isSubscribedContext = false) {
959
+ const parts = threadId.split(":");
960
+ const channelId = parts[1] || "";
961
+ const isDM = adapter.isDM?.(threadId) ?? false;
962
+ return new ThreadImpl({
963
+ id: threadId,
964
+ adapter,
965
+ channelId,
966
+ state: this.state,
967
+ initialMessage,
968
+ isSubscribedContext,
969
+ isDM
970
+ });
971
+ }
972
+ /**
973
+ * Detect if the bot was mentioned in the message.
974
+ * All adapters normalize mentions to @name format, so we just check for @username.
975
+ */
976
+ detectMention(adapter, message) {
977
+ const botUserName = adapter.userName || this.userName;
978
+ const botUserId = adapter.botUserId;
979
+ const usernamePattern = new RegExp(
980
+ `@${this.escapeRegex(botUserName)}\\b`,
981
+ "i"
982
+ );
983
+ if (usernamePattern.test(message.text)) {
984
+ return true;
985
+ }
986
+ if (botUserId) {
987
+ const userIdPattern = new RegExp(
988
+ `@${this.escapeRegex(botUserId)}\\b`,
989
+ "i"
990
+ );
991
+ if (userIdPattern.test(message.text)) {
992
+ return true;
993
+ }
994
+ }
995
+ return false;
996
+ }
997
+ escapeRegex(str) {
998
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
999
+ }
1000
+ async runHandlers(handlers, thread, message) {
1001
+ for (const handler of handlers) {
1002
+ await handler(thread, message);
1003
+ }
1004
+ }
1005
+ };
1006
+
1007
+ // src/emoji.ts
1008
+ var emojiRegistry = /* @__PURE__ */ new Map();
1009
+ function getEmoji(name) {
1010
+ let emojiValue = emojiRegistry.get(name);
1011
+ if (!emojiValue) {
1012
+ emojiValue = Object.freeze({
1013
+ name,
1014
+ toString: () => `{{emoji:${name}}}`,
1015
+ toJSON: () => `{{emoji:${name}}}`
1016
+ });
1017
+ emojiRegistry.set(name, emojiValue);
1018
+ }
1019
+ return emojiValue;
1020
+ }
1021
+ var DEFAULT_EMOJI_MAP = {
1022
+ // Reactions & Gestures
1023
+ thumbs_up: { slack: ["+1", "thumbsup"], gchat: "\u{1F44D}" },
1024
+ thumbs_down: { slack: ["-1", "thumbsdown"], gchat: "\u{1F44E}" },
1025
+ clap: { slack: "clap", gchat: "\u{1F44F}" },
1026
+ wave: { slack: "wave", gchat: "\u{1F44B}" },
1027
+ pray: { slack: "pray", gchat: "\u{1F64F}" },
1028
+ muscle: { slack: "muscle", gchat: "\u{1F4AA}" },
1029
+ ok_hand: { slack: "ok_hand", gchat: "\u{1F44C}" },
1030
+ point_up: { slack: "point_up", gchat: "\u{1F446}" },
1031
+ point_down: { slack: "point_down", gchat: "\u{1F447}" },
1032
+ point_left: { slack: "point_left", gchat: "\u{1F448}" },
1033
+ point_right: { slack: "point_right", gchat: "\u{1F449}" },
1034
+ raised_hands: { slack: "raised_hands", gchat: "\u{1F64C}" },
1035
+ shrug: { slack: "shrug", gchat: "\u{1F937}" },
1036
+ facepalm: { slack: "facepalm", gchat: "\u{1F926}" },
1037
+ // Emotions & Faces
1038
+ heart: { slack: "heart", gchat: ["\u2764\uFE0F", "\u2764"] },
1039
+ smile: { slack: ["smile", "slightly_smiling_face"], gchat: "\u{1F60A}" },
1040
+ laugh: { slack: ["laughing", "satisfied", "joy"], gchat: ["\u{1F602}", "\u{1F606}"] },
1041
+ thinking: { slack: "thinking_face", gchat: "\u{1F914}" },
1042
+ sad: { slack: ["cry", "sad", "white_frowning_face"], gchat: "\u{1F622}" },
1043
+ cry: { slack: "sob", gchat: "\u{1F62D}" },
1044
+ angry: { slack: "angry", gchat: "\u{1F620}" },
1045
+ love_eyes: { slack: "heart_eyes", gchat: "\u{1F60D}" },
1046
+ cool: { slack: "sunglasses", gchat: "\u{1F60E}" },
1047
+ wink: { slack: "wink", gchat: "\u{1F609}" },
1048
+ surprised: { slack: "open_mouth", gchat: "\u{1F62E}" },
1049
+ worried: { slack: "worried", gchat: "\u{1F61F}" },
1050
+ confused: { slack: "confused", gchat: "\u{1F615}" },
1051
+ neutral: { slack: "neutral_face", gchat: "\u{1F610}" },
1052
+ sleeping: { slack: "sleeping", gchat: "\u{1F634}" },
1053
+ sick: { slack: "nauseated_face", gchat: "\u{1F922}" },
1054
+ mind_blown: { slack: "exploding_head", gchat: "\u{1F92F}" },
1055
+ relieved: { slack: "relieved", gchat: "\u{1F60C}" },
1056
+ grimace: { slack: "grimacing", gchat: "\u{1F62C}" },
1057
+ rolling_eyes: { slack: "rolling_eyes", gchat: "\u{1F644}" },
1058
+ hug: { slack: "hugging_face", gchat: "\u{1F917}" },
1059
+ zany: { slack: "zany_face", gchat: "\u{1F92A}" },
1060
+ // Status & Symbols
1061
+ check: {
1062
+ slack: ["white_check_mark", "heavy_check_mark"],
1063
+ gchat: ["\u2705", "\u2714\uFE0F"]
1064
+ },
1065
+ x: { slack: ["x", "heavy_multiplication_x"], gchat: ["\u274C", "\u2716\uFE0F"] },
1066
+ question: { slack: "question", gchat: ["\u2753", "?"] },
1067
+ exclamation: { slack: "exclamation", gchat: "\u2757" },
1068
+ warning: { slack: "warning", gchat: "\u26A0\uFE0F" },
1069
+ stop: { slack: "octagonal_sign", gchat: "\u{1F6D1}" },
1070
+ info: { slack: "information_source", gchat: "\u2139\uFE0F" },
1071
+ "100": { slack: "100", gchat: "\u{1F4AF}" },
1072
+ fire: { slack: "fire", gchat: "\u{1F525}" },
1073
+ star: { slack: "star", gchat: "\u2B50" },
1074
+ sparkles: { slack: "sparkles", gchat: "\u2728" },
1075
+ lightning: { slack: "zap", gchat: "\u26A1" },
1076
+ boom: { slack: "boom", gchat: "\u{1F4A5}" },
1077
+ eyes: { slack: "eyes", gchat: "\u{1F440}" },
1078
+ // Status Indicators (colored circles)
1079
+ green_circle: { slack: "large_green_circle", gchat: "\u{1F7E2}" },
1080
+ yellow_circle: { slack: "large_yellow_circle", gchat: "\u{1F7E1}" },
1081
+ red_circle: { slack: "red_circle", gchat: "\u{1F534}" },
1082
+ blue_circle: { slack: "large_blue_circle", gchat: "\u{1F535}" },
1083
+ white_circle: { slack: "white_circle", gchat: "\u26AA" },
1084
+ black_circle: { slack: "black_circle", gchat: "\u26AB" },
1085
+ // Objects & Tools
1086
+ rocket: { slack: "rocket", gchat: "\u{1F680}" },
1087
+ party: { slack: ["tada", "partying_face"], gchat: ["\u{1F389}", "\u{1F973}"] },
1088
+ confetti: { slack: "confetti_ball", gchat: "\u{1F38A}" },
1089
+ balloon: { slack: "balloon", gchat: "\u{1F388}" },
1090
+ gift: { slack: "gift", gchat: "\u{1F381}" },
1091
+ trophy: { slack: "trophy", gchat: "\u{1F3C6}" },
1092
+ medal: { slack: "first_place_medal", gchat: "\u{1F947}" },
1093
+ lightbulb: { slack: "bulb", gchat: "\u{1F4A1}" },
1094
+ gear: { slack: "gear", gchat: "\u2699\uFE0F" },
1095
+ wrench: { slack: "wrench", gchat: "\u{1F527}" },
1096
+ hammer: { slack: "hammer", gchat: "\u{1F528}" },
1097
+ bug: { slack: "bug", gchat: "\u{1F41B}" },
1098
+ link: { slack: "link", gchat: "\u{1F517}" },
1099
+ lock: { slack: "lock", gchat: "\u{1F512}" },
1100
+ unlock: { slack: "unlock", gchat: "\u{1F513}" },
1101
+ key: { slack: "key", gchat: "\u{1F511}" },
1102
+ pin: { slack: "pushpin", gchat: "\u{1F4CC}" },
1103
+ memo: { slack: "memo", gchat: "\u{1F4DD}" },
1104
+ clipboard: { slack: "clipboard", gchat: "\u{1F4CB}" },
1105
+ calendar: { slack: "calendar", gchat: "\u{1F4C5}" },
1106
+ clock: { slack: "clock1", gchat: "\u{1F550}" },
1107
+ hourglass: { slack: "hourglass", gchat: "\u23F3" },
1108
+ bell: { slack: "bell", gchat: "\u{1F514}" },
1109
+ megaphone: { slack: "mega", gchat: "\u{1F4E2}" },
1110
+ speech_bubble: { slack: "speech_balloon", gchat: "\u{1F4AC}" },
1111
+ email: { slack: "email", gchat: "\u{1F4E7}" },
1112
+ inbox: { slack: "inbox_tray", gchat: "\u{1F4E5}" },
1113
+ outbox: { slack: "outbox_tray", gchat: "\u{1F4E4}" },
1114
+ package: { slack: "package", gchat: "\u{1F4E6}" },
1115
+ folder: { slack: "file_folder", gchat: "\u{1F4C1}" },
1116
+ file: { slack: "page_facing_up", gchat: "\u{1F4C4}" },
1117
+ chart_up: { slack: "chart_with_upwards_trend", gchat: "\u{1F4C8}" },
1118
+ chart_down: { slack: "chart_with_downwards_trend", gchat: "\u{1F4C9}" },
1119
+ coffee: { slack: "coffee", gchat: "\u2615" },
1120
+ pizza: { slack: "pizza", gchat: "\u{1F355}" },
1121
+ beer: { slack: "beer", gchat: "\u{1F37A}" },
1122
+ // Arrows & Directions
1123
+ arrow_up: { slack: "arrow_up", gchat: "\u2B06\uFE0F" },
1124
+ arrow_down: { slack: "arrow_down", gchat: "\u2B07\uFE0F" },
1125
+ arrow_left: { slack: "arrow_left", gchat: "\u2B05\uFE0F" },
1126
+ arrow_right: { slack: "arrow_right", gchat: "\u27A1\uFE0F" },
1127
+ refresh: { slack: "arrows_counterclockwise", gchat: "\u{1F504}" },
1128
+ // Nature & Weather
1129
+ sun: { slack: "sunny", gchat: "\u2600\uFE0F" },
1130
+ cloud: { slack: "cloud", gchat: "\u2601\uFE0F" },
1131
+ rain: { slack: "rain_cloud", gchat: "\u{1F327}\uFE0F" },
1132
+ snow: { slack: "snowflake", gchat: "\u2744\uFE0F" },
1133
+ rainbow: { slack: "rainbow", gchat: "\u{1F308}" }
1134
+ };
1135
+ var EmojiResolver = class {
1136
+ emojiMap;
1137
+ slackToNormalized;
1138
+ gchatToNormalized;
1139
+ constructor(customMap) {
1140
+ this.emojiMap = { ...DEFAULT_EMOJI_MAP, ...customMap };
1141
+ this.slackToNormalized = /* @__PURE__ */ new Map();
1142
+ this.gchatToNormalized = /* @__PURE__ */ new Map();
1143
+ this.buildReverseMaps();
1144
+ }
1145
+ buildReverseMaps() {
1146
+ for (const [normalized, formats] of Object.entries(this.emojiMap)) {
1147
+ const slackFormats = Array.isArray(formats.slack) ? formats.slack : [formats.slack];
1148
+ for (const slack of slackFormats) {
1149
+ this.slackToNormalized.set(slack.toLowerCase(), normalized);
1150
+ }
1151
+ const gchatFormats = Array.isArray(formats.gchat) ? formats.gchat : [formats.gchat];
1152
+ for (const gchat of gchatFormats) {
1153
+ this.gchatToNormalized.set(gchat, normalized);
1154
+ }
1155
+ }
1156
+ }
1157
+ /**
1158
+ * Convert a Slack emoji name to normalized EmojiValue.
1159
+ * Returns an EmojiValue for the raw emoji if no mapping exists.
1160
+ */
1161
+ fromSlack(slackEmoji) {
1162
+ const cleaned = slackEmoji.replace(/^:|:$/g, "").toLowerCase();
1163
+ const normalized = this.slackToNormalized.get(cleaned) ?? slackEmoji;
1164
+ return getEmoji(normalized);
1165
+ }
1166
+ /**
1167
+ * Convert a Google Chat unicode emoji to normalized EmojiValue.
1168
+ * Returns an EmojiValue for the raw emoji if no mapping exists.
1169
+ */
1170
+ fromGChat(gchatEmoji) {
1171
+ const normalized = this.gchatToNormalized.get(gchatEmoji) ?? gchatEmoji;
1172
+ return getEmoji(normalized);
1173
+ }
1174
+ /**
1175
+ * Convert a Teams reaction type to normalized EmojiValue.
1176
+ * Teams uses specific names: like, heart, laugh, surprised, sad, angry
1177
+ * Returns an EmojiValue for the raw reaction if no mapping exists.
1178
+ */
1179
+ fromTeams(teamsReaction) {
1180
+ const teamsMap = {
1181
+ like: "thumbs_up",
1182
+ heart: "heart",
1183
+ laugh: "laugh",
1184
+ surprised: "surprised",
1185
+ sad: "sad",
1186
+ angry: "angry"
1187
+ };
1188
+ const normalized = teamsMap[teamsReaction] ?? teamsReaction;
1189
+ return getEmoji(normalized);
1190
+ }
1191
+ /**
1192
+ * Convert a normalized emoji (or EmojiValue) to Slack format.
1193
+ * Returns the first Slack format if multiple exist.
1194
+ */
1195
+ toSlack(emoji2) {
1196
+ const name = typeof emoji2 === "string" ? emoji2 : emoji2.name;
1197
+ const formats = this.emojiMap[name];
1198
+ if (!formats) return name;
1199
+ return Array.isArray(formats.slack) ? formats.slack[0] : formats.slack;
1200
+ }
1201
+ /**
1202
+ * Convert a normalized emoji (or EmojiValue) to Google Chat format.
1203
+ * Returns the first GChat format if multiple exist.
1204
+ */
1205
+ toGChat(emoji2) {
1206
+ const name = typeof emoji2 === "string" ? emoji2 : emoji2.name;
1207
+ const formats = this.emojiMap[name];
1208
+ if (!formats) return name;
1209
+ return Array.isArray(formats.gchat) ? formats.gchat[0] : formats.gchat;
1210
+ }
1211
+ /**
1212
+ * Check if an emoji (in any format) matches a normalized emoji name or EmojiValue.
1213
+ */
1214
+ matches(rawEmoji, normalized) {
1215
+ const name = typeof normalized === "string" ? normalized : normalized.name;
1216
+ const formats = this.emojiMap[name];
1217
+ if (!formats) return rawEmoji === name;
1218
+ const slackFormats = Array.isArray(formats.slack) ? formats.slack : [formats.slack];
1219
+ const gchatFormats = Array.isArray(formats.gchat) ? formats.gchat : [formats.gchat];
1220
+ const cleanedRaw = rawEmoji.replace(/^:|:$/g, "").toLowerCase();
1221
+ return slackFormats.some((s) => s.toLowerCase() === cleanedRaw) || gchatFormats.includes(rawEmoji);
1222
+ }
1223
+ /**
1224
+ * Add or override emoji mappings.
1225
+ */
1226
+ extend(customMap) {
1227
+ Object.assign(this.emojiMap, customMap);
1228
+ this.buildReverseMaps();
1229
+ }
1230
+ };
1231
+ var defaultEmojiResolver = new EmojiResolver();
1232
+ var EMOJI_PLACEHOLDER_REGEX = /\{\{emoji:([a-z0-9_]+)\}\}/gi;
1233
+ function convertEmojiPlaceholders(text2, platform, resolver = defaultEmojiResolver) {
1234
+ return text2.replace(EMOJI_PLACEHOLDER_REGEX, (_, emojiName) => {
1235
+ switch (platform) {
1236
+ case "slack":
1237
+ return `:${resolver.toSlack(emojiName)}:`;
1238
+ case "gchat":
1239
+ return resolver.toGChat(emojiName);
1240
+ case "teams":
1241
+ return resolver.toGChat(emojiName);
1242
+ default:
1243
+ return resolver.toGChat(emojiName);
1244
+ }
1245
+ });
1246
+ }
1247
+ function createEmoji(customEmoji) {
1248
+ const wellKnownEmoji = [
1249
+ // Reactions & Gestures
1250
+ "thumbs_up",
1251
+ "thumbs_down",
1252
+ "clap",
1253
+ "wave",
1254
+ "pray",
1255
+ "muscle",
1256
+ "ok_hand",
1257
+ "point_up",
1258
+ "point_down",
1259
+ "point_left",
1260
+ "point_right",
1261
+ "raised_hands",
1262
+ "shrug",
1263
+ "facepalm",
1264
+ // Emotions & Faces
1265
+ "heart",
1266
+ "smile",
1267
+ "laugh",
1268
+ "thinking",
1269
+ "sad",
1270
+ "cry",
1271
+ "angry",
1272
+ "love_eyes",
1273
+ "cool",
1274
+ "wink",
1275
+ "surprised",
1276
+ "worried",
1277
+ "confused",
1278
+ "neutral",
1279
+ "sleeping",
1280
+ "sick",
1281
+ "mind_blown",
1282
+ "relieved",
1283
+ "grimace",
1284
+ "rolling_eyes",
1285
+ "hug",
1286
+ "zany",
1287
+ // Status & Symbols
1288
+ "check",
1289
+ "x",
1290
+ "question",
1291
+ "exclamation",
1292
+ "warning",
1293
+ "stop",
1294
+ "info",
1295
+ "100",
1296
+ "fire",
1297
+ "star",
1298
+ "sparkles",
1299
+ "lightning",
1300
+ "boom",
1301
+ "eyes",
1302
+ // Status Indicators
1303
+ "green_circle",
1304
+ "yellow_circle",
1305
+ "red_circle",
1306
+ "blue_circle",
1307
+ "white_circle",
1308
+ "black_circle",
1309
+ // Objects & Tools
1310
+ "rocket",
1311
+ "party",
1312
+ "confetti",
1313
+ "balloon",
1314
+ "gift",
1315
+ "trophy",
1316
+ "medal",
1317
+ "lightbulb",
1318
+ "gear",
1319
+ "wrench",
1320
+ "hammer",
1321
+ "bug",
1322
+ "link",
1323
+ "lock",
1324
+ "unlock",
1325
+ "key",
1326
+ "pin",
1327
+ "memo",
1328
+ "clipboard",
1329
+ "calendar",
1330
+ "clock",
1331
+ "hourglass",
1332
+ "bell",
1333
+ "megaphone",
1334
+ "speech_bubble",
1335
+ "email",
1336
+ "inbox",
1337
+ "outbox",
1338
+ "package",
1339
+ "folder",
1340
+ "file",
1341
+ "chart_up",
1342
+ "chart_down",
1343
+ "coffee",
1344
+ "pizza",
1345
+ "beer",
1346
+ // Arrows & Directions
1347
+ "arrow_up",
1348
+ "arrow_down",
1349
+ "arrow_left",
1350
+ "arrow_right",
1351
+ "refresh",
1352
+ // Nature & Weather
1353
+ "sun",
1354
+ "cloud",
1355
+ "rain",
1356
+ "snow",
1357
+ "rainbow"
1358
+ ];
1359
+ const helper = {
1360
+ custom: (name) => getEmoji(name)
1361
+ };
1362
+ for (const name of wellKnownEmoji) {
1363
+ helper[name] = getEmoji(name);
1364
+ }
1365
+ if (customEmoji) {
1366
+ for (const key of Object.keys(customEmoji)) {
1367
+ helper[key] = getEmoji(key);
1368
+ }
1369
+ defaultEmojiResolver.extend(customEmoji);
1370
+ }
1371
+ return helper;
1372
+ }
1373
+ var emoji = createEmoji();
1374
+
1375
+ // src/index.ts
1376
+ var Actions2 = Actions;
1377
+ var Button2 = Button;
1378
+ var Card2 = Card;
1379
+ var CardText2 = CardText;
1380
+ var Divider2 = Divider;
1381
+ var Field2 = Field;
1382
+ var Fields2 = Fields;
1383
+ var fromReactElement2 = fromReactElement;
1384
+ var Image2 = Image;
1385
+ var isCardElement2 = isCardElement;
1386
+ var isJSX2 = isJSX;
1387
+ var Section2 = Section;
1388
+ var toCardElement2 = toCardElement;
1389
+ export {
1390
+ Actions2 as Actions,
1391
+ BaseFormatConverter,
1392
+ Button2 as Button,
1393
+ Card2 as Card,
1394
+ CardText2 as CardText,
1395
+ Chat,
1396
+ ChatError,
1397
+ ConsoleLogger,
1398
+ DEFAULT_EMOJI_MAP,
1399
+ Divider2 as Divider,
1400
+ EmojiResolver,
1401
+ Field2 as Field,
1402
+ Fields2 as Fields,
1403
+ Image2 as Image,
1404
+ LockError,
1405
+ NotImplementedError,
1406
+ RateLimitError,
1407
+ Section2 as Section,
1408
+ blockquote,
1409
+ codeBlock,
1410
+ convertEmojiPlaceholders,
1411
+ createEmoji,
1412
+ defaultEmojiResolver,
1413
+ emoji,
1414
+ emphasis,
1415
+ fromReactElement2 as fromReactElement,
1416
+ getEmoji,
1417
+ inlineCode,
1418
+ isCardElement2 as isCardElement,
1419
+ isJSX2 as isJSX,
1420
+ link,
1421
+ markdownToPlainText,
1422
+ paragraph,
1423
+ parseMarkdown,
1424
+ root,
1425
+ strikethrough,
1426
+ stringifyMarkdown,
1427
+ strong,
1428
+ text,
1429
+ toCardElement2 as toCardElement,
1430
+ toPlainText,
1431
+ walkAst
1432
+ };
1433
+ //# sourceMappingURL=index.js.map