@cuylabs/channel-slack 0.1.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/LICENSE +201 -0
- package/README.md +168 -0
- package/dist/activity-ByrD9Ftr.d.ts +66 -0
- package/dist/assistant.d.ts +58 -0
- package/dist/assistant.js +188 -0
- package/dist/bolt.d.ts +344 -0
- package/dist/bolt.js +705 -0
- package/dist/chunk-BODPT4I6.js +322 -0
- package/dist/chunk-FPCE5V5Y.js +292 -0
- package/dist/chunk-FX2JOVX5.js +405 -0
- package/dist/chunk-JZG4IETE.js +141 -0
- package/dist/chunk-NE57BLLU.js +0 -0
- package/dist/chunk-TWJGVDA2.js +108 -0
- package/dist/core.d.ts +425 -0
- package/dist/core.js +42 -0
- package/dist/diagnostics.d.ts +105 -0
- package/dist/diagnostics.js +8 -0
- package/dist/feedback.d.ts +137 -0
- package/dist/feedback.js +128 -0
- package/dist/history.d.ts +266 -0
- package/dist/history.js +747 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +57 -0
- package/dist/logging-Bl3HfcC8.d.ts +8 -0
- package/dist/policy.d.ts +130 -0
- package/dist/policy.js +16 -0
- package/dist/setup.d.ts +165 -0
- package/dist/setup.js +453 -0
- package/dist/shared.d.ts +2 -0
- package/dist/shared.js +43 -0
- package/dist/targets.d.ts +113 -0
- package/dist/targets.js +484 -0
- package/dist/users.d.ts +109 -0
- package/dist/users.js +240 -0
- package/docs/concepts/activity.md +33 -0
- package/docs/concepts/bolt-runtime.md +30 -0
- package/docs/concepts/message-policy.md +49 -0
- package/docs/concepts/setup-requirements.md +44 -0
- package/docs/concepts/supplemental-history.md +55 -0
- package/docs/recipes/app-mention-handler.md +34 -0
- package/docs/recipes/assistant-thread-handler.md +28 -0
- package/docs/recipes/generate-slack-manifest.md +28 -0
- package/docs/recipes/history-visibility.md +36 -0
- package/docs/recipes/socket-mode-app.md +29 -0
- package/docs/reference/channel-slack-boundary.md +50 -0
- package/docs/reference/exports.md +32 -0
- package/package.json +130 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
// src/policy/message/keys.ts
|
|
2
|
+
function createSlackMessagePolicyMessageKey(activity) {
|
|
3
|
+
if (!activity.messageTs) {
|
|
4
|
+
return void 0;
|
|
5
|
+
}
|
|
6
|
+
return `${activity.teamId ?? "unknown"}:${activity.channelId}:${activity.messageTs}`;
|
|
7
|
+
}
|
|
8
|
+
function createSlackMessagePolicyThreadKey(activity) {
|
|
9
|
+
return `${activity.teamId ?? "unknown"}:${activity.channelId}:${activity.threadTs ?? activity.messageTs ?? "unthreaded"}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/policy/message/state-store.ts
|
|
13
|
+
var DEFAULT_MAX_MENTIONED_THREADS = 1e4;
|
|
14
|
+
var DEFAULT_MAX_ACCEPTED_MESSAGES = 1e4;
|
|
15
|
+
function createInMemorySlackMessagePolicyStateStore({
|
|
16
|
+
maxAcceptedMessages = DEFAULT_MAX_ACCEPTED_MESSAGES,
|
|
17
|
+
maxMentionedThreads = DEFAULT_MAX_MENTIONED_THREADS
|
|
18
|
+
} = {}) {
|
|
19
|
+
const mentionedThreads = /* @__PURE__ */ new Map();
|
|
20
|
+
const mentionedThreadOrder = [];
|
|
21
|
+
const acceptedMessageKeys = /* @__PURE__ */ new Set();
|
|
22
|
+
const acceptedMessageOrder = [];
|
|
23
|
+
function claimAcceptedMessageKey(key) {
|
|
24
|
+
if (acceptedMessageKeys.has(key)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
acceptedMessageKeys.add(key);
|
|
28
|
+
acceptedMessageOrder.push(key);
|
|
29
|
+
trimSetByOrder(
|
|
30
|
+
acceptedMessageKeys,
|
|
31
|
+
acceptedMessageOrder,
|
|
32
|
+
maxAcceptedMessages
|
|
33
|
+
);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
getMentionedThread(key) {
|
|
38
|
+
return mentionedThreads.get(key);
|
|
39
|
+
},
|
|
40
|
+
hasAcceptedMessage(key) {
|
|
41
|
+
return acceptedMessageKeys.has(key);
|
|
42
|
+
},
|
|
43
|
+
claimAcceptedMessage(key) {
|
|
44
|
+
return claimAcceptedMessageKey(key);
|
|
45
|
+
},
|
|
46
|
+
rememberMentionedThread(key, state) {
|
|
47
|
+
if (!mentionedThreads.has(key)) {
|
|
48
|
+
mentionedThreadOrder.push(key);
|
|
49
|
+
}
|
|
50
|
+
mentionedThreads.set(key, state);
|
|
51
|
+
trimMapByOrder(
|
|
52
|
+
mentionedThreads,
|
|
53
|
+
mentionedThreadOrder,
|
|
54
|
+
maxMentionedThreads
|
|
55
|
+
);
|
|
56
|
+
},
|
|
57
|
+
rememberAcceptedMessage(key) {
|
|
58
|
+
claimAcceptedMessageKey(key);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function trimMapByOrder(map, order, maxEntries) {
|
|
63
|
+
if (!Number.isFinite(maxEntries) || maxEntries <= 0) {
|
|
64
|
+
map.clear();
|
|
65
|
+
order.length = 0;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
while (order.length > maxEntries) {
|
|
69
|
+
const oldest = order.shift();
|
|
70
|
+
if (oldest !== void 0) {
|
|
71
|
+
map.delete(oldest);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function trimSetByOrder(set, order, maxEntries) {
|
|
76
|
+
if (!Number.isFinite(maxEntries) || maxEntries <= 0) {
|
|
77
|
+
set.clear();
|
|
78
|
+
order.length = 0;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
while (order.length > maxEntries) {
|
|
82
|
+
const oldest = order.shift();
|
|
83
|
+
if (oldest !== void 0) {
|
|
84
|
+
set.delete(oldest);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/policy/message/async-resolver.ts
|
|
90
|
+
function createAsyncSlackMessagePolicyResolver(config = {}) {
|
|
91
|
+
const allowedChannelIds = new Set(config.allowedChannelIds ?? []);
|
|
92
|
+
const messagePolicy = config.messagePolicy ?? "disabled";
|
|
93
|
+
const threadReplyPolicy = config.threadReplyPolicy ?? "mention-required";
|
|
94
|
+
const stateStore = config.stateStore ?? createInMemorySlackMessagePolicyStateStore({
|
|
95
|
+
maxAcceptedMessages: config.maxAcceptedMessages,
|
|
96
|
+
maxMentionedThreads: config.maxMentionedThreads
|
|
97
|
+
});
|
|
98
|
+
async function rememberMentionedThread(activity) {
|
|
99
|
+
const key = createSlackMessagePolicyThreadKey(activity);
|
|
100
|
+
const originalUserId = activity.parentUserId ?? activity.userId;
|
|
101
|
+
await stateStore.rememberMentionedThread(
|
|
102
|
+
key,
|
|
103
|
+
{ originalUserId },
|
|
104
|
+
{
|
|
105
|
+
activity,
|
|
106
|
+
key
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
async function claimAcceptedMessage(activity) {
|
|
111
|
+
const key = createSlackMessagePolicyMessageKey(activity);
|
|
112
|
+
if (!key) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
const context = { activity, key };
|
|
116
|
+
if (stateStore.claimAcceptedMessage) {
|
|
117
|
+
return await stateStore.claimAcceptedMessage(key, context);
|
|
118
|
+
}
|
|
119
|
+
if (await stateStore.hasAcceptedMessage(key, context)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
await stateStore.rememberAcceptedMessage(key, context);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
async function isDuplicate(activity) {
|
|
126
|
+
const key = createSlackMessagePolicyMessageKey(activity);
|
|
127
|
+
return Boolean(
|
|
128
|
+
key && await stateStore.hasAcceptedMessage(key, { activity, key })
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
async function accept(activity, reason) {
|
|
132
|
+
if (!await claimAcceptedMessage(activity)) {
|
|
133
|
+
return reject(activity, "duplicate-message-event");
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
accepted: true,
|
|
137
|
+
activity,
|
|
138
|
+
reason,
|
|
139
|
+
text: activity.text
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function reject(activity, reason) {
|
|
143
|
+
return {
|
|
144
|
+
accepted: false,
|
|
145
|
+
activity,
|
|
146
|
+
reason
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
async function resolve(activity) {
|
|
150
|
+
if (activity.isMention) {
|
|
151
|
+
await rememberMentionedThread(activity);
|
|
152
|
+
return accept(activity, "bot-mentioned");
|
|
153
|
+
}
|
|
154
|
+
if (await isDuplicate(activity)) {
|
|
155
|
+
return reject(activity, "duplicate-message-event");
|
|
156
|
+
}
|
|
157
|
+
if (activity.channelType === "dm") {
|
|
158
|
+
return accept(activity, "direct-message");
|
|
159
|
+
}
|
|
160
|
+
if (messagePolicy === "disabled") {
|
|
161
|
+
return reject(activity, "passive-channel-messages-disabled");
|
|
162
|
+
}
|
|
163
|
+
if (messagePolicy === "any-added-channel") {
|
|
164
|
+
return accept(activity, "any-added-channel");
|
|
165
|
+
}
|
|
166
|
+
if (messagePolicy === "allowed-channels") {
|
|
167
|
+
return allowedChannelIds.has(activity.channelId) ? accept(activity, "allowed-channel") : reject(activity, "channel-not-allowed");
|
|
168
|
+
}
|
|
169
|
+
const threadKey = createSlackMessagePolicyThreadKey(activity);
|
|
170
|
+
const threadState = await stateStore.getMentionedThread(threadKey, {
|
|
171
|
+
activity,
|
|
172
|
+
key: threadKey
|
|
173
|
+
});
|
|
174
|
+
if (!threadState) {
|
|
175
|
+
return reject(activity, "thread-not-mentioned");
|
|
176
|
+
}
|
|
177
|
+
if (threadReplyPolicy === "mention-required") {
|
|
178
|
+
return reject(activity, "thread-reply-mention-required");
|
|
179
|
+
}
|
|
180
|
+
if (threadReplyPolicy === "original-user" && threadState.originalUserId && activity.userId !== threadState.originalUserId) {
|
|
181
|
+
return reject(activity, "thread-reply-not-original-user");
|
|
182
|
+
}
|
|
183
|
+
return accept(activity, "mentioned-thread-reply");
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
resolve,
|
|
187
|
+
async resolveMessage(activity) {
|
|
188
|
+
const decision = await resolve(activity);
|
|
189
|
+
return decision.accepted ? decision.text : void 0;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/policy/message/passive.ts
|
|
195
|
+
function shouldRegisterSlackPassiveChannelMessages(config = {}) {
|
|
196
|
+
const messagePolicy = config.messagePolicy ?? "disabled";
|
|
197
|
+
const threadReplyPolicy = config.threadReplyPolicy ?? "mention-required";
|
|
198
|
+
if (messagePolicy === "disabled") {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
if (messagePolicy === "mentioned-threads" && threadReplyPolicy === "mention-required") {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
if (messagePolicy === "allowed-channels") {
|
|
205
|
+
return Boolean(config.allowedChannelIds?.length);
|
|
206
|
+
}
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/policy/message/resolver.ts
|
|
211
|
+
function createSlackMessagePolicyResolver(config = {}) {
|
|
212
|
+
const allowedChannelIds = new Set(config.allowedChannelIds ?? []);
|
|
213
|
+
const messagePolicy = config.messagePolicy ?? "disabled";
|
|
214
|
+
const threadReplyPolicy = config.threadReplyPolicy ?? "mention-required";
|
|
215
|
+
const stateStore = config.stateStore ?? createInMemorySlackMessagePolicyStateStore({
|
|
216
|
+
maxAcceptedMessages: config.maxAcceptedMessages,
|
|
217
|
+
maxMentionedThreads: config.maxMentionedThreads
|
|
218
|
+
});
|
|
219
|
+
function rememberMentionedThread(activity) {
|
|
220
|
+
const key = createSlackMessagePolicyThreadKey(activity);
|
|
221
|
+
const originalUserId = activity.parentUserId ?? activity.userId;
|
|
222
|
+
stateStore.rememberMentionedThread(
|
|
223
|
+
key,
|
|
224
|
+
{ originalUserId },
|
|
225
|
+
{
|
|
226
|
+
activity,
|
|
227
|
+
key
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
function claimAcceptedMessage(activity) {
|
|
232
|
+
const key = createSlackMessagePolicyMessageKey(activity);
|
|
233
|
+
if (!key) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
const context = { activity, key };
|
|
237
|
+
if (stateStore.claimAcceptedMessage) {
|
|
238
|
+
return stateStore.claimAcceptedMessage(key, context);
|
|
239
|
+
}
|
|
240
|
+
if (stateStore.hasAcceptedMessage(key, context)) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
stateStore.rememberAcceptedMessage(key, context);
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
function isDuplicate(activity) {
|
|
247
|
+
const key = createSlackMessagePolicyMessageKey(activity);
|
|
248
|
+
return Boolean(
|
|
249
|
+
key && stateStore.hasAcceptedMessage(key, { activity, key })
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
function accept(activity, reason) {
|
|
253
|
+
if (!claimAcceptedMessage(activity)) {
|
|
254
|
+
return reject(activity, "duplicate-message-event");
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
accepted: true,
|
|
258
|
+
activity,
|
|
259
|
+
reason,
|
|
260
|
+
text: activity.text
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function reject(activity, reason) {
|
|
264
|
+
return {
|
|
265
|
+
accepted: false,
|
|
266
|
+
activity,
|
|
267
|
+
reason
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function resolve(activity) {
|
|
271
|
+
if (activity.isMention) {
|
|
272
|
+
rememberMentionedThread(activity);
|
|
273
|
+
return accept(activity, "bot-mentioned");
|
|
274
|
+
}
|
|
275
|
+
if (isDuplicate(activity)) {
|
|
276
|
+
return reject(activity, "duplicate-message-event");
|
|
277
|
+
}
|
|
278
|
+
if (activity.channelType === "dm") {
|
|
279
|
+
return accept(activity, "direct-message");
|
|
280
|
+
}
|
|
281
|
+
if (messagePolicy === "disabled") {
|
|
282
|
+
return reject(activity, "passive-channel-messages-disabled");
|
|
283
|
+
}
|
|
284
|
+
if (messagePolicy === "any-added-channel") {
|
|
285
|
+
return accept(activity, "any-added-channel");
|
|
286
|
+
}
|
|
287
|
+
if (messagePolicy === "allowed-channels") {
|
|
288
|
+
return allowedChannelIds.has(activity.channelId) ? accept(activity, "allowed-channel") : reject(activity, "channel-not-allowed");
|
|
289
|
+
}
|
|
290
|
+
const threadKey = createSlackMessagePolicyThreadKey(activity);
|
|
291
|
+
const threadState = stateStore.getMentionedThread(threadKey, {
|
|
292
|
+
activity,
|
|
293
|
+
key: threadKey
|
|
294
|
+
});
|
|
295
|
+
if (!threadState) {
|
|
296
|
+
return reject(activity, "thread-not-mentioned");
|
|
297
|
+
}
|
|
298
|
+
if (threadReplyPolicy === "mention-required") {
|
|
299
|
+
return reject(activity, "thread-reply-mention-required");
|
|
300
|
+
}
|
|
301
|
+
if (threadReplyPolicy === "original-user" && threadState.originalUserId && activity.userId !== threadState.originalUserId) {
|
|
302
|
+
return reject(activity, "thread-reply-not-original-user");
|
|
303
|
+
}
|
|
304
|
+
return accept(activity, "mentioned-thread-reply");
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
resolve,
|
|
308
|
+
resolveMessage(activity) {
|
|
309
|
+
const decision = resolve(activity);
|
|
310
|
+
return decision.accepted ? decision.text : void 0;
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export {
|
|
316
|
+
createSlackMessagePolicyMessageKey,
|
|
317
|
+
createSlackMessagePolicyThreadKey,
|
|
318
|
+
createInMemorySlackMessagePolicyStateStore,
|
|
319
|
+
createAsyncSlackMessagePolicyResolver,
|
|
320
|
+
shouldRegisterSlackPassiveChannelMessages,
|
|
321
|
+
createSlackMessagePolicyResolver
|
|
322
|
+
};
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// src/shared/activity/classify.ts
|
|
2
|
+
function resolveSlackChannelType(channelId, threadTs, inReplyToMessage) {
|
|
3
|
+
if (channelId.startsWith("D")) return "dm";
|
|
4
|
+
if (channelId.startsWith("G")) return "group";
|
|
5
|
+
if (inReplyToMessage || threadTs) return "thread";
|
|
6
|
+
return "channel";
|
|
7
|
+
}
|
|
8
|
+
function stripLeadingMentions(text) {
|
|
9
|
+
return text.replace(/^(<@[A-Z0-9]+>\s*)+/i, "").trim();
|
|
10
|
+
}
|
|
11
|
+
function isProcessableMessage(payload) {
|
|
12
|
+
if (payload.bot_id || payload.subtype === "bot_message") return false;
|
|
13
|
+
if (payload.subtype === "message_changed" || payload.subtype === "message_deleted" || payload.subtype === "message_replied") {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
if (!payload.user) return false;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/shared/activity/text.ts
|
|
21
|
+
function extractSlackMessageText(payload, options = {}) {
|
|
22
|
+
const payloadText = prepareExtractedText(payload?.text, options);
|
|
23
|
+
const blocksText = prepareExtractedBlocksText(payload?.blocks, options);
|
|
24
|
+
const attachmentsText = prepareExtractedText(
|
|
25
|
+
extractSlackAttachmentsText(payload?.attachments),
|
|
26
|
+
options
|
|
27
|
+
);
|
|
28
|
+
return joinTextParts([
|
|
29
|
+
choosePrimarySlackText({ payloadText, blocksText }),
|
|
30
|
+
attachmentsText
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
function extractSlackBlocksText(blocks) {
|
|
34
|
+
return extractSlackBlocksTextResult(blocks)?.text ?? "";
|
|
35
|
+
}
|
|
36
|
+
function extractSlackBlocksTextResult(blocks) {
|
|
37
|
+
if (!Array.isArray(blocks)) {
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
const parts = [];
|
|
41
|
+
let hasRichText = false;
|
|
42
|
+
for (const block of blocks) {
|
|
43
|
+
const record = readRecord(block);
|
|
44
|
+
if (readString(record?.type) === "rich_text") {
|
|
45
|
+
hasRichText = true;
|
|
46
|
+
}
|
|
47
|
+
parts.push(...extractBlockText(block));
|
|
48
|
+
}
|
|
49
|
+
const text = joinTextParts(parts);
|
|
50
|
+
return text ? { text, hasRichText } : void 0;
|
|
51
|
+
}
|
|
52
|
+
function extractSlackAttachmentsText(attachments) {
|
|
53
|
+
if (!Array.isArray(attachments)) {
|
|
54
|
+
return "";
|
|
55
|
+
}
|
|
56
|
+
return joinTextParts(
|
|
57
|
+
attachments.flatMap((attachment) => extractAttachmentText(attachment))
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
function extractBlockText(block) {
|
|
61
|
+
const record = readRecord(block);
|
|
62
|
+
if (!record) {
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
const type = readString(record.type);
|
|
66
|
+
if (type === "rich_text") {
|
|
67
|
+
return extractRichTextElements(record.elements);
|
|
68
|
+
}
|
|
69
|
+
if (type === "section") {
|
|
70
|
+
return [
|
|
71
|
+
...extractTextObject(record.text),
|
|
72
|
+
...readArray(record.fields).flatMap((field) => extractTextObject(field)),
|
|
73
|
+
...extractAccessoryText(record.accessory)
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
if (type === "context") {
|
|
77
|
+
return readArray(record.elements).flatMap((element) => [
|
|
78
|
+
...extractTextObject(element),
|
|
79
|
+
...extractImageText(element)
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
if (type === "header") {
|
|
83
|
+
return extractTextObject(record.text);
|
|
84
|
+
}
|
|
85
|
+
if (type === "image") {
|
|
86
|
+
return extractImageText(record);
|
|
87
|
+
}
|
|
88
|
+
if (type === "video") {
|
|
89
|
+
return [
|
|
90
|
+
...extractTextObject(record.title),
|
|
91
|
+
...extractTextObject(record.description),
|
|
92
|
+
...readTextField(record.alt_text)
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
function extractRichTextElements(elements) {
|
|
98
|
+
return readArray(elements).flatMap((element) => {
|
|
99
|
+
const record = readRecord(element);
|
|
100
|
+
if (!record) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
const type = readString(record.type);
|
|
104
|
+
if (type === "rich_text_section" || type === "rich_text_quote" || type === "rich_text_preformatted") {
|
|
105
|
+
return [joinInlineRichText(record.elements)];
|
|
106
|
+
}
|
|
107
|
+
if (type === "rich_text_list") {
|
|
108
|
+
return readArray(record.elements).map(
|
|
109
|
+
(item) => joinTextParts(extractRichTextElements([item]))
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return extractInlineRichTextElement(record);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function joinInlineRichText(elements) {
|
|
116
|
+
return readArray(elements).flatMap((element) => extractInlineRichTextElement(readRecord(element))).join("");
|
|
117
|
+
}
|
|
118
|
+
function extractInlineRichTextElement(record) {
|
|
119
|
+
if (!record) {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const type = readString(record.type);
|
|
123
|
+
if (type === "text") {
|
|
124
|
+
return readRawTextField(record.text);
|
|
125
|
+
}
|
|
126
|
+
if (type === "emoji") {
|
|
127
|
+
const name = readString(record.name);
|
|
128
|
+
return name ? [`:${name}:`] : [];
|
|
129
|
+
}
|
|
130
|
+
if (type === "user") {
|
|
131
|
+
const userId = readString(record.user_id);
|
|
132
|
+
return userId ? [`<@${userId}>`] : [];
|
|
133
|
+
}
|
|
134
|
+
if (type === "channel") {
|
|
135
|
+
const channelId = readString(record.channel_id);
|
|
136
|
+
return channelId ? [`<#${channelId}>`] : [];
|
|
137
|
+
}
|
|
138
|
+
if (type === "usergroup") {
|
|
139
|
+
const usergroupId = readString(record.usergroup_id);
|
|
140
|
+
return usergroupId ? [`<!subteam^${usergroupId}>`] : [];
|
|
141
|
+
}
|
|
142
|
+
if (type === "link") {
|
|
143
|
+
return readLinkText(record);
|
|
144
|
+
}
|
|
145
|
+
if (type === "broadcast") {
|
|
146
|
+
const range = readString(record.range);
|
|
147
|
+
return range ? [`<!${range}>`] : [];
|
|
148
|
+
}
|
|
149
|
+
if (type === "date") {
|
|
150
|
+
return readTextField(record.fallback);
|
|
151
|
+
}
|
|
152
|
+
if (type === "color") {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
return extractRichTextElements(record.elements);
|
|
156
|
+
}
|
|
157
|
+
function extractAttachmentText(attachment) {
|
|
158
|
+
const record = readRecord(attachment);
|
|
159
|
+
if (!record) {
|
|
160
|
+
return [];
|
|
161
|
+
}
|
|
162
|
+
const richParts = [
|
|
163
|
+
...readTextField(record.pretext),
|
|
164
|
+
...readAttachmentTitle(record),
|
|
165
|
+
...readTextField(record.text),
|
|
166
|
+
...readArray(record.fields).flatMap(
|
|
167
|
+
(field) => extractAttachmentFieldText(field)
|
|
168
|
+
)
|
|
169
|
+
];
|
|
170
|
+
return richParts.length > 0 ? richParts : readTextField(record.fallback);
|
|
171
|
+
}
|
|
172
|
+
function extractAttachmentFieldText(field) {
|
|
173
|
+
const record = readRecord(field);
|
|
174
|
+
if (!record) {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
return [...readTextField(record.title), ...readTextField(record.value)];
|
|
178
|
+
}
|
|
179
|
+
function extractTextObject(value) {
|
|
180
|
+
const record = readRecord(value);
|
|
181
|
+
if (!record) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
return readTextField(record.text);
|
|
185
|
+
}
|
|
186
|
+
function extractAccessoryText(value) {
|
|
187
|
+
const record = readRecord(value);
|
|
188
|
+
if (!record) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
const type = readString(record.type);
|
|
192
|
+
if (type === "image") {
|
|
193
|
+
return extractImageText(record);
|
|
194
|
+
}
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
function extractImageText(value) {
|
|
198
|
+
const record = readRecord(value);
|
|
199
|
+
if (!record) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
return [
|
|
203
|
+
...extractTextObject(record.title),
|
|
204
|
+
...readTextField(record.alt_text)
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
function readAttachmentTitle(record) {
|
|
208
|
+
const title = normalizeText(record.title);
|
|
209
|
+
const titleLink = normalizeText(record.title_link);
|
|
210
|
+
if (title && titleLink) {
|
|
211
|
+
return [formatLinkText(title, titleLink)];
|
|
212
|
+
}
|
|
213
|
+
return title ? [title] : [];
|
|
214
|
+
}
|
|
215
|
+
function readLinkText(record) {
|
|
216
|
+
const url = normalizeText(record.url);
|
|
217
|
+
const text = normalizeText(record.text);
|
|
218
|
+
if (url && text) {
|
|
219
|
+
return [formatLinkText(text, url)];
|
|
220
|
+
}
|
|
221
|
+
return readTextField(text ?? url);
|
|
222
|
+
}
|
|
223
|
+
function formatLinkText(text, url) {
|
|
224
|
+
return text === url ? url : `${text} (${url})`;
|
|
225
|
+
}
|
|
226
|
+
function readTextField(value) {
|
|
227
|
+
const text = normalizeText(value);
|
|
228
|
+
return text ? [text] : [];
|
|
229
|
+
}
|
|
230
|
+
function prepareExtractedText(value, options) {
|
|
231
|
+
const text = normalizeText(value);
|
|
232
|
+
if (!text) {
|
|
233
|
+
return void 0;
|
|
234
|
+
}
|
|
235
|
+
const prepared = options.stripLeadingMentions ? stripLeadingMentions(text) : text;
|
|
236
|
+
return normalizeText(prepared);
|
|
237
|
+
}
|
|
238
|
+
function prepareExtractedBlocksText(blocks, options) {
|
|
239
|
+
const extracted = extractSlackBlocksTextResult(blocks);
|
|
240
|
+
if (!extracted) {
|
|
241
|
+
return void 0;
|
|
242
|
+
}
|
|
243
|
+
const text = prepareExtractedText(extracted.text, options);
|
|
244
|
+
return text ? { ...extracted, text } : void 0;
|
|
245
|
+
}
|
|
246
|
+
function choosePrimarySlackText(context) {
|
|
247
|
+
const { payloadText, blocksText } = context;
|
|
248
|
+
if (!blocksText) {
|
|
249
|
+
return payloadText;
|
|
250
|
+
}
|
|
251
|
+
if (!payloadText) {
|
|
252
|
+
return blocksText.text;
|
|
253
|
+
}
|
|
254
|
+
if (blocksText.hasRichText && blocksText.text.length > payloadText.length) {
|
|
255
|
+
return blocksText.text;
|
|
256
|
+
}
|
|
257
|
+
if (blocksText.text.length > payloadText.length && blocksText.text.startsWith(payloadText)) {
|
|
258
|
+
return blocksText.text;
|
|
259
|
+
}
|
|
260
|
+
return payloadText;
|
|
261
|
+
}
|
|
262
|
+
function readRawTextField(value) {
|
|
263
|
+
return typeof value === "string" && value.length > 0 ? [value] : [];
|
|
264
|
+
}
|
|
265
|
+
function joinTextParts(parts) {
|
|
266
|
+
return parts.map((part) => part?.trim()).filter(Boolean).join("\n").trim();
|
|
267
|
+
}
|
|
268
|
+
function normalizeText(value) {
|
|
269
|
+
if (typeof value !== "string") {
|
|
270
|
+
return void 0;
|
|
271
|
+
}
|
|
272
|
+
const trimmed = value.trim();
|
|
273
|
+
return trimmed || void 0;
|
|
274
|
+
}
|
|
275
|
+
function readRecord(value) {
|
|
276
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
277
|
+
}
|
|
278
|
+
function readArray(value) {
|
|
279
|
+
return Array.isArray(value) ? value : [];
|
|
280
|
+
}
|
|
281
|
+
function readString(value) {
|
|
282
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export {
|
|
286
|
+
resolveSlackChannelType,
|
|
287
|
+
stripLeadingMentions,
|
|
288
|
+
isProcessableMessage,
|
|
289
|
+
extractSlackMessageText,
|
|
290
|
+
extractSlackBlocksText,
|
|
291
|
+
extractSlackAttachmentsText
|
|
292
|
+
};
|