@frogbotai/piece-slack 0.21.0 → 0.23.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,641 @@
1
+ import { z } from 'zod';
2
+ import { slackApiResult, slackResponse, slackValue, slackValues, } from './client.js';
3
+ import { loadSlackFile, slackFile } from './files.js';
4
+ import { channelOptions, userOptions } from './options.js';
5
+ function action(definition) {
6
+ return definition;
7
+ }
8
+ const channel = z.string().min(1).meta({ label: 'Channel' });
9
+ const user = z.string().min(1).meta({ label: 'User' });
10
+ const timestamp = z.string().regex(/^(?:.*\/p\d{16}|\d+\.\d{6})$/, 'Invalid Slack timestamp.');
11
+ const blocks = z.array(slackValue).default([]);
12
+ const messageResult = slackResponse.extend({
13
+ channel: z.string().optional(),
14
+ ts: z.string().optional(),
15
+ });
16
+ function normalizeTimestamp(value) {
17
+ const link = value.match(/\/p(\d+)(\d{6})$/);
18
+ return link ? `${link[1]}.${link[2]}` : value;
19
+ }
20
+ async function upload({ client, file, channelId, title, initialComment, threadTimestamp, token = 'bot', signal, }) {
21
+ const created = await client.request('files.getUploadURLExternal', { filename: file.name, length: file.data.size }, token);
22
+ const uploadUrl = typeof created.upload_url === 'string' ? new URL(created.upload_url) : undefined;
23
+ const fileId = typeof created.file_id === 'string' ? created.file_id : undefined;
24
+ if (!uploadUrl ||
25
+ !fileId ||
26
+ uploadUrl.protocol !== 'https:' ||
27
+ uploadUrl.username ||
28
+ uploadUrl.password) {
29
+ throw new Error('Slack returned an invalid file upload target.');
30
+ }
31
+ if (uploadUrl.hostname !== 'files.slack.com' && !uploadUrl.hostname.endsWith('.slack.com')) {
32
+ throw new Error('Slack returned an untrusted file upload target.');
33
+ }
34
+ const response = await fetch(uploadUrl, {
35
+ method: 'POST',
36
+ body: file.data,
37
+ redirect: 'error',
38
+ signal,
39
+ });
40
+ if (!response.ok)
41
+ throw new Error(`Slack file upload failed (${response.status}).`);
42
+ return client.request('files.completeUploadExternal', {
43
+ files: [{ id: fileId, title }],
44
+ channel_id: channelId,
45
+ initial_comment: initialComment,
46
+ thread_ts: threadTimestamp,
47
+ }, token);
48
+ }
49
+ const messageFields = {
50
+ text: z.string().min(1),
51
+ username: z.string().optional(),
52
+ profilePicture: z.url().optional(),
53
+ iconEmoji: z.string().optional(),
54
+ blocks,
55
+ unfurlLinks: z.boolean().default(true),
56
+ };
57
+ const sendDirectMessageInput = z.object({ userId: user, ...messageFields });
58
+ export const sendDirectMessage = action({
59
+ slug: 'sendDirectMessage',
60
+ description: 'Send a direct message to a Slack user.',
61
+ input: sendDirectMessageInput,
62
+ output: messageResult,
63
+ idempotent: false,
64
+ options: { userId: userOptions },
65
+ async run({ client, input }) {
66
+ return client.request('chat.postMessage', {
67
+ channel: input.userId,
68
+ text: input.text,
69
+ username: input.username,
70
+ icon_url: input.profilePicture,
71
+ icon_emoji: input.iconEmoji,
72
+ blocks: input.blocks,
73
+ unfurl_links: input.unfurlLinks,
74
+ });
75
+ },
76
+ });
77
+ const sendChannelInput = z
78
+ .object({
79
+ channel,
80
+ text: z.string().optional(),
81
+ sendAsBot: z.boolean().default(true),
82
+ threadTimestamp: timestamp.optional(),
83
+ username: z.string().optional(),
84
+ profilePicture: z.url().optional(),
85
+ iconEmoji: z.string().optional(),
86
+ file: slackFile.optional(),
87
+ replyBroadcast: z.boolean().default(false),
88
+ unfurlLinks: z.boolean().default(true),
89
+ blocks,
90
+ })
91
+ .refine(({ text, blocks: value }) => Boolean(text) || value.length > 0, {
92
+ message: 'A message or Block Kit blocks are required.',
93
+ });
94
+ export const sendChannelMessage = action({
95
+ slug: 'sendChannelMessage',
96
+ description: 'Send a message and optional file to a Slack channel.',
97
+ input: sendChannelInput,
98
+ output: slackResponse,
99
+ idempotent: false,
100
+ options: { channel: channelOptions },
101
+ async run({ client, input, req }) {
102
+ const token = input.sendAsBot ? 'bot' : 'user';
103
+ if (input.file) {
104
+ const file = await loadSlackFile(req, input.file);
105
+ return upload({
106
+ client,
107
+ file,
108
+ channelId: input.channel,
109
+ initialComment: input.text,
110
+ threadTimestamp: input.threadTimestamp
111
+ ? normalizeTimestamp(input.threadTimestamp)
112
+ : undefined,
113
+ token,
114
+ signal: req.signal,
115
+ });
116
+ }
117
+ return client.request('chat.postMessage', {
118
+ channel: input.channel,
119
+ text: input.text,
120
+ thread_ts: input.threadTimestamp ? normalizeTimestamp(input.threadTimestamp) : undefined,
121
+ username: input.username,
122
+ icon_url: input.profilePicture,
123
+ icon_emoji: input.iconEmoji,
124
+ reply_broadcast: input.replyBroadcast,
125
+ unfurl_links: input.unfurlLinks,
126
+ blocks: input.blocks,
127
+ }, token);
128
+ },
129
+ });
130
+ const addReactionInput = z.object({
131
+ channel,
132
+ timestamp,
133
+ reaction: z.string().min(1),
134
+ reactAsUser: z.boolean().default(false),
135
+ });
136
+ export const addReaction = action({
137
+ slug: 'addReaction',
138
+ description: 'Add an emoji reaction to a message.',
139
+ input: addReactionInput,
140
+ output: slackResponse,
141
+ idempotent: true,
142
+ options: { channel: channelOptions },
143
+ async run({ client, input }) {
144
+ return client.request('reactions.add', {
145
+ channel: input.channel,
146
+ timestamp: normalizeTimestamp(input.timestamp),
147
+ name: input.reaction,
148
+ }, input.reactAsUser ? 'user' : 'bot');
149
+ },
150
+ });
151
+ const uploadFileInput = z.object({
152
+ file: slackFile,
153
+ title: z.string().optional(),
154
+ filename: z.string().optional(),
155
+ channel: channel.optional(),
156
+ });
157
+ export const uploadFile = action({
158
+ slug: 'uploadFile',
159
+ description: 'Upload a file, optionally sharing it to a channel.',
160
+ input: uploadFileInput,
161
+ output: slackResponse,
162
+ idempotent: false,
163
+ options: { channel: channelOptions },
164
+ async run({ client, input, req }) {
165
+ const loaded = await loadSlackFile(req, {
166
+ ...input.file,
167
+ name: input.filename ?? input.file.name,
168
+ });
169
+ return upload({
170
+ client,
171
+ file: loaded,
172
+ channelId: input.channel,
173
+ title: input.title,
174
+ signal: req.signal,
175
+ });
176
+ },
177
+ });
178
+ const getFileInput = z.object({ fileId: z.string().min(1) });
179
+ export const getFile = action({
180
+ slug: 'getFile',
181
+ description: 'Get Slack file metadata and safely download its contents.',
182
+ input: getFileInput,
183
+ output: slackValue,
184
+ idempotent: true,
185
+ async run({ client, input, req }) {
186
+ const response = await client.request('files.info', { file: input.fileId });
187
+ const file = response.file;
188
+ if (!file ||
189
+ typeof file !== 'object' ||
190
+ !('url_private_download' in file) ||
191
+ typeof file.url_private_download !== 'string') {
192
+ throw new Error('Slack file has no download URL.');
193
+ }
194
+ const url = new URL(file.url_private_download);
195
+ if (url.protocol !== 'https:' ||
196
+ url.hostname !== 'files.slack.com' ||
197
+ url.username ||
198
+ url.password) {
199
+ throw new Error('Slack file download URL is untrusted.');
200
+ }
201
+ const downloaded = await fetch(url, {
202
+ headers: { authorization: `Bearer ${client.token()}` },
203
+ redirect: 'error',
204
+ signal: req.signal,
205
+ });
206
+ if (!downloaded.ok)
207
+ throw new Error(`Slack file download failed (${downloaded.status}).`);
208
+ const data = Buffer.from(await downloaded.arrayBuffer());
209
+ const name = 'name' in file && typeof file.name === 'string' ? file.name : `slack-${input.fileId}`;
210
+ const mimeType = downloaded.headers.get('content-type')?.split(';')[0] || 'application/octet-stream';
211
+ const collection = req.frogbot.config.files?.slug;
212
+ if (!collection) {
213
+ throw new Error('[frogbot] Slack file downloads require the files collection.');
214
+ }
215
+ req.signal?.throwIfAborted();
216
+ const saved = await req.frogbot.create({
217
+ collection,
218
+ data: {},
219
+ file: { data, name, mimetype: mimeType, size: data.length },
220
+ req,
221
+ overrideAccess: false,
222
+ });
223
+ return {
224
+ ...file,
225
+ data: {
226
+ id: saved.id,
227
+ name,
228
+ mimeType,
229
+ size: data.length,
230
+ url: typeof saved.url === 'string' ? saved.url : undefined,
231
+ },
232
+ };
233
+ },
234
+ });
235
+ const searchMessagesInput = z.object({ query: z.string().min(1) });
236
+ export const searchMessages = action({
237
+ slug: 'searchMessages',
238
+ description: 'Search all Slack messages matching a query.',
239
+ input: searchMessagesInput,
240
+ output: slackValues,
241
+ idempotent: true,
242
+ async run({ client, input }) {
243
+ const matches = [];
244
+ let cursor = '*';
245
+ do {
246
+ const response = await client.request('search.messages', { query: input.query, count: 100, cursor }, 'user');
247
+ const messages = response.messages;
248
+ if (!messages || typeof messages !== 'object')
249
+ break;
250
+ if ('matches' in messages && Array.isArray(messages.matches)) {
251
+ for (const match of messages.matches) {
252
+ if (match && typeof match === 'object' && !Array.isArray(match)) {
253
+ matches.push(Object.fromEntries(Object.entries(match)));
254
+ }
255
+ }
256
+ }
257
+ const pagination = 'pagination' in messages ? messages.pagination : undefined;
258
+ cursor =
259
+ pagination && typeof pagination === 'object' && 'next_cursor' in pagination
260
+ ? String(pagination.next_cursor || '') || undefined
261
+ : undefined;
262
+ } while (cursor);
263
+ return matches;
264
+ },
265
+ });
266
+ const emailInput = z.object({ email: z.email() });
267
+ export const findUserByEmail = action({
268
+ slug: 'findUserByEmail',
269
+ description: 'Find a Slack user by exact email address.',
270
+ input: emailInput,
271
+ output: slackResponse,
272
+ idempotent: true,
273
+ async run({ client, input }) {
274
+ return client.request('users.lookupByEmail', input);
275
+ },
276
+ });
277
+ const handleInput = z.object({ handle: z.string().min(1) });
278
+ export const findUserByHandle = action({
279
+ slug: 'findUserByHandle',
280
+ description: 'Find a Slack user by display-name handle.',
281
+ input: handleInput,
282
+ output: slackValue,
283
+ idempotent: true,
284
+ async run({ client, input }) {
285
+ const handle = input.handle.replace(/^@/, '');
286
+ const members = await client.paginate({
287
+ path: 'users.list',
288
+ item: 'members',
289
+ body: { limit: 1000 },
290
+ });
291
+ const found = members.find((member) => {
292
+ if (!member || typeof member !== 'object' || !('profile' in member))
293
+ return false;
294
+ const profile = member.profile;
295
+ return (profile &&
296
+ typeof profile === 'object' &&
297
+ 'display_name' in profile &&
298
+ profile.display_name === handle);
299
+ });
300
+ if (!found || typeof found !== 'object') {
301
+ throw new Error(`Could not find user with handle @${handle}`);
302
+ }
303
+ return found;
304
+ },
305
+ });
306
+ const findUserByIdInput = z.object({ id: z.string().min(1) });
307
+ export const findUserById = action({
308
+ slug: 'findUserById',
309
+ description: 'Find a Slack user profile by ID.',
310
+ input: findUserByIdInput,
311
+ output: slackResponse,
312
+ idempotent: true,
313
+ async run({ client, input }) {
314
+ return client.request('users.profile.get', { user: input.id });
315
+ },
316
+ });
317
+ const listUsersInput = z.object({
318
+ includeBots: z.boolean().default(false),
319
+ includeDisabled: z.boolean().default(false),
320
+ });
321
+ export const listUsers = action({
322
+ slug: 'listUsers',
323
+ description: 'List all users in the workspace.',
324
+ input: listUsersInput,
325
+ output: slackValues,
326
+ idempotent: true,
327
+ async run({ client, input }) {
328
+ const members = await client.paginate({
329
+ path: 'users.list',
330
+ item: 'members',
331
+ body: { limit: 1000 },
332
+ });
333
+ return members.filter((member) => {
334
+ if (!member || typeof member !== 'object')
335
+ return false;
336
+ if (!input.includeBots && 'is_bot' in member && member.is_bot === true)
337
+ return false;
338
+ if (!input.includeDisabled && 'deleted' in member && member.deleted === true)
339
+ return false;
340
+ return true;
341
+ });
342
+ },
343
+ });
344
+ const updateMessageInput = z.object({ channel, timestamp, text: z.string().min(1), blocks });
345
+ export const updateMessage = action({
346
+ slug: 'updateMessage',
347
+ description: 'Update an existing Slack message.',
348
+ input: updateMessageInput,
349
+ output: slackResponse,
350
+ idempotent: true,
351
+ options: { channel: channelOptions },
352
+ async run({ client, input }) {
353
+ return client.request('chat.update', {
354
+ channel: input.channel,
355
+ ts: normalizeTimestamp(input.timestamp),
356
+ text: input.text,
357
+ blocks: input.blocks,
358
+ });
359
+ },
360
+ });
361
+ const deleteMessageInput = z.object({ channel, timestamp });
362
+ export const deleteMessage = action({
363
+ slug: 'deleteMessage',
364
+ description: 'Delete a Slack message after confirming it exists.',
365
+ input: deleteMessageInput,
366
+ output: slackResponse,
367
+ idempotent: false,
368
+ options: { channel: channelOptions },
369
+ async run({ client, input }) {
370
+ const ts = normalizeTimestamp(input.timestamp);
371
+ const history = await client.request('conversations.history', { channel: input.channel, oldest: ts, limit: 1, inclusive: true }, 'user');
372
+ if (!Array.isArray(history.messages) || history.messages.length === 0) {
373
+ throw new Error('No message found for the provided timestamp.');
374
+ }
375
+ return client.request('chat.delete', { channel: input.channel, ts }, 'user');
376
+ },
377
+ });
378
+ const createChannelInput = z.object({
379
+ channelName: z.string().min(1),
380
+ isPrivate: z.boolean().default(false),
381
+ });
382
+ export const createChannel = action({
383
+ slug: 'createChannel',
384
+ description: 'Create a public or private Slack channel.',
385
+ input: createChannelInput,
386
+ output: slackResponse,
387
+ idempotent: false,
388
+ async run({ client, input }) {
389
+ return client.request('conversations.create', {
390
+ name: input.channelName,
391
+ is_private: input.isPrivate,
392
+ });
393
+ },
394
+ });
395
+ const updateProfileInput = z.object({
396
+ firstName: z.string().optional(),
397
+ lastName: z.string().optional(),
398
+ email: z.email().optional(),
399
+ userId: z.string().optional(),
400
+ });
401
+ export const updateProfile = action({
402
+ slug: 'updateProfile',
403
+ description: 'Update a Slack user profile.',
404
+ input: updateProfileInput,
405
+ output: slackResponse,
406
+ idempotent: true,
407
+ async run({ client, input }) {
408
+ return client.request('users.profile.set', {
409
+ profile: { first_name: input.firstName, last_name: input.lastName, email: input.email },
410
+ user: input.userId,
411
+ }, 'user');
412
+ },
413
+ });
414
+ const getChannelHistoryInput = z.object({
415
+ channel,
416
+ oldest: z.number().optional(),
417
+ latest: z.number().optional(),
418
+ inclusive: z.boolean().default(false),
419
+ includeAllMetadata: z.boolean().default(false),
420
+ });
421
+ export const getChannelHistory = action({
422
+ slug: 'getChannelHistory',
423
+ description: 'Get all messages in a channel between optional timestamps.',
424
+ input: getChannelHistoryInput,
425
+ output: slackValues,
426
+ idempotent: true,
427
+ options: { channel: channelOptions },
428
+ async run({ client, input }) {
429
+ return client.paginate({
430
+ path: 'conversations.history',
431
+ item: 'messages',
432
+ body: {
433
+ channel: input.channel,
434
+ oldest: input.oldest,
435
+ latest: input.latest,
436
+ inclusive: input.inclusive,
437
+ include_all_metadata: input.includeAllMetadata,
438
+ limit: 200,
439
+ },
440
+ });
441
+ },
442
+ });
443
+ const setUserStatusInput = z.object({
444
+ text: z.string().max(100),
445
+ emoji: z.string().optional(),
446
+ expiration: z.number().int().optional(),
447
+ });
448
+ export const setUserStatus = action({
449
+ slug: 'setUserStatus',
450
+ description: 'Set the authenticated user custom status.',
451
+ input: setUserStatusInput,
452
+ output: slackResponse,
453
+ idempotent: true,
454
+ async run({ client, input }) {
455
+ return client.request('users.profile.set', {
456
+ profile: {
457
+ status_text: input.text,
458
+ status_emoji: input.emoji,
459
+ status_expiration: input.expiration,
460
+ },
461
+ }, 'user');
462
+ },
463
+ });
464
+ const markdownToSlackInput = z.object({ markdown: z.string() });
465
+ const markdownToSlackOutput = z.object({ text: z.string() });
466
+ export const markdownToSlack = action({
467
+ slug: 'markdownToSlack',
468
+ description: 'Convert Markdown text to Slack mrkdwn.',
469
+ input: markdownToSlackInput,
470
+ output: markdownToSlackOutput,
471
+ idempotent: true,
472
+ async run({ input }) {
473
+ const text = input.markdown
474
+ .replace(/\[([^\]]+)]\((https?:\/\/[^)]+)\)/g, '<$2|$1>')
475
+ .replace(/\*\*([^*]+)\*\*/g, '*$1*')
476
+ .replace(/^[-+] /gm, '• ');
477
+ return { text };
478
+ },
479
+ });
480
+ const listThreadMessagesInput = z.object({ channel, threadTimestamp: timestamp });
481
+ export const listThreadMessages = action({
482
+ slug: 'listThreadMessages',
483
+ description: 'Retrieve all messages in a Slack thread.',
484
+ input: listThreadMessagesInput,
485
+ output: slackValues,
486
+ idempotent: true,
487
+ options: { channel: channelOptions },
488
+ async run({ client, input }) {
489
+ return client.paginate({
490
+ path: 'conversations.replies',
491
+ item: 'messages',
492
+ body: { channel: input.channel, ts: normalizeTimestamp(input.threadTimestamp) },
493
+ });
494
+ },
495
+ });
496
+ const setChannelTopicInput = z.object({ channel, topic: z.string() });
497
+ export const setChannelTopic = action({
498
+ slug: 'setChannelTopic',
499
+ description: 'Set a Slack channel topic.',
500
+ input: setChannelTopicInput,
501
+ output: slackResponse,
502
+ idempotent: true,
503
+ options: { channel: channelOptions },
504
+ async run({ client, input }) {
505
+ return client.request('conversations.setTopic', input);
506
+ },
507
+ });
508
+ const getMessageInput = z.object({ channel, timestamp });
509
+ export const getMessage = action({
510
+ slug: 'getMessage',
511
+ description: 'Get a Slack message by channel and timestamp.',
512
+ input: getMessageInput,
513
+ output: slackResponse,
514
+ idempotent: true,
515
+ options: { channel: channelOptions },
516
+ async run({ client, input }) {
517
+ return client.request('conversations.history', {
518
+ channel: input.channel,
519
+ oldest: normalizeTimestamp(input.timestamp),
520
+ limit: 1,
521
+ inclusive: true,
522
+ });
523
+ },
524
+ });
525
+ const inviteUserToChannelInput = z.object({ channel, userId: user });
526
+ export const inviteUserToChannel = action({
527
+ slug: 'inviteUserToChannel',
528
+ description: 'Invite an existing user to a Slack channel.',
529
+ input: inviteUserToChannelInput,
530
+ output: slackResponse,
531
+ idempotent: false,
532
+ options: { channel: channelOptions, userId: userOptions },
533
+ async run({ client, input }) {
534
+ return client.request('conversations.invite', { channel: input.channel, users: input.userId });
535
+ },
536
+ });
537
+ export const getUserGroupByHandle = action({
538
+ slug: 'getUserGroupByHandle',
539
+ description: 'Get a Slack user group by handle.',
540
+ input: handleInput,
541
+ output: slackValue,
542
+ idempotent: true,
543
+ async run({ client, input }) {
544
+ return findUserGroup(client, input.handle);
545
+ },
546
+ });
547
+ async function findUserGroup(client, value) {
548
+ const handle = value.replace(/^@/, '').toLowerCase();
549
+ const response = await client.request('usergroups.list');
550
+ const groups = Array.isArray(response.usergroups) ? response.usergroups : [];
551
+ const found = groups.find((group) => group &&
552
+ typeof group === 'object' &&
553
+ 'handle' in group &&
554
+ String(group.handle).toLowerCase() === handle);
555
+ if (!found || typeof found !== 'object') {
556
+ throw new Error(`User group with handle '@${handle}' not found.`);
557
+ }
558
+ return found;
559
+ }
560
+ const updateUserGroupMembersInput = handleInput.extend({
561
+ userIds: z.array(z.string()).optional(),
562
+ appendUsers: z.boolean().default(true),
563
+ });
564
+ export const updateUserGroupMembers = action({
565
+ slug: 'updateUserGroupMembers',
566
+ description: 'Replace or append members in a Slack user group.',
567
+ input: updateUserGroupMembersInput,
568
+ output: slackResponse,
569
+ idempotent: false,
570
+ async run({ client, input }) {
571
+ const group = await findUserGroup(client, input.handle);
572
+ const id = 'id' in group ? String(group.id) : '';
573
+ const existing = input.appendUsers && 'users' in group && Array.isArray(group.users)
574
+ ? group.users.map(String)
575
+ : [];
576
+ const users = [...new Set([...existing, ...(input.userIds ?? []).filter(Boolean)])].join(', ');
577
+ return client.request('usergroups.users.update', { usergroup: id, users }, 'user');
578
+ },
579
+ });
580
+ const customApiInput = z.object({
581
+ method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),
582
+ path: z.string().regex(/^[a-zA-Z][a-zA-Z0-9._-]*$/),
583
+ headers: z.record(z.string(), z.string()).default({}),
584
+ queryParams: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).default({}),
585
+ body: z.record(z.string(), z.unknown()).optional(),
586
+ useUserToken: z.boolean().default(false),
587
+ });
588
+ export const customApiCall = action({
589
+ slug: 'customApiCall',
590
+ description: 'Call one relative Slack Web API method.',
591
+ input: customApiInput,
592
+ output: slackApiResult,
593
+ idempotent: false,
594
+ async run({ client, input }) {
595
+ const query = new URLSearchParams();
596
+ for (const [key, value] of Object.entries(input.queryParams))
597
+ query.set(key, String(value));
598
+ const suffix = query.size ? `?${query}` : '';
599
+ const headers = new Headers(input.headers);
600
+ headers.delete('authorization');
601
+ headers.delete('cookie');
602
+ headers.delete('host');
603
+ if (input.body !== undefined && !headers.has('content-type')) {
604
+ headers.set('content-type', 'application/json; charset=utf-8');
605
+ }
606
+ return client.raw({
607
+ path: `${input.path}${suffix}`,
608
+ method: input.method,
609
+ headers,
610
+ body: input.body === undefined ? undefined : JSON.stringify(input.body),
611
+ token: input.useUserToken ? 'user' : 'bot',
612
+ });
613
+ },
614
+ });
615
+ export const slackActions = [
616
+ addReaction,
617
+ sendDirectMessage,
618
+ sendChannelMessage,
619
+ uploadFile,
620
+ getFile,
621
+ searchMessages,
622
+ findUserByEmail,
623
+ findUserByHandle,
624
+ findUserById,
625
+ listUsers,
626
+ updateMessage,
627
+ deleteMessage,
628
+ createChannel,
629
+ updateProfile,
630
+ getChannelHistory,
631
+ setUserStatus,
632
+ markdownToSlack,
633
+ listThreadMessages,
634
+ setChannelTopic,
635
+ getMessage,
636
+ inviteUserToChannel,
637
+ getUserGroupByHandle,
638
+ updateUserGroupMembers,
639
+ customApiCall,
640
+ ];
641
+ //# sourceMappingURL=actions.js.map