@chat-adapter/slack 4.20.1 → 4.20.2
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 +16 -4
- package/dist/index.js +158 -8
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -175,6 +175,7 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
|
|
|
175
175
|
private readonly formatConverter;
|
|
176
176
|
private static USER_CACHE_TTL_MS;
|
|
177
177
|
private static CHANNEL_CACHE_TTL_MS;
|
|
178
|
+
private static REVERSE_INDEX_TTL_MS;
|
|
178
179
|
private readonly clientId;
|
|
179
180
|
private readonly clientSecret;
|
|
180
181
|
private readonly encryptionKey;
|
|
@@ -291,6 +292,7 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
|
|
|
291
292
|
* Fires when a user (including the bot) joins a channel.
|
|
292
293
|
*/
|
|
293
294
|
private handleMemberJoinedChannel;
|
|
295
|
+
private handleUserChange;
|
|
294
296
|
/**
|
|
295
297
|
* Publish a Home tab view for a user.
|
|
296
298
|
* Slack API: views.publish
|
|
@@ -342,14 +344,24 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
|
|
|
342
344
|
* Includes a fetchData method that uses the bot token for auth.
|
|
343
345
|
*/
|
|
344
346
|
private createAttachment;
|
|
347
|
+
/**
|
|
348
|
+
* Resolve @name mentions in text to Slack <@USER_ID> format using the
|
|
349
|
+
* reverse user cache. When multiple users share a display name, prefers
|
|
350
|
+
* the one who is a participant in the given thread.
|
|
351
|
+
*/
|
|
352
|
+
private resolveOutgoingMentions;
|
|
353
|
+
/**
|
|
354
|
+
* Pre-process an outgoing message to resolve @name mentions before rendering.
|
|
355
|
+
*/
|
|
356
|
+
private resolveMessageMentions;
|
|
345
357
|
/**
|
|
346
358
|
* Try to render a message using native Slack table blocks.
|
|
347
359
|
* Returns blocks + fallback text if the message contains tables, null otherwise.
|
|
348
360
|
*/
|
|
349
361
|
private renderWithTableBlocks;
|
|
350
|
-
postMessage(threadId: string,
|
|
351
|
-
postEphemeral(threadId: string, userId: string,
|
|
352
|
-
scheduleMessage(threadId: string,
|
|
362
|
+
postMessage(threadId: string, _message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
|
|
363
|
+
postEphemeral(threadId: string, userId: string, _message: AdapterPostableMessage): Promise<EphemeralMessage>;
|
|
364
|
+
scheduleMessage(threadId: string, _message: AdapterPostableMessage, options: {
|
|
353
365
|
postAt: Date;
|
|
354
366
|
}): Promise<ScheduledMessage>;
|
|
355
367
|
openModal(triggerId: string, modal: ModalElement, contextId?: string): Promise<{
|
|
@@ -363,7 +375,7 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
|
|
|
363
375
|
* Returns the file IDs of uploaded files.
|
|
364
376
|
*/
|
|
365
377
|
private uploadFiles;
|
|
366
|
-
editMessage(threadId: string, messageId: string,
|
|
378
|
+
editMessage(threadId: string, messageId: string, _message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
|
|
367
379
|
deleteMessage(threadId: string, messageId: string): Promise<void>;
|
|
368
380
|
addReaction(threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
|
|
369
381
|
removeReaction(threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -708,6 +708,7 @@ function radioSelectToBlock(radioSelect) {
|
|
|
708
708
|
|
|
709
709
|
// src/index.ts
|
|
710
710
|
var SLACK_USER_ID_PATTERN = /^[A-Z0-9_]+$/;
|
|
711
|
+
var SLACK_USER_ID_EXACT_PATTERN = /^U[A-Z0-9]+$/;
|
|
711
712
|
function findNextMention(text) {
|
|
712
713
|
const atIdx = text.indexOf("<@");
|
|
713
714
|
const hashIdx = text.indexOf("<#");
|
|
@@ -732,10 +733,12 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
732
733
|
_botId = null;
|
|
733
734
|
// Bot app ID (B_xxx) - different from user ID
|
|
734
735
|
formatConverter = new SlackFormatConverter();
|
|
735
|
-
static USER_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
736
|
-
//
|
|
737
|
-
static CHANNEL_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
738
|
-
//
|
|
736
|
+
static USER_CACHE_TTL_MS = 8 * 24 * 60 * 60 * 1e3;
|
|
737
|
+
// 8 days
|
|
738
|
+
static CHANNEL_CACHE_TTL_MS = 8 * 24 * 60 * 60 * 1e3;
|
|
739
|
+
// 8 days
|
|
740
|
+
static REVERSE_INDEX_TTL_MS = 8 * 24 * 60 * 60 * 1e3;
|
|
741
|
+
// 8 days
|
|
739
742
|
// Multi-workspace support
|
|
740
743
|
clientId;
|
|
741
744
|
clientSecret;
|
|
@@ -1006,6 +1009,15 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1006
1009
|
{ displayName, realName },
|
|
1007
1010
|
_SlackAdapter.USER_CACHE_TTL_MS
|
|
1008
1011
|
);
|
|
1012
|
+
const normalizedName = displayName.toLowerCase();
|
|
1013
|
+
const reverseKey = `slack:user-by-name:${normalizedName}`;
|
|
1014
|
+
const existing = await this.chat.getState().getList(reverseKey);
|
|
1015
|
+
if (!existing.includes(userId)) {
|
|
1016
|
+
await this.chat.getState().appendToList(reverseKey, userId, {
|
|
1017
|
+
maxLength: 50,
|
|
1018
|
+
ttlMs: _SlackAdapter.REVERSE_INDEX_TTL_MS
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1009
1021
|
}
|
|
1010
1022
|
this.logger.debug("Fetched user info", {
|
|
1011
1023
|
userId,
|
|
@@ -1144,6 +1156,8 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1144
1156
|
event,
|
|
1145
1157
|
options
|
|
1146
1158
|
);
|
|
1159
|
+
} else if (event.type === "user_change") {
|
|
1160
|
+
this.handleUserChange(event);
|
|
1147
1161
|
}
|
|
1148
1162
|
}
|
|
1149
1163
|
}
|
|
@@ -1650,6 +1664,19 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1650
1664
|
options
|
|
1651
1665
|
);
|
|
1652
1666
|
}
|
|
1667
|
+
async handleUserChange(event) {
|
|
1668
|
+
if (!this.chat) {
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
try {
|
|
1672
|
+
await this.chat.getState().delete(`slack:user:${event.user.id}`);
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
this.logger.warn("Failed to invalidate user cache", {
|
|
1675
|
+
userId: event.user.id,
|
|
1676
|
+
error
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1653
1680
|
/**
|
|
1654
1681
|
* Publish a Home tab view for a user.
|
|
1655
1682
|
* Slack API: views.publish
|
|
@@ -1864,6 +1891,24 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1864
1891
|
userName = userInfo.displayName;
|
|
1865
1892
|
fullName = userInfo.realName;
|
|
1866
1893
|
}
|
|
1894
|
+
if (event.user && this.chat) {
|
|
1895
|
+
try {
|
|
1896
|
+
const participantKey = `slack:thread-participants:${threadId}`;
|
|
1897
|
+
const participants = await this.chat.getState().getList(participantKey);
|
|
1898
|
+
if (!participants.includes(event.user)) {
|
|
1899
|
+
await this.chat.getState().appendToList(participantKey, event.user, {
|
|
1900
|
+
maxLength: 100,
|
|
1901
|
+
ttlMs: _SlackAdapter.REVERSE_INDEX_TTL_MS
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
} catch (error) {
|
|
1905
|
+
this.logger.warn("Failed to track thread participant", {
|
|
1906
|
+
threadId,
|
|
1907
|
+
userId: event.user,
|
|
1908
|
+
error
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1867
1912
|
const text = await this.resolveInlineMentions(rawText, skipSelfMention);
|
|
1868
1913
|
return new Message({
|
|
1869
1914
|
id: event.ts || "",
|
|
@@ -1936,6 +1981,107 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1936
1981
|
} : void 0
|
|
1937
1982
|
};
|
|
1938
1983
|
}
|
|
1984
|
+
/**
|
|
1985
|
+
* Resolve @name mentions in text to Slack <@USER_ID> format using the
|
|
1986
|
+
* reverse user cache. When multiple users share a display name, prefers
|
|
1987
|
+
* the one who is a participant in the given thread.
|
|
1988
|
+
*/
|
|
1989
|
+
async resolveOutgoingMentions(text, threadId) {
|
|
1990
|
+
if (!this.chat) {
|
|
1991
|
+
return text;
|
|
1992
|
+
}
|
|
1993
|
+
const state = this.chat.getState();
|
|
1994
|
+
const mentionPattern = /@(\w+)/g;
|
|
1995
|
+
const mentions = /* @__PURE__ */ new Map();
|
|
1996
|
+
for (const match of text.matchAll(mentionPattern)) {
|
|
1997
|
+
const name = match[1];
|
|
1998
|
+
if (SLACK_USER_ID_EXACT_PATTERN.test(name)) {
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
const idx = match.index;
|
|
2002
|
+
if (idx > 0 && text[idx - 1] === "<") {
|
|
2003
|
+
continue;
|
|
2004
|
+
}
|
|
2005
|
+
if (!mentions.has(name.toLowerCase())) {
|
|
2006
|
+
mentions.set(name.toLowerCase(), []);
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
if (mentions.size === 0) {
|
|
2010
|
+
return text;
|
|
2011
|
+
}
|
|
2012
|
+
for (const name of mentions.keys()) {
|
|
2013
|
+
const userIds = await state.getList(`slack:user-by-name:${name}`);
|
|
2014
|
+
const unique = [...new Set(userIds)];
|
|
2015
|
+
mentions.set(name, unique);
|
|
2016
|
+
}
|
|
2017
|
+
let participants = null;
|
|
2018
|
+
const needsParticipants = [...mentions.values()].some(
|
|
2019
|
+
(ids) => ids.length > 1
|
|
2020
|
+
);
|
|
2021
|
+
if (needsParticipants) {
|
|
2022
|
+
const participantList = await state.getList(
|
|
2023
|
+
`slack:thread-participants:${threadId}`
|
|
2024
|
+
);
|
|
2025
|
+
participants = new Set(participantList);
|
|
2026
|
+
}
|
|
2027
|
+
return text.replace(
|
|
2028
|
+
mentionPattern,
|
|
2029
|
+
(match, name, offset) => {
|
|
2030
|
+
if (offset > 0 && text[offset - 1] === "<") {
|
|
2031
|
+
return match;
|
|
2032
|
+
}
|
|
2033
|
+
if (SLACK_USER_ID_EXACT_PATTERN.test(name)) {
|
|
2034
|
+
return match;
|
|
2035
|
+
}
|
|
2036
|
+
const userIds = mentions.get(name.toLowerCase());
|
|
2037
|
+
if (!userIds || userIds.length === 0) {
|
|
2038
|
+
return match;
|
|
2039
|
+
}
|
|
2040
|
+
if (userIds.length === 1) {
|
|
2041
|
+
return `<@${userIds[0]}>`;
|
|
2042
|
+
}
|
|
2043
|
+
if (participants) {
|
|
2044
|
+
const inThread = userIds.filter((id) => participants.has(id));
|
|
2045
|
+
if (inThread.length === 1) {
|
|
2046
|
+
return `<@${inThread[0]}>`;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
return match;
|
|
2050
|
+
}
|
|
2051
|
+
);
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Pre-process an outgoing message to resolve @name mentions before rendering.
|
|
2055
|
+
*/
|
|
2056
|
+
async resolveMessageMentions(message, threadId) {
|
|
2057
|
+
if (!this.chat) {
|
|
2058
|
+
return message;
|
|
2059
|
+
}
|
|
2060
|
+
if (typeof message === "string") {
|
|
2061
|
+
return this.resolveOutgoingMentions(message, threadId);
|
|
2062
|
+
}
|
|
2063
|
+
if (typeof message === "object" && message !== null) {
|
|
2064
|
+
if ("raw" in message && typeof message.raw === "string") {
|
|
2065
|
+
return {
|
|
2066
|
+
...message,
|
|
2067
|
+
raw: await this.resolveOutgoingMentions(
|
|
2068
|
+
message.raw,
|
|
2069
|
+
threadId
|
|
2070
|
+
)
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
if ("markdown" in message && typeof message.markdown === "string") {
|
|
2074
|
+
return {
|
|
2075
|
+
...message,
|
|
2076
|
+
markdown: await this.resolveOutgoingMentions(
|
|
2077
|
+
message.markdown,
|
|
2078
|
+
threadId
|
|
2079
|
+
)
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
return message;
|
|
2084
|
+
}
|
|
1939
2085
|
/**
|
|
1940
2086
|
* Try to render a message using native Slack table blocks.
|
|
1941
2087
|
* Returns blocks + fallback text if the message contains tables, null otherwise.
|
|
@@ -1962,7 +2108,8 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
1962
2108
|
);
|
|
1963
2109
|
return { text: fallbackText, blocks };
|
|
1964
2110
|
}
|
|
1965
|
-
async postMessage(threadId,
|
|
2111
|
+
async postMessage(threadId, _message) {
|
|
2112
|
+
const message = await this.resolveMessageMentions(_message, threadId);
|
|
1966
2113
|
const { channel, threadTs } = this.decodeThreadId(threadId);
|
|
1967
2114
|
try {
|
|
1968
2115
|
const files = extractFiles(message);
|
|
@@ -2066,7 +2213,8 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2066
2213
|
this.handleSlackError(error);
|
|
2067
2214
|
}
|
|
2068
2215
|
}
|
|
2069
|
-
async postEphemeral(threadId, userId,
|
|
2216
|
+
async postEphemeral(threadId, userId, _message) {
|
|
2217
|
+
const message = await this.resolveMessageMentions(_message, threadId);
|
|
2070
2218
|
const { channel, threadTs } = this.decodeThreadId(threadId);
|
|
2071
2219
|
try {
|
|
2072
2220
|
const card = extractCard(message);
|
|
@@ -2159,7 +2307,8 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2159
2307
|
this.handleSlackError(error);
|
|
2160
2308
|
}
|
|
2161
2309
|
}
|
|
2162
|
-
async scheduleMessage(threadId,
|
|
2310
|
+
async scheduleMessage(threadId, _message, options) {
|
|
2311
|
+
const message = await this.resolveMessageMentions(_message, threadId);
|
|
2163
2312
|
const { channel, threadTs } = this.decodeThreadId(threadId);
|
|
2164
2313
|
const postAtUnix = Math.floor(options.postAt.getTime() / 1e3);
|
|
2165
2314
|
if (postAtUnix <= Math.floor(Date.now() / 1e3)) {
|
|
@@ -2345,7 +2494,8 @@ var SlackAdapter = class _SlackAdapter {
|
|
|
2345
2494
|
}
|
|
2346
2495
|
return fileIds;
|
|
2347
2496
|
}
|
|
2348
|
-
async editMessage(threadId, messageId,
|
|
2497
|
+
async editMessage(threadId, messageId, _message) {
|
|
2498
|
+
const message = await this.resolveMessageMentions(_message, threadId);
|
|
2349
2499
|
const ephemeral = this.decodeEphemeralMessageId(messageId);
|
|
2350
2500
|
if (ephemeral) {
|
|
2351
2501
|
const { threadTs } = this.decodeThreadId(threadId);
|