@executor-js/emulate 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +333 -3
- package/dist/api.js +125 -13
- package/dist/api.js.map +1 -1
- package/dist/{dist-YPRJYQHW.js → dist-FE2JWST3.js} +846 -234
- package/dist/dist-FE2JWST3.js.map +1 -0
- package/dist/{dist-K4CVTD6K.js → dist-KBZ6SM7D.js} +357 -12
- package/dist/dist-KBZ6SM7D.js.map +1 -0
- package/dist/{dist-RMPDKZUA.js → dist-OT6R2YO2.js} +440 -81
- package/dist/dist-OT6R2YO2.js.map +1 -0
- package/dist/{dist-WBKONLOE.js → dist-OVTPVMMW.js} +636 -165
- package/dist/dist-OVTPVMMW.js.map +1 -0
- package/dist/{dist-BTEY33DJ.js → dist-QXFWC3LV.js} +567 -75
- package/dist/dist-QXFWC3LV.js.map +1 -0
- package/dist/{dist-IYZPDKJW.js → dist-S47YJ552.js} +543 -43
- package/dist/dist-S47YJ552.js.map +1 -0
- package/dist/{dist-XM5HSBDC.js → dist-TWJXVA7X.js} +268 -30
- package/dist/dist-TWJXVA7X.js.map +1 -0
- package/dist/{dist-JJ2ZRCAX.js → dist-YXHZTLFR.js} +138 -7
- package/dist/dist-YXHZTLFR.js.map +1 -0
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/package.json +4 -7
- package/dist/dist-BTEY33DJ.js.map +0 -1
- package/dist/dist-IYZPDKJW.js.map +0 -1
- package/dist/dist-JJ2ZRCAX.js.map +0 -1
- package/dist/dist-K4CVTD6K.js.map +0 -1
- package/dist/dist-RMPDKZUA.js.map +0 -1
- package/dist/dist-WBKONLOE.js.map +0 -1
- package/dist/dist-XM5HSBDC.js.map +0 -1
- package/dist/dist-YPRJYQHW.js.map +0 -1
|
@@ -189,10 +189,10 @@ function formatSlackFile(file) {
|
|
|
189
189
|
...file.thread_ts ? { thread_ts: file.thread_ts } : {}
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
|
-
function formatSlackPermalink(baseUrl,
|
|
193
|
-
const permalink = `${baseUrl.replace(/\/$/, "")}/archives/${
|
|
192
|
+
function formatSlackPermalink(baseUrl, channel2, msg) {
|
|
193
|
+
const permalink = `${baseUrl.replace(/\/$/, "")}/archives/${channel2}/p${msg.ts.replace(".", "")}`;
|
|
194
194
|
if (!msg.thread_ts || msg.thread_ts === msg.ts) return permalink;
|
|
195
|
-
const params = new URLSearchParams({ thread_ts: msg.thread_ts, cid:
|
|
195
|
+
const params = new URLSearchParams({ thread_ts: msg.thread_ts, cid: channel2 });
|
|
196
196
|
return `${permalink}?${params.toString()}`;
|
|
197
197
|
}
|
|
198
198
|
function formatSlackScheduledMessage(msg) {
|
|
@@ -398,22 +398,22 @@ function authRoutes(ctx) {
|
|
|
398
398
|
function chatRoutes(ctx) {
|
|
399
399
|
const { app, store, webhooks, baseUrl } = ctx;
|
|
400
400
|
const ss = () => getSlackStore(store);
|
|
401
|
-
const findChannel = (
|
|
401
|
+
const findChannel = (channel2) => ss().channels.findOneBy("channel_id", channel2) ?? ss().channels.all().find((ch) => !ch.is_im && !ch.is_mpim && ch.name === channel2);
|
|
402
402
|
const getAuthSlackUser = (authUser) => ss().users.findOneBy("user_id", authUser.login) ?? ss().users.findOneBy("name", authUser.login);
|
|
403
403
|
const getAuthUserId = (authUser) => getAuthSlackUser(authUser)?.user_id ?? authUser.login;
|
|
404
|
-
const isAuthChannelMember = (
|
|
404
|
+
const isAuthChannelMember = (channel2, authUser) => {
|
|
405
405
|
const user = getAuthSlackUser(authUser);
|
|
406
406
|
const userId = user?.user_id ?? authUser.login;
|
|
407
|
-
return
|
|
407
|
+
return channel2.members.includes(userId) || (user ? channel2.members.includes(user.name) : false);
|
|
408
408
|
};
|
|
409
|
-
const canAccessConversation = (
|
|
409
|
+
const canAccessConversation = (channel2, authUser) => !channel2.is_private || isAuthChannelMember(channel2, authUser);
|
|
410
410
|
const isAuthoredByUser = (msg, authUser) => {
|
|
411
411
|
const user = getAuthSlackUser(authUser);
|
|
412
412
|
return msg.user === authUser.login || msg.user === user?.user_id || msg.user === user?.name;
|
|
413
413
|
};
|
|
414
|
-
const isChannelMember = (
|
|
415
|
-
const deletePinsForMessage = (
|
|
416
|
-
for (const pin of ss().pins.findBy("message_ts", ts).filter((pin2) => pin2.channel_id ===
|
|
414
|
+
const isChannelMember = (channel2, user) => channel2.members.includes(user.user_id) || channel2.members.includes(user.name);
|
|
415
|
+
const deletePinsForMessage = (channel2, ts) => {
|
|
416
|
+
for (const pin of ss().pins.findBy("message_ts", ts).filter((pin2) => pin2.channel_id === channel2)) {
|
|
417
417
|
ss().pins.delete(pin.id);
|
|
418
418
|
}
|
|
419
419
|
};
|
|
@@ -471,21 +471,21 @@ function chatRoutes(ctx) {
|
|
|
471
471
|
await dispatchConversationEvent("im_open", { channel: created.channel_id });
|
|
472
472
|
return created;
|
|
473
473
|
};
|
|
474
|
-
const findWritableConversation = async (authUser,
|
|
474
|
+
const findWritableConversation = async (authUser, channel2) => findChannel(channel2) ?? await findOrCreateDirectMessage(authUser, channel2);
|
|
475
475
|
app.post("/api/chat.postMessage", async (c) => {
|
|
476
476
|
const authUser = c.get("authUser");
|
|
477
477
|
if (!authUser) return slackError(c, "not_authed");
|
|
478
478
|
const scopeError = requireSlackScopes(c, store, ["chat:write"]);
|
|
479
479
|
if (scopeError) return scopeError;
|
|
480
480
|
const body = await parseSlackBody(c);
|
|
481
|
-
const
|
|
481
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
482
482
|
const text = typeof body.text === "string" ? body.text : "";
|
|
483
483
|
const thread_ts = typeof body.thread_ts === "string" ? body.thread_ts : void 0;
|
|
484
484
|
const richMessage = parseSlackRichMessageFields(body);
|
|
485
485
|
if (richMessage.error) return slackError(c, richMessage.error);
|
|
486
|
-
if (!
|
|
486
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
487
487
|
if (!hasSlackMessageContent(text, richMessage.fields)) return slackError(c, "no_text");
|
|
488
|
-
const ch = await findWritableConversation(authUser,
|
|
488
|
+
const ch = await findWritableConversation(authUser, channel2);
|
|
489
489
|
if (!ch) return slackError(c, "channel_not_found");
|
|
490
490
|
if (ch.is_archived) return slackError(c, "is_archived");
|
|
491
491
|
if (!canAccessConversation(ch, authUser)) return slackError(c, "not_in_channel");
|
|
@@ -538,16 +538,16 @@ function chatRoutes(ctx) {
|
|
|
538
538
|
const scopeError = requireSlackScopes(c, store, ["chat:write"]);
|
|
539
539
|
if (scopeError) return scopeError;
|
|
540
540
|
const body = await parseSlackBody(c);
|
|
541
|
-
const
|
|
541
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
542
542
|
const user = typeof body.user === "string" ? body.user : "";
|
|
543
543
|
const text = typeof body.text === "string" ? body.text : "";
|
|
544
544
|
const thread_ts = typeof body.thread_ts === "string" ? body.thread_ts : void 0;
|
|
545
545
|
const richMessage = parseSlackRichMessageFields(body);
|
|
546
546
|
if (richMessage.error) return slackError(c, richMessage.error);
|
|
547
|
-
if (!
|
|
547
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
548
548
|
if (!user) return slackError(c, "user_not_found");
|
|
549
549
|
if (!hasSlackMessageContent(text, richMessage.fields)) return slackError(c, "no_text");
|
|
550
|
-
const ch = findChannel(
|
|
550
|
+
const ch = findChannel(channel2);
|
|
551
551
|
if (!ch) return slackError(c, "channel_not_found");
|
|
552
552
|
if (ch.is_archived) return slackError(c, "is_archived");
|
|
553
553
|
if (!canAccessConversation(ch, authUser)) return slackError(c, "not_in_channel");
|
|
@@ -577,16 +577,16 @@ function chatRoutes(ctx) {
|
|
|
577
577
|
const scopeError = requireSlackScopes(c, store, ["chat:write"]);
|
|
578
578
|
if (scopeError) return scopeError;
|
|
579
579
|
const body = await parseSlackBody(c);
|
|
580
|
-
const
|
|
580
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
581
581
|
const ts = typeof body.ts === "string" ? body.ts : "";
|
|
582
582
|
const hasText = typeof body.text === "string";
|
|
583
583
|
const text = hasText ? body.text : "";
|
|
584
584
|
const richMessage = parseSlackRichMessageFields(body);
|
|
585
585
|
if (richMessage.error) return slackError(c, richMessage.error);
|
|
586
|
-
if (!
|
|
587
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
586
|
+
if (!channel2 || !ts) return slackError(c, "message_not_found");
|
|
587
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
588
588
|
if (ch && !canAccessConversation(ch, authUser)) return slackError(c, "not_in_channel");
|
|
589
|
-
const msg = ss().messages.all().find((m) => m.ts === ts && m.channel_id ===
|
|
589
|
+
const msg = ss().messages.all().find((m) => m.ts === ts && m.channel_id === channel2);
|
|
590
590
|
if (!msg) return slackError(c, "message_not_found");
|
|
591
591
|
if (!isAuthoredByUser(msg, authUser)) return slackError(c, "cant_update_message");
|
|
592
592
|
const updates = { ...richMessage.fields };
|
|
@@ -613,7 +613,7 @@ function chatRoutes(ctx) {
|
|
|
613
613
|
type: "message",
|
|
614
614
|
subtype: "message_changed",
|
|
615
615
|
hidden: true,
|
|
616
|
-
channel,
|
|
616
|
+
channel: channel2,
|
|
617
617
|
ts: eventTs,
|
|
618
618
|
event_ts: eventTs,
|
|
619
619
|
message: formatSlackMessage(updated),
|
|
@@ -623,7 +623,7 @@ function chatRoutes(ctx) {
|
|
|
623
623
|
"slack"
|
|
624
624
|
);
|
|
625
625
|
return slackOk(c, {
|
|
626
|
-
channel,
|
|
626
|
+
channel: channel2,
|
|
627
627
|
ts,
|
|
628
628
|
text: updated.text,
|
|
629
629
|
message: formatSlackMessage(updated)
|
|
@@ -635,16 +635,16 @@ function chatRoutes(ctx) {
|
|
|
635
635
|
const scopeError = requireSlackScopes(c, store, ["chat:write"]);
|
|
636
636
|
if (scopeError) return scopeError;
|
|
637
637
|
const body = await parseSlackBody(c);
|
|
638
|
-
const
|
|
638
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
639
639
|
const ts = typeof body.ts === "string" ? body.ts : "";
|
|
640
|
-
if (!
|
|
641
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
640
|
+
if (!channel2 || !ts) return slackError(c, "message_not_found");
|
|
641
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
642
642
|
if (ch && !canAccessConversation(ch, authUser)) return slackError(c, "not_in_channel");
|
|
643
|
-
const msg = ss().messages.all().find((m) => m.ts === ts && m.channel_id ===
|
|
643
|
+
const msg = ss().messages.all().find((m) => m.ts === ts && m.channel_id === channel2);
|
|
644
644
|
if (!msg) return slackError(c, "message_not_found");
|
|
645
645
|
if (!isAuthoredByUser(msg, authUser)) return slackError(c, "cant_delete_message");
|
|
646
646
|
ss().messages.delete(msg.id);
|
|
647
|
-
deletePinsForMessage(
|
|
647
|
+
deletePinsForMessage(channel2, ts);
|
|
648
648
|
const eventTs = generateTs();
|
|
649
649
|
await webhooks.dispatch(
|
|
650
650
|
"message",
|
|
@@ -655,7 +655,7 @@ function chatRoutes(ctx) {
|
|
|
655
655
|
type: "message",
|
|
656
656
|
subtype: "message_deleted",
|
|
657
657
|
hidden: true,
|
|
658
|
-
channel,
|
|
658
|
+
channel: channel2,
|
|
659
659
|
ts: eventTs,
|
|
660
660
|
event_ts: eventTs,
|
|
661
661
|
deleted_ts: ts,
|
|
@@ -664,23 +664,23 @@ function chatRoutes(ctx) {
|
|
|
664
664
|
},
|
|
665
665
|
"slack"
|
|
666
666
|
);
|
|
667
|
-
return slackOk(c, { channel, ts });
|
|
667
|
+
return slackOk(c, { channel: channel2, ts });
|
|
668
668
|
});
|
|
669
669
|
async function getPermalink(c) {
|
|
670
670
|
const authUser = c.get("authUser");
|
|
671
671
|
if (!authUser) return slackError(c, "not_authed");
|
|
672
672
|
const body = c.req.method === "GET" ? {} : await parseSlackBody(c);
|
|
673
|
-
const
|
|
673
|
+
const channel2 = typeof body.channel === "string" ? body.channel : c.req.query("channel") ?? "";
|
|
674
674
|
const messageTs = typeof body.message_ts === "string" ? body.message_ts : c.req.query("message_ts") ?? "";
|
|
675
|
-
if (!
|
|
675
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
676
676
|
if (!messageTs) return slackError(c, "message_not_found");
|
|
677
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
677
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
678
678
|
if (!ch) return slackError(c, "channel_not_found");
|
|
679
679
|
if (!canAccessConversation(ch, authUser)) return slackError(c, "not_in_channel");
|
|
680
|
-
const msg = ss().messages.all().find((m) => m.ts === messageTs && m.channel_id ===
|
|
680
|
+
const msg = ss().messages.all().find((m) => m.ts === messageTs && m.channel_id === channel2);
|
|
681
681
|
if (!msg) return slackError(c, "message_not_found");
|
|
682
682
|
return slackOk(c, {
|
|
683
|
-
channel,
|
|
683
|
+
channel: channel2,
|
|
684
684
|
permalink: formatSlackPermalink(baseUrl, ch.channel_id, msg)
|
|
685
685
|
});
|
|
686
686
|
}
|
|
@@ -692,20 +692,20 @@ function chatRoutes(ctx) {
|
|
|
692
692
|
const scopeError = requireSlackScopes(c, store, ["chat:write"]);
|
|
693
693
|
if (scopeError) return scopeError;
|
|
694
694
|
const body = await parseSlackBody(c);
|
|
695
|
-
const
|
|
695
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
696
696
|
const text = typeof body.text === "string" ? body.text : "";
|
|
697
697
|
const postAt = Number(body.post_at);
|
|
698
698
|
const thread_ts = typeof body.thread_ts === "string" ? body.thread_ts : void 0;
|
|
699
699
|
const richMessage = parseSlackRichMessageFields(body);
|
|
700
700
|
if (richMessage.error) return slackError(c, richMessage.error);
|
|
701
|
-
if (!
|
|
701
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
702
702
|
if (!hasSlackMessageContent(text, richMessage.fields)) return slackError(c, "no_text");
|
|
703
703
|
if (!Number.isFinite(postAt) || postAt <= 0) return slackError(c, "invalid_time");
|
|
704
704
|
const now = Math.floor(Date.now() / 1e3);
|
|
705
705
|
const postAtSeconds = Math.floor(postAt);
|
|
706
706
|
if (postAtSeconds <= now) return slackError(c, "time_in_past");
|
|
707
707
|
if (postAtSeconds > now + 120 * 24 * 60 * 60) return slackError(c, "time_too_far");
|
|
708
|
-
const ch = findChannel(
|
|
708
|
+
const ch = findChannel(channel2);
|
|
709
709
|
if (!ch) return slackError(c, "channel_not_found");
|
|
710
710
|
if (ch.is_archived) return slackError(c, "is_archived");
|
|
711
711
|
if (!canAccessConversation(ch, authUser)) return slackError(c, "not_in_channel");
|
|
@@ -735,11 +735,11 @@ function chatRoutes(ctx) {
|
|
|
735
735
|
const scopeError = requireSlackScopes(c, store, ["chat:write"]);
|
|
736
736
|
if (scopeError) return scopeError;
|
|
737
737
|
const body = await parseSlackBody(c);
|
|
738
|
-
const
|
|
738
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
739
739
|
const scheduledMessageId = typeof body.scheduled_message_id === "string" ? body.scheduled_message_id : "";
|
|
740
|
-
if (!
|
|
740
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
741
741
|
if (!scheduledMessageId) return slackError(c, "invalid_scheduled_message_id");
|
|
742
|
-
const ch = findChannel(
|
|
742
|
+
const ch = findChannel(channel2);
|
|
743
743
|
if (!ch) return slackError(c, "channel_not_found");
|
|
744
744
|
if (!canAccessConversation(ch, authUser)) return slackError(c, "not_in_channel");
|
|
745
745
|
const scheduled = ss().scheduledMessages.all().find((m) => m.channel_id === ch.channel_id && m.scheduled_message_id === scheduledMessageId);
|
|
@@ -754,8 +754,8 @@ function chatRoutes(ctx) {
|
|
|
754
754
|
const scopeError = requireSlackScopes(c, store, ["chat:write"]);
|
|
755
755
|
if (scopeError) return scopeError;
|
|
756
756
|
const body = await parseSlackBody(c);
|
|
757
|
-
const
|
|
758
|
-
const
|
|
757
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
758
|
+
const cursor2 = typeof body.cursor === "string" ? body.cursor : "";
|
|
759
759
|
const requestedLimit = body.limit === void 0 ? 100 : Number(body.limit);
|
|
760
760
|
const oldest = body.oldest === void 0 ? void 0 : Number(body.oldest);
|
|
761
761
|
const latest = body.latest === void 0 ? void 0 : Number(body.latest);
|
|
@@ -768,22 +768,22 @@ function chatRoutes(ctx) {
|
|
|
768
768
|
if (oldest !== void 0 && latest !== void 0 && oldest > latest) {
|
|
769
769
|
return slackError(c, "invalid_arguments");
|
|
770
770
|
}
|
|
771
|
-
const
|
|
772
|
-
const ch =
|
|
773
|
-
if (
|
|
771
|
+
const limit2 = Math.min(Math.floor(requestedLimit), 1e3);
|
|
772
|
+
const ch = channel2 ? findChannel(channel2) : void 0;
|
|
773
|
+
if (channel2 && !ch) return slackError(c, "channel_not_found");
|
|
774
774
|
if (ch && !canAccessConversation(ch, authUser)) return slackError(c, "not_in_channel");
|
|
775
775
|
const allScheduled = ss().scheduledMessages.all().filter((msg) => isAuthoredByUser(msg, authUser)).filter((msg) => !ch || msg.channel_id === ch.channel_id).filter((msg) => {
|
|
776
776
|
const messageChannel = ss().channels.findOneBy("channel_id", msg.channel_id);
|
|
777
777
|
return messageChannel ? canAccessConversation(messageChannel, authUser) : false;
|
|
778
778
|
}).filter((msg) => oldest === void 0 || msg.post_at >= oldest).filter((msg) => latest === void 0 || msg.post_at <= latest).sort((a, b) => a.post_at - b.post_at || a.scheduled_message_id.localeCompare(b.scheduled_message_id));
|
|
779
779
|
let startIndex = 0;
|
|
780
|
-
if (
|
|
781
|
-
const idx = allScheduled.findIndex((msg) => msg.scheduled_message_id ===
|
|
780
|
+
if (cursor2) {
|
|
781
|
+
const idx = allScheduled.findIndex((msg) => msg.scheduled_message_id === cursor2);
|
|
782
782
|
if (idx < 0) return slackError(c, "invalid_cursor");
|
|
783
783
|
if (idx >= 0) startIndex = idx;
|
|
784
784
|
}
|
|
785
|
-
const page = allScheduled.slice(startIndex, startIndex +
|
|
786
|
-
const nextCursor = startIndex +
|
|
785
|
+
const page = allScheduled.slice(startIndex, startIndex + limit2);
|
|
786
|
+
const nextCursor = startIndex + limit2 < allScheduled.length ? allScheduled[startIndex + limit2].scheduled_message_id : "";
|
|
787
787
|
return slackOk(c, {
|
|
788
788
|
scheduled_messages: page.map(formatSlackScheduledMessageListItem),
|
|
789
789
|
response_metadata: { next_cursor: nextCursor }
|
|
@@ -795,10 +795,10 @@ function chatRoutes(ctx) {
|
|
|
795
795
|
const scopeError = requireSlackScopes(c, store, ["chat:write"]);
|
|
796
796
|
if (scopeError) return scopeError;
|
|
797
797
|
const body = await parseSlackBody(c);
|
|
798
|
-
const
|
|
798
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
799
799
|
const text = typeof body.text === "string" ? body.text : "";
|
|
800
|
-
if (!
|
|
801
|
-
const ch = findChannel(
|
|
800
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
801
|
+
const ch = findChannel(channel2);
|
|
802
802
|
if (!ch) return slackError(c, "channel_not_found");
|
|
803
803
|
if (ch.is_archived) return slackError(c, "is_archived");
|
|
804
804
|
if (!canAccessConversation(ch, authUser)) return slackError(c, "not_in_channel");
|
|
@@ -846,18 +846,18 @@ function conversationsRoutes(ctx) {
|
|
|
846
846
|
const getAuthSlackUser = (authUser) => ss().users.findOneBy("user_id", authUser.login) ?? ss().users.findOneBy("name", authUser.login);
|
|
847
847
|
const getAuthUserId = (authUser) => getAuthSlackUser(authUser)?.user_id ?? authUser.login;
|
|
848
848
|
const memberAliases = (user, userId) => new Set([userId, user?.name].filter((value) => Boolean(value)));
|
|
849
|
-
const getChannelMemberKey = (
|
|
849
|
+
const getChannelMemberKey = (channel2, user, userId) => {
|
|
850
850
|
const aliases = memberAliases(user, userId);
|
|
851
|
-
return
|
|
851
|
+
return channel2.members.find((member) => aliases.has(member));
|
|
852
852
|
};
|
|
853
|
-
const isChannelMember = (
|
|
854
|
-
const canReadConversation = (
|
|
853
|
+
const isChannelMember = (channel2, user, userId) => getChannelMemberKey(channel2, user, userId) !== void 0;
|
|
854
|
+
const canReadConversation = (channel2, user, userId) => !channel2.is_private || isChannelMember(channel2, user, userId);
|
|
855
855
|
const visibleFileChannelIds = (file, authUser) => {
|
|
856
856
|
const authSlackUser = getAuthSlackUser(authUser);
|
|
857
857
|
const authUserId = authSlackUser?.user_id ?? authUser.login;
|
|
858
858
|
return fileChannels(file).filter((channelId) => {
|
|
859
|
-
const
|
|
860
|
-
return
|
|
859
|
+
const channel2 = ss().channels.findOneBy("channel_id", channelId);
|
|
860
|
+
return channel2 ? canReadConversation(channel2, authSlackUser, authUserId) : false;
|
|
861
861
|
});
|
|
862
862
|
};
|
|
863
863
|
const visibleFileForAuth = (file, authUser) => {
|
|
@@ -892,10 +892,10 @@ function conversationsRoutes(ctx) {
|
|
|
892
892
|
"slack"
|
|
893
893
|
);
|
|
894
894
|
};
|
|
895
|
-
const insertAndDispatchMessageEvent = async (
|
|
895
|
+
const insertAndDispatchMessageEvent = async (channel2, user, message) => {
|
|
896
896
|
const msg = ss().messages.insert({
|
|
897
897
|
ts: generateTs(),
|
|
898
|
-
channel_id:
|
|
898
|
+
channel_id: channel2.channel_id,
|
|
899
899
|
user,
|
|
900
900
|
type: "message",
|
|
901
901
|
...message,
|
|
@@ -910,7 +910,7 @@ function conversationsRoutes(ctx) {
|
|
|
910
910
|
type: "event_callback",
|
|
911
911
|
event: {
|
|
912
912
|
...formatSlackMessage(msg),
|
|
913
|
-
channel:
|
|
913
|
+
channel: channel2.channel_id,
|
|
914
914
|
event_ts: msg.ts
|
|
915
915
|
}
|
|
916
916
|
},
|
|
@@ -918,29 +918,29 @@ function conversationsRoutes(ctx) {
|
|
|
918
918
|
);
|
|
919
919
|
return msg;
|
|
920
920
|
};
|
|
921
|
-
const dispatchMemberJoined = async (
|
|
921
|
+
const dispatchMemberJoined = async (channel2, user, inviter) => {
|
|
922
922
|
await dispatchConversationEvent("member_joined_channel", {
|
|
923
923
|
user,
|
|
924
|
-
channel:
|
|
925
|
-
channel_type: channelTypeLetter(
|
|
926
|
-
team:
|
|
924
|
+
channel: channel2.channel_id,
|
|
925
|
+
channel_type: channelTypeLetter(channel2),
|
|
926
|
+
team: channel2.team_id,
|
|
927
927
|
...inviter ? { inviter } : {}
|
|
928
928
|
});
|
|
929
929
|
};
|
|
930
|
-
const dispatchMemberLeft = async (
|
|
930
|
+
const dispatchMemberLeft = async (channel2, user) => {
|
|
931
931
|
await dispatchConversationEvent("member_left_channel", {
|
|
932
932
|
user,
|
|
933
|
-
channel:
|
|
934
|
-
channel_type: channelTypeLetter(
|
|
935
|
-
team:
|
|
933
|
+
channel: channel2.channel_id,
|
|
934
|
+
channel_type: channelTypeLetter(channel2),
|
|
935
|
+
team: channel2.team_id
|
|
936
936
|
});
|
|
937
937
|
};
|
|
938
938
|
app.post("/api/conversations.list", async (c) => {
|
|
939
939
|
const authUser = c.get("authUser");
|
|
940
940
|
if (!authUser) return slackError(c, "not_authed");
|
|
941
941
|
const body = await parseSlackBody(c);
|
|
942
|
-
const
|
|
943
|
-
const
|
|
942
|
+
const limit2 = Math.min(Number(body.limit) || 100, 1e3);
|
|
943
|
+
const cursor2 = typeof body.cursor === "string" ? body.cursor : "";
|
|
944
944
|
const excludeArchived = isTruthySlackBoolean(body.exclude_archived);
|
|
945
945
|
const types = parseConversationTypes(body.types);
|
|
946
946
|
const scopeError = requireSlackScopes(c, store, readScopesForConversationTypes(types));
|
|
@@ -949,12 +949,12 @@ function conversationsRoutes(ctx) {
|
|
|
949
949
|
const authUserId = getAuthUserId(authUser);
|
|
950
950
|
const allChannels = ss().channels.all().filter((ch) => matchesConversationTypes(ch, types)).filter((ch) => canReadConversation(ch, authSlackUser, authUserId)).filter((ch) => !excludeArchived || !ch.is_archived);
|
|
951
951
|
let startIndex = 0;
|
|
952
|
-
if (
|
|
953
|
-
const idx = allChannels.findIndex((ch) => ch.channel_id ===
|
|
952
|
+
if (cursor2) {
|
|
953
|
+
const idx = allChannels.findIndex((ch) => ch.channel_id === cursor2);
|
|
954
954
|
if (idx >= 0) startIndex = idx;
|
|
955
955
|
}
|
|
956
|
-
const page = allChannels.slice(startIndex, startIndex +
|
|
957
|
-
const nextCursor = startIndex +
|
|
956
|
+
const page = allChannels.slice(startIndex, startIndex + limit2);
|
|
957
|
+
const nextCursor = startIndex + limit2 < allChannels.length ? allChannels[startIndex + limit2].channel_id : "";
|
|
958
958
|
return slackOk(c, {
|
|
959
959
|
channels: page.map((ch) => formatChannel(ch, authUserId, authSlackUser?.name)),
|
|
960
960
|
response_metadata: { next_cursor: nextCursor }
|
|
@@ -964,8 +964,8 @@ function conversationsRoutes(ctx) {
|
|
|
964
964
|
const authUser = c.get("authUser");
|
|
965
965
|
if (!authUser) return slackError(c, "not_authed");
|
|
966
966
|
const body = await parseSlackBody(c);
|
|
967
|
-
const
|
|
968
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
967
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
968
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
969
969
|
if (!ch) return slackError(c, "channel_not_found");
|
|
970
970
|
const scopeError = requireSlackScopes(c, store, [slackConversationReadScope(ch)]);
|
|
971
971
|
if (scopeError) return scopeError;
|
|
@@ -1013,9 +1013,9 @@ function conversationsRoutes(ctx) {
|
|
|
1013
1013
|
const authUser = c.get("authUser");
|
|
1014
1014
|
if (!authUser) return slackError(c, "not_authed");
|
|
1015
1015
|
const body = await parseSlackBody(c);
|
|
1016
|
-
const
|
|
1017
|
-
if (!
|
|
1018
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1016
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1017
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
1018
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1019
1019
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1020
1020
|
const scopeError = requireSlackScopes(c, store, [slackConversationWriteScope(ch)]);
|
|
1021
1021
|
if (scopeError) return scopeError;
|
|
@@ -1040,9 +1040,9 @@ function conversationsRoutes(ctx) {
|
|
|
1040
1040
|
const authUser = c.get("authUser");
|
|
1041
1041
|
if (!authUser) return slackError(c, "not_authed");
|
|
1042
1042
|
const body = await parseSlackBody(c);
|
|
1043
|
-
const
|
|
1044
|
-
if (!
|
|
1045
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1043
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1044
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
1045
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1046
1046
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1047
1047
|
const scopeError = requireSlackScopes(c, store, [slackConversationWriteScope(ch)]);
|
|
1048
1048
|
if (scopeError) return scopeError;
|
|
@@ -1072,12 +1072,12 @@ function conversationsRoutes(ctx) {
|
|
|
1072
1072
|
const authUser = c.get("authUser");
|
|
1073
1073
|
if (!authUser) return slackError(c, "not_authed");
|
|
1074
1074
|
const body = await parseSlackBody(c);
|
|
1075
|
-
const
|
|
1075
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1076
1076
|
const name = normalizeChannelName(typeof body.name === "string" ? body.name : "");
|
|
1077
|
-
if (!
|
|
1077
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
1078
1078
|
const nameError = validateChannelName(name);
|
|
1079
1079
|
if (nameError) return slackError(c, nameError);
|
|
1080
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1080
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1081
1081
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1082
1082
|
const scopeError = requireSlackScopes(c, store, [slackConversationWriteScope(ch)]);
|
|
1083
1083
|
if (scopeError) return scopeError;
|
|
@@ -1113,12 +1113,12 @@ function conversationsRoutes(ctx) {
|
|
|
1113
1113
|
const authUser = c.get("authUser");
|
|
1114
1114
|
if (!authUser) return slackError(c, "not_authed");
|
|
1115
1115
|
const body = await parseSlackBody(c);
|
|
1116
|
-
const
|
|
1116
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1117
1117
|
const topic = typeof body.topic === "string" ? body.topic : void 0;
|
|
1118
|
-
if (!
|
|
1118
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
1119
1119
|
if (topic === void 0) return slackError(c, "invalid_arguments");
|
|
1120
1120
|
if (topic.length > 250) return slackError(c, "too_long");
|
|
1121
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1121
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1122
1122
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1123
1123
|
const scopeError = requireSlackScopes(c, store, [slackConversationWriteScope(ch)]);
|
|
1124
1124
|
if (scopeError) return scopeError;
|
|
@@ -1142,12 +1142,12 @@ function conversationsRoutes(ctx) {
|
|
|
1142
1142
|
const authUser = c.get("authUser");
|
|
1143
1143
|
if (!authUser) return slackError(c, "not_authed");
|
|
1144
1144
|
const body = await parseSlackBody(c);
|
|
1145
|
-
const
|
|
1145
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1146
1146
|
const purpose = typeof body.purpose === "string" ? body.purpose : void 0;
|
|
1147
|
-
if (!
|
|
1147
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
1148
1148
|
if (purpose === void 0) return slackError(c, "invalid_arguments");
|
|
1149
1149
|
if (purpose.length > 250) return slackError(c, "too_long");
|
|
1150
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1150
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1151
1151
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1152
1152
|
const scopeError = requireSlackScopes(c, store, [slackConversationWriteScope(ch)]);
|
|
1153
1153
|
if (scopeError) return scopeError;
|
|
@@ -1171,26 +1171,26 @@ function conversationsRoutes(ctx) {
|
|
|
1171
1171
|
const authUser = c.get("authUser");
|
|
1172
1172
|
if (!authUser) return slackError(c, "not_authed");
|
|
1173
1173
|
const body = await parseSlackBody(c);
|
|
1174
|
-
const
|
|
1175
|
-
const
|
|
1176
|
-
const
|
|
1177
|
-
if (!
|
|
1178
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1174
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1175
|
+
const limit2 = Math.min(Number(body.limit) || 100, 1e3);
|
|
1176
|
+
const cursor2 = typeof body.cursor === "string" ? body.cursor : "";
|
|
1177
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
1178
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1179
1179
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1180
1180
|
const scopeError = requireSlackScopes(c, store, [slackConversationHistoryScope(ch)]);
|
|
1181
1181
|
if (scopeError) return scopeError;
|
|
1182
1182
|
const authSlackUser = getAuthSlackUser(authUser);
|
|
1183
1183
|
const authUserId = getAuthUserId(authUser);
|
|
1184
1184
|
if (!canReadConversation(ch, authSlackUser, authUserId)) return slackError(c, "not_in_channel");
|
|
1185
|
-
const allMessages = ss().messages.findBy("channel_id",
|
|
1185
|
+
const allMessages = ss().messages.findBy("channel_id", channel2).filter((m) => !m.thread_ts || m.thread_ts === m.ts).sort((a, b) => b.ts > a.ts ? 1 : -1);
|
|
1186
1186
|
let startIndex = 0;
|
|
1187
|
-
if (
|
|
1188
|
-
const idx = allMessages.findIndex((m) => m.ts ===
|
|
1187
|
+
if (cursor2) {
|
|
1188
|
+
const idx = allMessages.findIndex((m) => m.ts === cursor2);
|
|
1189
1189
|
if (idx >= 0) startIndex = idx;
|
|
1190
1190
|
}
|
|
1191
|
-
const page = allMessages.slice(startIndex, startIndex +
|
|
1192
|
-
const hasMore = startIndex +
|
|
1193
|
-
const nextCursor = hasMore ? allMessages[startIndex +
|
|
1191
|
+
const page = allMessages.slice(startIndex, startIndex + limit2);
|
|
1192
|
+
const hasMore = startIndex + limit2 < allMessages.length;
|
|
1193
|
+
const nextCursor = hasMore ? allMessages[startIndex + limit2].ts : "";
|
|
1194
1194
|
return slackOk(c, {
|
|
1195
1195
|
messages: page.map((message) => formatSlackMessageForAuth(message, authUser)),
|
|
1196
1196
|
has_more: hasMore,
|
|
@@ -1201,17 +1201,17 @@ function conversationsRoutes(ctx) {
|
|
|
1201
1201
|
const authUser = c.get("authUser");
|
|
1202
1202
|
if (!authUser) return slackError(c, "not_authed");
|
|
1203
1203
|
const body = await parseSlackBody(c);
|
|
1204
|
-
const
|
|
1204
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1205
1205
|
const ts = typeof body.ts === "string" ? body.ts : "";
|
|
1206
|
-
if (!
|
|
1207
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1206
|
+
if (!channel2 || !ts) return slackError(c, "channel_not_found");
|
|
1207
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1208
1208
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1209
1209
|
const scopeError = requireSlackScopes(c, store, [slackConversationHistoryScope(ch)]);
|
|
1210
1210
|
if (scopeError) return scopeError;
|
|
1211
1211
|
const authSlackUser = getAuthSlackUser(authUser);
|
|
1212
1212
|
const authUserId = getAuthUserId(authUser);
|
|
1213
1213
|
if (!canReadConversation(ch, authSlackUser, authUserId)) return slackError(c, "not_in_channel");
|
|
1214
|
-
const allMessages = ss().messages.findBy("channel_id",
|
|
1214
|
+
const allMessages = ss().messages.findBy("channel_id", channel2).filter((m) => m.ts === ts || m.thread_ts === ts).sort((a, b) => a.ts > b.ts ? 1 : -1);
|
|
1215
1215
|
return slackOk(c, {
|
|
1216
1216
|
messages: allMessages.map((message) => formatSlackMessageForAuth(message, authUser)),
|
|
1217
1217
|
has_more: false
|
|
@@ -1221,8 +1221,8 @@ function conversationsRoutes(ctx) {
|
|
|
1221
1221
|
const authUser = c.get("authUser");
|
|
1222
1222
|
if (!authUser) return slackError(c, "not_authed");
|
|
1223
1223
|
const body = await parseSlackBody(c);
|
|
1224
|
-
const
|
|
1225
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1224
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1225
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1226
1226
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1227
1227
|
const scopeError = requireSlackScopes(c, store, [slackConversationJoinScope(ch)]);
|
|
1228
1228
|
if (scopeError) return scopeError;
|
|
@@ -1241,15 +1241,15 @@ function conversationsRoutes(ctx) {
|
|
|
1241
1241
|
});
|
|
1242
1242
|
await dispatchMemberJoined(updated2, authUserId);
|
|
1243
1243
|
}
|
|
1244
|
-
const updated = ss().channels.findOneBy("channel_id",
|
|
1244
|
+
const updated = ss().channels.findOneBy("channel_id", channel2);
|
|
1245
1245
|
return slackOk(c, { channel: formatChannel(updated, authUserId, authSlackUser?.name) });
|
|
1246
1246
|
});
|
|
1247
1247
|
app.post("/api/conversations.leave", async (c) => {
|
|
1248
1248
|
const authUser = c.get("authUser");
|
|
1249
1249
|
if (!authUser) return slackError(c, "not_authed");
|
|
1250
1250
|
const body = await parseSlackBody(c);
|
|
1251
|
-
const
|
|
1252
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1251
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1252
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1253
1253
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1254
1254
|
const scopeError = requireSlackScopes(c, store, [slackConversationWriteScope(ch)]);
|
|
1255
1255
|
if (scopeError) return scopeError;
|
|
@@ -1273,12 +1273,12 @@ function conversationsRoutes(ctx) {
|
|
|
1273
1273
|
const authUser = c.get("authUser");
|
|
1274
1274
|
if (!authUser) return slackError(c, "not_authed");
|
|
1275
1275
|
const body = await parseSlackBody(c);
|
|
1276
|
-
const
|
|
1276
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1277
1277
|
const users = parseUserList(body.users);
|
|
1278
|
-
if (!
|
|
1278
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
1279
1279
|
if (users.length === 0) return slackError(c, "user_not_found");
|
|
1280
1280
|
if (users.length > 100) return slackError(c, "too_many_users");
|
|
1281
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1281
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1282
1282
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1283
1283
|
const scopeError = requireSlackScopes(c, store, [slackConversationWriteScope(ch)]);
|
|
1284
1284
|
if (scopeError) return scopeError;
|
|
@@ -1318,11 +1318,11 @@ function conversationsRoutes(ctx) {
|
|
|
1318
1318
|
const authUser = c.get("authUser");
|
|
1319
1319
|
if (!authUser) return slackError(c, "not_authed");
|
|
1320
1320
|
const body = await parseSlackBody(c);
|
|
1321
|
-
const
|
|
1321
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1322
1322
|
const user = typeof body.user === "string" ? body.user : "";
|
|
1323
|
-
if (!
|
|
1323
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
1324
1324
|
if (!user) return slackError(c, "user_not_found");
|
|
1325
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1325
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1326
1326
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1327
1327
|
const scopeError = requireSlackScopes(c, store, [slackConversationWriteScope(ch)]);
|
|
1328
1328
|
if (scopeError) return scopeError;
|
|
@@ -1350,14 +1350,14 @@ function conversationsRoutes(ctx) {
|
|
|
1350
1350
|
const authUser = c.get("authUser");
|
|
1351
1351
|
if (!authUser) return slackError(c, "not_authed");
|
|
1352
1352
|
const body = await parseSlackBody(c);
|
|
1353
|
-
const
|
|
1353
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1354
1354
|
const users = parseUserList(body.users);
|
|
1355
1355
|
const returnIm = isTruthySlackBoolean(body.return_im);
|
|
1356
1356
|
const preventCreation = isTruthySlackBoolean(body.prevent_creation);
|
|
1357
1357
|
const authUserId = getAuthUserId(authUser);
|
|
1358
1358
|
const authSlackUser = getAuthSlackUser(authUser);
|
|
1359
|
-
if (
|
|
1360
|
-
const existing2 = ss().channels.findOneBy("channel_id",
|
|
1359
|
+
if (channel2) {
|
|
1360
|
+
const existing2 = ss().channels.findOneBy("channel_id", channel2);
|
|
1361
1361
|
if (!existing2 || !existing2.is_im && !existing2.is_mpim) return slackError(c, "channel_not_found");
|
|
1362
1362
|
const scopeError2 = requireSlackScopes(c, store, [slackConversationWriteScope(existing2)]);
|
|
1363
1363
|
if (scopeError2) return scopeError2;
|
|
@@ -1427,9 +1427,9 @@ function conversationsRoutes(ctx) {
|
|
|
1427
1427
|
const authUser = c.get("authUser");
|
|
1428
1428
|
if (!authUser) return slackError(c, "not_authed");
|
|
1429
1429
|
const body = await parseSlackBody(c);
|
|
1430
|
-
const
|
|
1431
|
-
if (!
|
|
1432
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1430
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1431
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
1432
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1433
1433
|
if (!ch || !ch.is_im && !ch.is_mpim) return slackError(c, "channel_not_found");
|
|
1434
1434
|
const scopeError = requireSlackScopes(c, store, [slackConversationWriteScope(ch)]);
|
|
1435
1435
|
if (scopeError) return scopeError;
|
|
@@ -1447,11 +1447,11 @@ function conversationsRoutes(ctx) {
|
|
|
1447
1447
|
const authUser = c.get("authUser");
|
|
1448
1448
|
if (!authUser) return slackError(c, "not_authed");
|
|
1449
1449
|
const body = await parseSlackBody(c);
|
|
1450
|
-
const
|
|
1450
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1451
1451
|
const ts = typeof body.ts === "string" ? body.ts : "";
|
|
1452
|
-
if (!
|
|
1452
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
1453
1453
|
if (!ts) return slackError(c, "invalid_ts");
|
|
1454
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1454
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1455
1455
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1456
1456
|
const scopeError = requireSlackScopes(c, store, [slackConversationWriteScope(ch)]);
|
|
1457
1457
|
if (scopeError) return scopeError;
|
|
@@ -1468,8 +1468,8 @@ function conversationsRoutes(ctx) {
|
|
|
1468
1468
|
const authUser = c.get("authUser");
|
|
1469
1469
|
if (!authUser) return slackError(c, "not_authed");
|
|
1470
1470
|
const body = await parseSlackBody(c);
|
|
1471
|
-
const
|
|
1472
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1471
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1472
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1473
1473
|
if (!ch) return slackError(c, "channel_not_found");
|
|
1474
1474
|
const scopeError = requireSlackScopes(c, store, [slackConversationReadScope(ch)]);
|
|
1475
1475
|
if (scopeError) return scopeError;
|
|
@@ -1616,16 +1616,16 @@ function usersRoutes(ctx) {
|
|
|
1616
1616
|
const scopeError = requireSlackScopes(c, store, ["users:read"]);
|
|
1617
1617
|
if (scopeError) return scopeError;
|
|
1618
1618
|
const body = await parseSlackBody(c);
|
|
1619
|
-
const
|
|
1620
|
-
const
|
|
1619
|
+
const limit2 = Math.min(Number(body.limit) || 100, 1e3);
|
|
1620
|
+
const cursor2 = typeof body.cursor === "string" ? body.cursor : "";
|
|
1621
1621
|
const allUsers = ss().users.all().filter((u) => !u.deleted);
|
|
1622
1622
|
let startIndex = 0;
|
|
1623
|
-
if (
|
|
1624
|
-
const idx = allUsers.findIndex((u) => u.user_id ===
|
|
1623
|
+
if (cursor2) {
|
|
1624
|
+
const idx = allUsers.findIndex((u) => u.user_id === cursor2);
|
|
1625
1625
|
if (idx >= 0) startIndex = idx;
|
|
1626
1626
|
}
|
|
1627
|
-
const page = allUsers.slice(startIndex, startIndex +
|
|
1628
|
-
const nextCursor = startIndex +
|
|
1627
|
+
const page = allUsers.slice(startIndex, startIndex + limit2);
|
|
1628
|
+
const nextCursor = startIndex + limit2 < allUsers.length ? allUsers[startIndex + limit2].user_id : "";
|
|
1629
1629
|
return slackOk(c, {
|
|
1630
1630
|
members: page.map((user) => formatUser(user, canExposeEmail(c))),
|
|
1631
1631
|
response_metadata: { next_cursor: nextCursor }
|
|
@@ -1861,25 +1861,25 @@ function reactionsRoutes(ctx) {
|
|
|
1861
1861
|
const user = getAuthSlackUser(authUser);
|
|
1862
1862
|
return new Set([authUser.login, user?.user_id, user?.name].filter((value) => Boolean(value)));
|
|
1863
1863
|
};
|
|
1864
|
-
const isAuthChannelMember = (
|
|
1864
|
+
const isAuthChannelMember = (channel2, authUser) => {
|
|
1865
1865
|
const user = getAuthSlackUser(authUser);
|
|
1866
1866
|
const userId = user?.user_id ?? authUser.login;
|
|
1867
|
-
return
|
|
1867
|
+
return channel2.members.includes(userId) || (user ? channel2.members.includes(user.name) : false);
|
|
1868
1868
|
};
|
|
1869
|
-
const canAccessConversation = (
|
|
1869
|
+
const canAccessConversation = (channel2, authUser) => !channel2.is_private || isAuthChannelMember(channel2, authUser);
|
|
1870
1870
|
app.post("/api/reactions.add", async (c) => {
|
|
1871
1871
|
const authUser = c.get("authUser");
|
|
1872
1872
|
if (!authUser) return slackError(c, "not_authed");
|
|
1873
1873
|
const scopeError = requireSlackScopes(c, store, ["reactions:write"]);
|
|
1874
1874
|
if (scopeError) return scopeError;
|
|
1875
1875
|
const body = await parseSlackBody(c);
|
|
1876
|
-
const
|
|
1876
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1877
1877
|
const timestamp = typeof body.timestamp === "string" ? body.timestamp : "";
|
|
1878
1878
|
const name = typeof body.name === "string" ? body.name : "";
|
|
1879
1879
|
if (!name) return slackError(c, "invalid_name");
|
|
1880
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1880
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1881
1881
|
if (ch && !canAccessConversation(ch, authUser)) return slackError(c, "not_in_channel");
|
|
1882
|
-
const msg = ss().messages.all().find((m) => m.ts === timestamp && m.channel_id ===
|
|
1882
|
+
const msg = ss().messages.all().find((m) => m.ts === timestamp && m.channel_id === channel2);
|
|
1883
1883
|
if (!msg) return slackError(c, "message_not_found");
|
|
1884
1884
|
const reactions = [...msg.reactions];
|
|
1885
1885
|
const existing = reactions.find((r) => r.name === name);
|
|
@@ -1904,7 +1904,7 @@ function reactionsRoutes(ctx) {
|
|
|
1904
1904
|
type: "reaction_added",
|
|
1905
1905
|
user: authUserId,
|
|
1906
1906
|
reaction: name,
|
|
1907
|
-
item: { type: "message", channel, ts: timestamp }
|
|
1907
|
+
item: { type: "message", channel: channel2, ts: timestamp }
|
|
1908
1908
|
}
|
|
1909
1909
|
},
|
|
1910
1910
|
"slack"
|
|
@@ -1917,13 +1917,13 @@ function reactionsRoutes(ctx) {
|
|
|
1917
1917
|
const scopeError = requireSlackScopes(c, store, ["reactions:write"]);
|
|
1918
1918
|
if (scopeError) return scopeError;
|
|
1919
1919
|
const body = await parseSlackBody(c);
|
|
1920
|
-
const
|
|
1920
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1921
1921
|
const timestamp = typeof body.timestamp === "string" ? body.timestamp : "";
|
|
1922
1922
|
const name = typeof body.name === "string" ? body.name : "";
|
|
1923
1923
|
if (!name) return slackError(c, "invalid_name");
|
|
1924
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1924
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1925
1925
|
if (ch && !canAccessConversation(ch, authUser)) return slackError(c, "not_in_channel");
|
|
1926
|
-
const msg = ss().messages.all().find((m) => m.ts === timestamp && m.channel_id ===
|
|
1926
|
+
const msg = ss().messages.all().find((m) => m.ts === timestamp && m.channel_id === channel2);
|
|
1927
1927
|
if (!msg) return slackError(c, "message_not_found");
|
|
1928
1928
|
const reactions = [...msg.reactions];
|
|
1929
1929
|
const existing = reactions.find((r) => r.name === name);
|
|
@@ -1945,7 +1945,7 @@ function reactionsRoutes(ctx) {
|
|
|
1945
1945
|
type: "reaction_removed",
|
|
1946
1946
|
user: authUserId,
|
|
1947
1947
|
reaction: name,
|
|
1948
|
-
item: { type: "message", channel, ts: timestamp }
|
|
1948
|
+
item: { type: "message", channel: channel2, ts: timestamp }
|
|
1949
1949
|
}
|
|
1950
1950
|
},
|
|
1951
1951
|
"slack"
|
|
@@ -1958,11 +1958,11 @@ function reactionsRoutes(ctx) {
|
|
|
1958
1958
|
const scopeError = requireSlackScopes(c, store, ["reactions:read"]);
|
|
1959
1959
|
if (scopeError) return scopeError;
|
|
1960
1960
|
const body = await parseSlackBody(c);
|
|
1961
|
-
const
|
|
1961
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
1962
1962
|
const timestamp = typeof body.timestamp === "string" ? body.timestamp : "";
|
|
1963
|
-
const ch = ss().channels.findOneBy("channel_id",
|
|
1963
|
+
const ch = ss().channels.findOneBy("channel_id", channel2);
|
|
1964
1964
|
if (ch && !canAccessConversation(ch, authUser)) return slackError(c, "not_in_channel");
|
|
1965
|
-
const msg = ss().messages.all().find((m) => m.ts === timestamp && m.channel_id ===
|
|
1965
|
+
const msg = ss().messages.all().find((m) => m.ts === timestamp && m.channel_id === channel2);
|
|
1966
1966
|
if (!msg) return slackError(c, "message_not_found");
|
|
1967
1967
|
return slackOk(c, {
|
|
1968
1968
|
type: "message",
|
|
@@ -2727,7 +2727,7 @@ function slugifyBotName(value) {
|
|
|
2727
2727
|
function webhookRoutes(ctx) {
|
|
2728
2728
|
const { app, store, webhooks } = ctx;
|
|
2729
2729
|
const ss = () => getSlackStore(store);
|
|
2730
|
-
const findChannel = (
|
|
2730
|
+
const findChannel = (channel2) => ss().channels.findOneBy("channel_id", channel2) ?? ss().channels.all().find((ch) => !ch.is_im && !ch.is_mpim && ch.name === channel2);
|
|
2731
2731
|
app.post("/services/:teamId/:botId/:token", async (c) => {
|
|
2732
2732
|
const contentType = c.req.header("Content-Type") ?? "";
|
|
2733
2733
|
const rawText = await c.req.text();
|
|
@@ -2813,14 +2813,14 @@ function filesRoutes(ctx) {
|
|
|
2813
2813
|
const serviceBaseUrl = baseUrl.replace(/\/$/, "");
|
|
2814
2814
|
const getAuthSlackUser = (authUser) => ss().users.findOneBy("user_id", authUser.login) ?? ss().users.findOneBy("name", authUser.login);
|
|
2815
2815
|
const getAuthUserId = (authUser) => getAuthSlackUser(authUser)?.user_id ?? authUser.login;
|
|
2816
|
-
const isChannelMember = (
|
|
2817
|
-
const canReadConversation = (
|
|
2816
|
+
const isChannelMember = (channel2, user, userId) => channel2.members.includes(userId) || (user ? channel2.members.includes(user.name) : false);
|
|
2817
|
+
const canReadConversation = (channel2, user, userId) => !channel2.is_private || isChannelMember(channel2, user, userId);
|
|
2818
2818
|
const visibleFileChannelIds = (file, authUser) => {
|
|
2819
2819
|
const authSlackUser = getAuthSlackUser(authUser);
|
|
2820
2820
|
const authUserId = authSlackUser?.user_id ?? authUser.login;
|
|
2821
2821
|
return fileChannels2(file).filter((channelId) => {
|
|
2822
|
-
const
|
|
2823
|
-
return
|
|
2822
|
+
const channel2 = ss().channels.findOneBy("channel_id", channelId);
|
|
2823
|
+
return channel2 ? canReadConversation(channel2, authSlackUser, authUserId) : false;
|
|
2824
2824
|
});
|
|
2825
2825
|
};
|
|
2826
2826
|
const canAccessFile = (file, authUser) => {
|
|
@@ -2852,7 +2852,7 @@ function filesRoutes(ctx) {
|
|
|
2852
2852
|
const authUserId = authSlackUser?.user_id ?? authUser.login;
|
|
2853
2853
|
return file.user === authUserId || authSlackUser?.is_admin === true;
|
|
2854
2854
|
};
|
|
2855
|
-
const findChannel = (
|
|
2855
|
+
const findChannel = (channel2) => ss().channels.findOneBy("channel_id", channel2) ?? ss().channels.all().find((ch) => !ch.is_im && !ch.is_mpim && ch.name === channel2);
|
|
2856
2856
|
const findDirectMessage = (authUserId, userId) => {
|
|
2857
2857
|
const members = [authUserId, userId].sort();
|
|
2858
2858
|
return ss().channels.all().find(
|
|
@@ -2888,11 +2888,11 @@ function filesRoutes(ctx) {
|
|
|
2888
2888
|
last_read: {}
|
|
2889
2889
|
});
|
|
2890
2890
|
};
|
|
2891
|
-
const resolveShareTarget = (authUser,
|
|
2892
|
-
const existingChannel = findChannel(
|
|
2891
|
+
const resolveShareTarget = (authUser, channel2) => {
|
|
2892
|
+
const existingChannel = findChannel(channel2);
|
|
2893
2893
|
if (existingChannel) return { key: existingChannel.channel_id, channel: existingChannel };
|
|
2894
|
-
if (!
|
|
2895
|
-
const targetUser = ss().users.findOneBy("user_id",
|
|
2894
|
+
if (!channel2.startsWith("U")) return void 0;
|
|
2895
|
+
const targetUser = ss().users.findOneBy("user_id", channel2);
|
|
2896
2896
|
if (!targetUser || targetUser.deleted) return void 0;
|
|
2897
2897
|
const authUserId = getAuthUserId(authUser);
|
|
2898
2898
|
if (targetUser.user_id === authUserId) return void 0;
|
|
@@ -2982,9 +2982,9 @@ function filesRoutes(ctx) {
|
|
|
2982
2982
|
}
|
|
2983
2983
|
const targets = [];
|
|
2984
2984
|
for (const target of targetRefs) {
|
|
2985
|
-
const
|
|
2986
|
-
if (!
|
|
2987
|
-
targets.push(
|
|
2985
|
+
const channel2 = target.channel ?? findOrCreateDirectMessage(authUser, target.directUserId ?? "");
|
|
2986
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
2987
|
+
targets.push(channel2);
|
|
2988
2988
|
}
|
|
2989
2989
|
const completedFiles = [];
|
|
2990
2990
|
for (let index = 0; index < requestedFiles.length; index++) {
|
|
@@ -3007,10 +3007,10 @@ function filesRoutes(ctx) {
|
|
|
3007
3007
|
return slackOk(c, { files: sharedFiles.map((file) => formatSlackFileForAuth(file, authUser)) });
|
|
3008
3008
|
async function shareFiles(channels, files) {
|
|
3009
3009
|
const updatedFiles = [...files];
|
|
3010
|
-
for (const
|
|
3010
|
+
for (const channel2 of channels) {
|
|
3011
3011
|
const msg = ss().messages.insert({
|
|
3012
3012
|
ts: generateTs(),
|
|
3013
|
-
channel_id:
|
|
3013
|
+
channel_id: channel2.channel_id,
|
|
3014
3014
|
user: authUserId,
|
|
3015
3015
|
text: initialComment,
|
|
3016
3016
|
type: "message",
|
|
@@ -3023,12 +3023,12 @@ function filesRoutes(ctx) {
|
|
|
3023
3023
|
reply_users: [],
|
|
3024
3024
|
reactions: []
|
|
3025
3025
|
});
|
|
3026
|
-
updateParentThread(
|
|
3026
|
+
updateParentThread(channel2.channel_id, threadTs, authUserId);
|
|
3027
3027
|
const messageFiles = [];
|
|
3028
3028
|
for (const file of updatedFiles) {
|
|
3029
|
-
const shared = updateFileShare(file,
|
|
3029
|
+
const shared = updateFileShare(file, channel2, msg, authUserId);
|
|
3030
3030
|
messageFiles.push(shared);
|
|
3031
|
-
await dispatchFileEvent(webhooks, "file_shared", shared, { channel_id:
|
|
3031
|
+
await dispatchFileEvent(webhooks, "file_shared", shared, { channel_id: channel2.channel_id });
|
|
3032
3032
|
}
|
|
3033
3033
|
const updatedMessage = ss().messages.update(msg.id, { files: messageFiles });
|
|
3034
3034
|
await webhooks.dispatch(
|
|
@@ -3040,7 +3040,7 @@ function filesRoutes(ctx) {
|
|
|
3040
3040
|
...formatSlackMessage(updatedMessage),
|
|
3041
3041
|
type: "message",
|
|
3042
3042
|
subtype: "file_share",
|
|
3043
|
-
channel:
|
|
3043
|
+
channel: channel2.channel_id
|
|
3044
3044
|
}
|
|
3045
3045
|
},
|
|
3046
3046
|
"slack"
|
|
@@ -3074,14 +3074,14 @@ function filesRoutes(ctx) {
|
|
|
3074
3074
|
const scopeError = requireSlackScopes(c, store, ["files:read"]);
|
|
3075
3075
|
if (scopeError) return scopeError;
|
|
3076
3076
|
const body = await parseSlackRequest2(c);
|
|
3077
|
-
const
|
|
3077
|
+
const channel2 = typeof body.channel === "string" ? body.channel : "";
|
|
3078
3078
|
const user = typeof body.user === "string" ? body.user : "";
|
|
3079
3079
|
const types = typeof body.types === "string" ? body.types : "all";
|
|
3080
3080
|
const tsFrom = body.ts_from === void 0 ? void 0 : Number(body.ts_from);
|
|
3081
3081
|
const tsTo = body.ts_to === void 0 ? void 0 : Number(body.ts_to);
|
|
3082
3082
|
const page = Math.max(1, Math.floor(Number(body.page) || 1));
|
|
3083
3083
|
const count = Math.min(Math.max(1, Math.floor(Number(body.count) || 100)), 1e3);
|
|
3084
|
-
const files = ss().files.all().filter((file) => !file.deleted).filter((file) => canAccessFile(file, authUser)).filter((file) => !
|
|
3084
|
+
const files = ss().files.all().filter((file) => !file.deleted).filter((file) => canAccessFile(file, authUser)).filter((file) => !channel2 || canAccessFileInChannel(file, authUser, channel2)).filter((file) => !user || file.user === user).filter((file) => tsFrom === void 0 || file.created >= tsFrom).filter((file) => tsTo === void 0 || file.created <= tsTo).filter((file) => matchesFileTypes(file, types)).sort((a, b) => b.created - a.created || b.file_id.localeCompare(a.file_id));
|
|
3085
3085
|
const start = (page - 1) * count;
|
|
3086
3086
|
const paged = files.slice(start, start + count);
|
|
3087
3087
|
return slackOk(c, {
|
|
@@ -3148,11 +3148,11 @@ function filesRoutes(ctx) {
|
|
|
3148
3148
|
reply_users: replyUsers
|
|
3149
3149
|
});
|
|
3150
3150
|
}
|
|
3151
|
-
function updateFileShare(file,
|
|
3151
|
+
function updateFileShare(file, channel2, msg, userId) {
|
|
3152
3152
|
const share = {
|
|
3153
3153
|
ts: msg.ts,
|
|
3154
|
-
channel_name:
|
|
3155
|
-
team_id:
|
|
3154
|
+
channel_name: channel2.name,
|
|
3155
|
+
team_id: channel2.team_id,
|
|
3156
3156
|
share_user_id: userId,
|
|
3157
3157
|
source: "UPLOAD",
|
|
3158
3158
|
thread_ts: msg.thread_ts,
|
|
@@ -3161,15 +3161,15 @@ function filesRoutes(ctx) {
|
|
|
3161
3161
|
reply_users_count: 0,
|
|
3162
3162
|
is_silent_share: false
|
|
3163
3163
|
};
|
|
3164
|
-
const shareBucket =
|
|
3164
|
+
const shareBucket = channel2.is_private ? "private" : "public";
|
|
3165
3165
|
const shares = {
|
|
3166
3166
|
...file.shares,
|
|
3167
3167
|
[shareBucket]: {
|
|
3168
3168
|
...file.shares[shareBucket] ?? {},
|
|
3169
|
-
[
|
|
3169
|
+
[channel2.channel_id]: [...file.shares[shareBucket]?.[channel2.channel_id] ?? [], share]
|
|
3170
3170
|
}
|
|
3171
3171
|
};
|
|
3172
|
-
const channelFields = nextFileChannelFields(file,
|
|
3172
|
+
const channelFields = nextFileChannelFields(file, channel2);
|
|
3173
3173
|
return ss().files.update(file.id, {
|
|
3174
3174
|
...channelFields,
|
|
3175
3175
|
shares,
|
|
@@ -3240,7 +3240,7 @@ function parseDestinationChannels(channelId, channels) {
|
|
|
3240
3240
|
const values = [];
|
|
3241
3241
|
if (typeof channelId === "string" && channelId.trim()) values.push(channelId.trim());
|
|
3242
3242
|
if (typeof channels === "string" && channels.trim()) {
|
|
3243
|
-
values.push(...channels.split(",").map((
|
|
3243
|
+
values.push(...channels.split(",").map((channel2) => channel2.trim()));
|
|
3244
3244
|
}
|
|
3245
3245
|
return [...new Set(values.filter(Boolean))];
|
|
3246
3246
|
}
|
|
@@ -3298,13 +3298,13 @@ function buildSlackFile(session, options) {
|
|
|
3298
3298
|
content_base64: session.content_base64
|
|
3299
3299
|
};
|
|
3300
3300
|
}
|
|
3301
|
-
function nextFileChannelFields(file,
|
|
3301
|
+
function nextFileChannelFields(file, channel2) {
|
|
3302
3302
|
const channels = new Set(file.channels);
|
|
3303
3303
|
const groups = new Set(file.groups);
|
|
3304
3304
|
const ims = new Set(file.ims);
|
|
3305
|
-
if (
|
|
3306
|
-
else if (
|
|
3307
|
-
else channels.add(
|
|
3305
|
+
if (channel2.is_im || channel2.is_mpim) ims.add(channel2.channel_id);
|
|
3306
|
+
else if (channel2.is_private) groups.add(channel2.channel_id);
|
|
3307
|
+
else channels.add(channel2.channel_id);
|
|
3308
3308
|
return { channels: [...channels], groups: [...groups], ims: [...ims] };
|
|
3309
3309
|
}
|
|
3310
3310
|
function fileChannels2(file) {
|
|
@@ -3381,8 +3381,8 @@ function pinsRoutes(ctx) {
|
|
|
3381
3381
|
const ss = () => getSlackStore(store);
|
|
3382
3382
|
const getAuthSlackUser = (authUser) => ss().users.findOneBy("user_id", authUser.login) ?? ss().users.findOneBy("name", authUser.login);
|
|
3383
3383
|
const getAuthUserId = (authUser) => getAuthSlackUser(authUser)?.user_id ?? authUser.login;
|
|
3384
|
-
const isChannelMember = (
|
|
3385
|
-
const canReadConversation = (
|
|
3384
|
+
const isChannelMember = (channel2, user, userId) => channel2.members.includes(userId) || (user ? channel2.members.includes(user.name) : false);
|
|
3385
|
+
const canReadConversation = (channel2, user, userId) => !channel2.is_private || isChannelMember(channel2, user, userId);
|
|
3386
3386
|
const findPinnedMessage = (channelId, timestamp) => ss().messages.all().find((message) => message.channel_id === channelId && message.ts === timestamp);
|
|
3387
3387
|
const findPin = (channelId, timestamp) => ss().pins.all().find((pin) => pin.channel_id === channelId && pin.message_ts === timestamp);
|
|
3388
3388
|
app.post("/api/pins.add", async (c) => {
|
|
@@ -3396,26 +3396,26 @@ function pinsRoutes(ctx) {
|
|
|
3396
3396
|
if (!channelId) return slackError(c, "channel_not_found");
|
|
3397
3397
|
if (!timestamp) return slackError(c, "no_item_specified");
|
|
3398
3398
|
if (!isSlackTimestamp(timestamp)) return slackError(c, "bad_timestamp");
|
|
3399
|
-
const
|
|
3400
|
-
if (!
|
|
3401
|
-
if (
|
|
3399
|
+
const channel2 = ss().channels.findOneBy("channel_id", channelId);
|
|
3400
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
3401
|
+
if (channel2.is_archived) return slackError(c, "is_archived");
|
|
3402
3402
|
const authSlackUser = getAuthSlackUser(authUser);
|
|
3403
3403
|
const authUserId = getAuthUserId(authUser);
|
|
3404
|
-
if (!isChannelMember(
|
|
3405
|
-
const message = findPinnedMessage(
|
|
3404
|
+
if (!isChannelMember(channel2, authSlackUser, authUserId)) return slackError(c, "not_in_channel");
|
|
3405
|
+
const message = findPinnedMessage(channel2.channel_id, timestamp);
|
|
3406
3406
|
if (!message) return slackError(c, "message_not_found");
|
|
3407
|
-
if (findPin(
|
|
3407
|
+
if (findPin(channel2.channel_id, timestamp)) return slackError(c, "already_pinned");
|
|
3408
3408
|
const pin = ss().pins.insert({
|
|
3409
3409
|
pin_id: generateSlackId("P"),
|
|
3410
|
-
team_id:
|
|
3411
|
-
channel_id:
|
|
3410
|
+
team_id: channel2.team_id,
|
|
3411
|
+
channel_id: channel2.channel_id,
|
|
3412
3412
|
message_ts: timestamp,
|
|
3413
3413
|
created: Math.floor(Date.now() / 1e3),
|
|
3414
3414
|
created_by: authUserId
|
|
3415
3415
|
});
|
|
3416
3416
|
await dispatchPinEvent("pin_added", {
|
|
3417
3417
|
user: authUserId,
|
|
3418
|
-
channel_id:
|
|
3418
|
+
channel_id: channel2.channel_id,
|
|
3419
3419
|
item: formatPinItem(pin, message),
|
|
3420
3420
|
event_ts: generateTs()
|
|
3421
3421
|
});
|
|
@@ -3429,12 +3429,12 @@ function pinsRoutes(ctx) {
|
|
|
3429
3429
|
const body = await parseSlackRequest3(c);
|
|
3430
3430
|
const channelId = typeof body.channel === "string" ? body.channel : "";
|
|
3431
3431
|
if (!channelId) return slackError(c, "channel_not_found");
|
|
3432
|
-
const
|
|
3433
|
-
if (!
|
|
3432
|
+
const channel2 = ss().channels.findOneBy("channel_id", channelId);
|
|
3433
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
3434
3434
|
const authSlackUser = getAuthSlackUser(authUser);
|
|
3435
3435
|
const authUserId = getAuthUserId(authUser);
|
|
3436
|
-
if (!canReadConversation(
|
|
3437
|
-
const items = ss().pins.findBy("channel_id",
|
|
3436
|
+
if (!canReadConversation(channel2, authSlackUser, authUserId)) return slackError(c, "not_in_channel");
|
|
3437
|
+
const items = ss().pins.findBy("channel_id", channel2.channel_id).sort((a, b) => b.created - a.created).flatMap((pin) => {
|
|
3438
3438
|
const message = findPinnedMessage(pin.channel_id, pin.message_ts);
|
|
3439
3439
|
return message ? [formatPinItem(pin, message)] : [];
|
|
3440
3440
|
});
|
|
@@ -3453,21 +3453,21 @@ function pinsRoutes(ctx) {
|
|
|
3453
3453
|
if (!channelId) return slackError(c, "channel_not_found");
|
|
3454
3454
|
if (!timestamp) return slackError(c, "no_item_specified");
|
|
3455
3455
|
if (!isSlackTimestamp(timestamp)) return slackError(c, "bad_timestamp");
|
|
3456
|
-
const
|
|
3457
|
-
if (!
|
|
3458
|
-
if (
|
|
3456
|
+
const channel2 = ss().channels.findOneBy("channel_id", channelId);
|
|
3457
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
3458
|
+
if (channel2.is_archived) return slackError(c, "is_archived");
|
|
3459
3459
|
const authSlackUser = getAuthSlackUser(authUser);
|
|
3460
3460
|
const authUserId = getAuthUserId(authUser);
|
|
3461
|
-
if (!isChannelMember(
|
|
3462
|
-
const pin = findPin(
|
|
3463
|
-
const message = findPinnedMessage(
|
|
3461
|
+
if (!isChannelMember(channel2, authSlackUser, authUserId)) return slackError(c, "not_in_channel");
|
|
3462
|
+
const pin = findPin(channel2.channel_id, timestamp);
|
|
3463
|
+
const message = findPinnedMessage(channel2.channel_id, timestamp);
|
|
3464
3464
|
if (!pin) return slackError(c, "no_pin");
|
|
3465
3465
|
ss().pins.delete(pin.id);
|
|
3466
3466
|
if (!message) return slackOk(c, {});
|
|
3467
|
-
const hasPins = ss().pins.findBy("channel_id",
|
|
3467
|
+
const hasPins = ss().pins.findBy("channel_id", channel2.channel_id).length > 0;
|
|
3468
3468
|
await dispatchPinEvent("pin_removed", {
|
|
3469
3469
|
user: authUserId,
|
|
3470
|
-
channel_id:
|
|
3470
|
+
channel_id: channel2.channel_id,
|
|
3471
3471
|
item: formatPinItem(pin, message),
|
|
3472
3472
|
has_pins: hasPins,
|
|
3473
3473
|
event_ts: generateTs()
|
|
@@ -3513,8 +3513,8 @@ function bookmarksRoutes(ctx) {
|
|
|
3513
3513
|
const ss = () => getSlackStore(store);
|
|
3514
3514
|
const getAuthSlackUser = (authUser) => ss().users.findOneBy("user_id", authUser.login) ?? ss().users.findOneBy("name", authUser.login);
|
|
3515
3515
|
const getAuthUserId = (authUser) => getAuthSlackUser(authUser)?.user_id ?? authUser.login;
|
|
3516
|
-
const isChannelMember = (
|
|
3517
|
-
const canReadConversation = (
|
|
3516
|
+
const isChannelMember = (channel2, user, userId) => channel2.members.includes(userId) || (user ? channel2.members.includes(user.name) : false);
|
|
3517
|
+
const canReadConversation = (channel2, user, userId) => !channel2.is_private || isChannelMember(channel2, user, userId);
|
|
3518
3518
|
app.post("/api/bookmarks.add", async (c) => {
|
|
3519
3519
|
const authUser = c.get("authUser");
|
|
3520
3520
|
if (!authUser) return slackError(c, "not_authed");
|
|
@@ -3522,27 +3522,27 @@ function bookmarksRoutes(ctx) {
|
|
|
3522
3522
|
if (scopeError) return scopeError;
|
|
3523
3523
|
const body = await parseSlackBody(c);
|
|
3524
3524
|
const channelId = stringField(body.channel_id) || stringField(body.channel);
|
|
3525
|
-
const
|
|
3526
|
-
if (!
|
|
3527
|
-
if (
|
|
3525
|
+
const channel2 = findBookmarkChannel(channelId);
|
|
3526
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
3527
|
+
if (channel2.is_archived) return slackError(c, "is_archived");
|
|
3528
3528
|
const authSlackUser = getAuthSlackUser(authUser);
|
|
3529
3529
|
const authUserId = getAuthUserId(authUser);
|
|
3530
|
-
if (!isChannelMember(
|
|
3530
|
+
if (!isChannelMember(channel2, authSlackUser, authUserId)) return slackError(c, "not_in_channel");
|
|
3531
3531
|
const title = stringField(body.title).trim();
|
|
3532
3532
|
const type = stringField(body.type);
|
|
3533
3533
|
const link = stringField(body.link) || stringField(body.url);
|
|
3534
3534
|
if (type !== "link") return slackError(c, "invalid_bookmark_type");
|
|
3535
3535
|
if (!title || !link) return slackError(c, "invalid_arguments");
|
|
3536
3536
|
if (!isValidBookmarkLink(link)) return slackError(c, "invalid_link");
|
|
3537
|
-
if (ss().bookmarks.findBy("channel_id",
|
|
3537
|
+
if (ss().bookmarks.findBy("channel_id", channel2.channel_id).length >= 100) {
|
|
3538
3538
|
return slackError(c, "too_many_bookmarks");
|
|
3539
3539
|
}
|
|
3540
3540
|
const now = Math.floor(Date.now() / 1e3);
|
|
3541
3541
|
const team = ss().teams.all()[0];
|
|
3542
3542
|
const bookmark = ss().bookmarks.insert({
|
|
3543
3543
|
bookmark_id: generateSlackId("Bk"),
|
|
3544
|
-
team_id: team?.team_id ??
|
|
3545
|
-
channel_id:
|
|
3544
|
+
team_id: team?.team_id ?? channel2.team_id,
|
|
3545
|
+
channel_id: channel2.channel_id,
|
|
3546
3546
|
title,
|
|
3547
3547
|
type: "link",
|
|
3548
3548
|
link,
|
|
@@ -3551,9 +3551,9 @@ function bookmarksRoutes(ctx) {
|
|
|
3551
3551
|
entity_id: null,
|
|
3552
3552
|
date_created: now,
|
|
3553
3553
|
date_updated: 0,
|
|
3554
|
-
rank: bookmarkRank(
|
|
3554
|
+
rank: bookmarkRank(channel2.channel_id),
|
|
3555
3555
|
last_updated_by_user_id: authUserId,
|
|
3556
|
-
last_updated_by_team_id: team?.team_id ??
|
|
3556
|
+
last_updated_by_team_id: team?.team_id ?? channel2.team_id,
|
|
3557
3557
|
shortcut_id: null,
|
|
3558
3558
|
app_id: null,
|
|
3559
3559
|
...accessLevel(body.access_level) ? { access_level: accessLevel(body.access_level) } : {},
|
|
@@ -3569,13 +3569,13 @@ function bookmarksRoutes(ctx) {
|
|
|
3569
3569
|
const body = await parseSlackBody(c);
|
|
3570
3570
|
const channelId = stringField(body.channel_id) || stringField(body.channel);
|
|
3571
3571
|
const bookmarkId = stringField(body.bookmark_id);
|
|
3572
|
-
const
|
|
3573
|
-
if (!
|
|
3574
|
-
if (
|
|
3572
|
+
const channel2 = findBookmarkChannel(channelId);
|
|
3573
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
3574
|
+
if (channel2.is_archived) return slackError(c, "is_archived");
|
|
3575
3575
|
const authSlackUser = getAuthSlackUser(authUser);
|
|
3576
3576
|
const authUserId = getAuthUserId(authUser);
|
|
3577
|
-
if (!isChannelMember(
|
|
3578
|
-
const bookmark = findBookmark(
|
|
3577
|
+
if (!isChannelMember(channel2, authSlackUser, authUserId)) return slackError(c, "not_in_channel");
|
|
3578
|
+
const bookmark = findBookmark(channel2.channel_id, bookmarkId);
|
|
3579
3579
|
if (!bookmark) return slackError(c, "not_found");
|
|
3580
3580
|
const updates = {
|
|
3581
3581
|
date_updated: Math.floor(Date.now() / 1e3),
|
|
@@ -3601,12 +3601,12 @@ function bookmarksRoutes(ctx) {
|
|
|
3601
3601
|
if (scopeError) return scopeError;
|
|
3602
3602
|
const body = await parseSlackBody(c);
|
|
3603
3603
|
const channelId = stringField(body.channel_id) || stringField(body.channel);
|
|
3604
|
-
const
|
|
3605
|
-
if (!
|
|
3604
|
+
const channel2 = findBookmarkChannel(channelId);
|
|
3605
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
3606
3606
|
const authSlackUser = getAuthSlackUser(authUser);
|
|
3607
3607
|
const authUserId = getAuthUserId(authUser);
|
|
3608
|
-
if (!canReadConversation(
|
|
3609
|
-
const bookmarks = ss().bookmarks.findBy("channel_id",
|
|
3608
|
+
if (!canReadConversation(channel2, authSlackUser, authUserId)) return slackError(c, "not_in_channel");
|
|
3609
|
+
const bookmarks = ss().bookmarks.findBy("channel_id", channel2.channel_id).sort(compareSlackBookmarks).map(formatBookmark);
|
|
3610
3610
|
return slackOk(c, { bookmarks });
|
|
3611
3611
|
});
|
|
3612
3612
|
app.post("/api/bookmarks.remove", async (c) => {
|
|
@@ -3617,13 +3617,13 @@ function bookmarksRoutes(ctx) {
|
|
|
3617
3617
|
const body = await parseSlackBody(c);
|
|
3618
3618
|
const channelId = stringField(body.channel_id) || stringField(body.channel);
|
|
3619
3619
|
const bookmarkId = stringField(body.bookmark_id);
|
|
3620
|
-
const
|
|
3621
|
-
if (!
|
|
3622
|
-
if (
|
|
3620
|
+
const channel2 = findBookmarkChannel(channelId);
|
|
3621
|
+
if (!channel2) return slackError(c, "channel_not_found");
|
|
3622
|
+
if (channel2.is_archived) return slackError(c, "is_archived");
|
|
3623
3623
|
const authSlackUser = getAuthSlackUser(authUser);
|
|
3624
3624
|
const authUserId = getAuthUserId(authUser);
|
|
3625
|
-
if (!isChannelMember(
|
|
3626
|
-
const bookmark = findBookmark(
|
|
3625
|
+
if (!isChannelMember(channel2, authSlackUser, authUserId)) return slackError(c, "not_in_channel");
|
|
3626
|
+
const bookmark = findBookmark(channel2.channel_id, bookmarkId);
|
|
3627
3627
|
if (!bookmark) return slackError(c, "not_found");
|
|
3628
3628
|
ss().bookmarks.delete(bookmark.id);
|
|
3629
3629
|
return slackOk(c, {});
|
|
@@ -4164,8 +4164,8 @@ function inspectorRoutes(ctx) {
|
|
|
4164
4164
|
}
|
|
4165
4165
|
function renderChannelsView(users) {
|
|
4166
4166
|
const channels = sortedChannels(ss().channels.all());
|
|
4167
|
-
const conversations = channels.filter((
|
|
4168
|
-
const dms = channels.filter((
|
|
4167
|
+
const conversations = channels.filter((channel2) => !channel2.is_im && !channel2.is_mpim);
|
|
4168
|
+
const dms = channels.filter((channel2) => channel2.is_im || channel2.is_mpim);
|
|
4169
4169
|
return [
|
|
4170
4170
|
renderSection(
|
|
4171
4171
|
"Channels",
|
|
@@ -4463,6 +4463,616 @@ function renderDeliveriesTable(deliveries, subscriptions, empty) {
|
|
|
4463
4463
|
empty
|
|
4464
4464
|
);
|
|
4465
4465
|
}
|
|
4466
|
+
function openapiRoutes({ app, baseUrl }) {
|
|
4467
|
+
app.get("/openapi.json", (c) => c.json(buildSpec(baseUrl)));
|
|
4468
|
+
}
|
|
4469
|
+
var ok = (description) => ({
|
|
4470
|
+
description,
|
|
4471
|
+
content: {
|
|
4472
|
+
"application/json": {
|
|
4473
|
+
schema: { type: "object", properties: { ok: { type: "boolean" } }, required: ["ok"] }
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
});
|
|
4477
|
+
var slackBody = (properties, required, description) => {
|
|
4478
|
+
const schema = { type: "object", properties, required: [...required] };
|
|
4479
|
+
return {
|
|
4480
|
+
required: required.length > 0,
|
|
4481
|
+
description,
|
|
4482
|
+
content: {
|
|
4483
|
+
"application/x-www-form-urlencoded": { schema },
|
|
4484
|
+
"application/json": { schema }
|
|
4485
|
+
}
|
|
4486
|
+
};
|
|
4487
|
+
};
|
|
4488
|
+
var channel = { type: "string", description: "Channel ID (or name for chat methods)." };
|
|
4489
|
+
var cursor = { type: "string", description: "Pagination cursor from response_metadata.next_cursor." };
|
|
4490
|
+
var limit = { type: "integer", description: "Page size, capped at 1000." };
|
|
4491
|
+
var jsonArray = (description) => ({
|
|
4492
|
+
type: ["array", "string"],
|
|
4493
|
+
items: { type: "object" },
|
|
4494
|
+
description: `${description} (array, or JSON-encoded string in form bodies).`
|
|
4495
|
+
});
|
|
4496
|
+
var richMessageFields = {
|
|
4497
|
+
blocks: jsonArray("Layout blocks"),
|
|
4498
|
+
attachments: jsonArray("Legacy attachments")
|
|
4499
|
+
};
|
|
4500
|
+
function buildSpec(baseUrl) {
|
|
4501
|
+
return {
|
|
4502
|
+
openapi: "3.1.0",
|
|
4503
|
+
info: {
|
|
4504
|
+
title: "Slack Web API (Emulated)",
|
|
4505
|
+
version: "1.0.0",
|
|
4506
|
+
description: "Emulated subset of the Slack Web API. Authenticate with a bearer bot token (mint one at POST /_emulate/credentials, or use a seeded token). Methods return 200 with { ok: false, error } on failure."
|
|
4507
|
+
},
|
|
4508
|
+
servers: [{ url: baseUrl }],
|
|
4509
|
+
components: {
|
|
4510
|
+
securitySchemes: {
|
|
4511
|
+
bearerAuth: {
|
|
4512
|
+
type: "http",
|
|
4513
|
+
scheme: "bearer",
|
|
4514
|
+
description: "Slack token, sent as `Authorization: Bearer xoxb-\u2026`."
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4517
|
+
},
|
|
4518
|
+
security: [{ bearerAuth: [] }],
|
|
4519
|
+
paths: {
|
|
4520
|
+
"/api/auth.test": {
|
|
4521
|
+
post: {
|
|
4522
|
+
operationId: "auth.test",
|
|
4523
|
+
tags: ["auth"],
|
|
4524
|
+
summary: "Check the token and return user/team identity",
|
|
4525
|
+
responses: { "200": ok("Identity for the authed token.") }
|
|
4526
|
+
}
|
|
4527
|
+
},
|
|
4528
|
+
"/api/chat.postMessage": {
|
|
4529
|
+
post: {
|
|
4530
|
+
operationId: "chat.postMessage",
|
|
4531
|
+
tags: ["chat"],
|
|
4532
|
+
summary: "Send a message to a channel",
|
|
4533
|
+
requestBody: slackBody(
|
|
4534
|
+
{
|
|
4535
|
+
channel,
|
|
4536
|
+
text: { type: "string" },
|
|
4537
|
+
thread_ts: { type: "string", description: "Parent message ts to reply in a thread." },
|
|
4538
|
+
...richMessageFields,
|
|
4539
|
+
metadata: { type: ["object", "string"] },
|
|
4540
|
+
parse: { type: "string" },
|
|
4541
|
+
username: { type: "string" },
|
|
4542
|
+
icon_url: { type: "string" },
|
|
4543
|
+
icon_emoji: { type: "string" }
|
|
4544
|
+
},
|
|
4545
|
+
["channel"],
|
|
4546
|
+
"The message to post. Requires text, blocks, or attachments."
|
|
4547
|
+
),
|
|
4548
|
+
responses: { "200": ok("The posted message with channel and ts.") }
|
|
4549
|
+
}
|
|
4550
|
+
},
|
|
4551
|
+
"/api/chat.postEphemeral": {
|
|
4552
|
+
post: {
|
|
4553
|
+
operationId: "chat.postEphemeral",
|
|
4554
|
+
tags: ["chat"],
|
|
4555
|
+
summary: "Send an ephemeral message to a user in a channel",
|
|
4556
|
+
requestBody: slackBody(
|
|
4557
|
+
{
|
|
4558
|
+
channel,
|
|
4559
|
+
user: { type: "string", description: "User ID who sees the message." },
|
|
4560
|
+
text: { type: "string" },
|
|
4561
|
+
thread_ts: { type: "string" },
|
|
4562
|
+
...richMessageFields
|
|
4563
|
+
},
|
|
4564
|
+
["channel", "user"],
|
|
4565
|
+
"The ephemeral message. Requires text, blocks, or attachments."
|
|
4566
|
+
),
|
|
4567
|
+
responses: { "200": ok("The ephemeral message_ts.") }
|
|
4568
|
+
}
|
|
4569
|
+
},
|
|
4570
|
+
"/api/chat.update": {
|
|
4571
|
+
post: {
|
|
4572
|
+
operationId: "chat.update",
|
|
4573
|
+
tags: ["chat"],
|
|
4574
|
+
summary: "Update an existing message",
|
|
4575
|
+
requestBody: slackBody(
|
|
4576
|
+
{
|
|
4577
|
+
channel,
|
|
4578
|
+
ts: { type: "string", description: "Timestamp of the message to update." },
|
|
4579
|
+
text: { type: "string" },
|
|
4580
|
+
...richMessageFields
|
|
4581
|
+
},
|
|
4582
|
+
["channel", "ts"],
|
|
4583
|
+
"The updated content."
|
|
4584
|
+
),
|
|
4585
|
+
responses: { "200": ok("The updated message.") }
|
|
4586
|
+
}
|
|
4587
|
+
},
|
|
4588
|
+
"/api/chat.delete": {
|
|
4589
|
+
post: {
|
|
4590
|
+
operationId: "chat.delete",
|
|
4591
|
+
tags: ["chat"],
|
|
4592
|
+
summary: "Delete a message",
|
|
4593
|
+
requestBody: slackBody({ channel, ts: { type: "string" } }, ["channel", "ts"], "The message to delete."),
|
|
4594
|
+
responses: { "200": ok("Deletion confirmation.") }
|
|
4595
|
+
}
|
|
4596
|
+
},
|
|
4597
|
+
"/api/chat.getPermalink": {
|
|
4598
|
+
post: {
|
|
4599
|
+
operationId: "chat.getPermalink",
|
|
4600
|
+
tags: ["chat"],
|
|
4601
|
+
summary: "Get a permalink for a message",
|
|
4602
|
+
requestBody: slackBody(
|
|
4603
|
+
{ channel, message_ts: { type: "string" } },
|
|
4604
|
+
["channel", "message_ts"],
|
|
4605
|
+
"The message to link to."
|
|
4606
|
+
),
|
|
4607
|
+
responses: { "200": ok("The permalink.") }
|
|
4608
|
+
}
|
|
4609
|
+
},
|
|
4610
|
+
"/api/chat.scheduleMessage": {
|
|
4611
|
+
post: {
|
|
4612
|
+
operationId: "chat.scheduleMessage",
|
|
4613
|
+
tags: ["chat"],
|
|
4614
|
+
summary: "Schedule a message for later delivery",
|
|
4615
|
+
requestBody: slackBody(
|
|
4616
|
+
{
|
|
4617
|
+
channel,
|
|
4618
|
+
text: { type: "string" },
|
|
4619
|
+
post_at: { type: "integer", description: "Unix timestamp (seconds) to post at." },
|
|
4620
|
+
thread_ts: { type: "string" },
|
|
4621
|
+
...richMessageFields
|
|
4622
|
+
},
|
|
4623
|
+
["channel", "post_at"],
|
|
4624
|
+
"The message to schedule. Requires text, blocks, or attachments."
|
|
4625
|
+
),
|
|
4626
|
+
responses: { "200": ok("The scheduled_message_id and post_at.") }
|
|
4627
|
+
}
|
|
4628
|
+
},
|
|
4629
|
+
"/api/conversations.list": {
|
|
4630
|
+
post: {
|
|
4631
|
+
operationId: "conversations.list",
|
|
4632
|
+
tags: ["conversations"],
|
|
4633
|
+
summary: "List conversations",
|
|
4634
|
+
requestBody: slackBody(
|
|
4635
|
+
{
|
|
4636
|
+
types: {
|
|
4637
|
+
type: "string",
|
|
4638
|
+
description: "Comma-separated: public_channel, private_channel, im, mpim. Defaults to public_channel."
|
|
4639
|
+
},
|
|
4640
|
+
exclude_archived: { type: ["boolean", "string"] },
|
|
4641
|
+
limit,
|
|
4642
|
+
cursor
|
|
4643
|
+
},
|
|
4644
|
+
[],
|
|
4645
|
+
"Listing filters."
|
|
4646
|
+
),
|
|
4647
|
+
responses: { "200": ok("Channel list with response_metadata.next_cursor.") }
|
|
4648
|
+
}
|
|
4649
|
+
},
|
|
4650
|
+
"/api/conversations.info": {
|
|
4651
|
+
post: {
|
|
4652
|
+
operationId: "conversations.info",
|
|
4653
|
+
tags: ["conversations"],
|
|
4654
|
+
summary: "Get a conversation's details",
|
|
4655
|
+
requestBody: slackBody({ channel }, ["channel"], "The conversation to look up."),
|
|
4656
|
+
responses: { "200": ok("The channel object.") }
|
|
4657
|
+
}
|
|
4658
|
+
},
|
|
4659
|
+
"/api/conversations.create": {
|
|
4660
|
+
post: {
|
|
4661
|
+
operationId: "conversations.create",
|
|
4662
|
+
tags: ["conversations"],
|
|
4663
|
+
summary: "Create a channel",
|
|
4664
|
+
requestBody: slackBody(
|
|
4665
|
+
{
|
|
4666
|
+
name: { type: "string", description: "Channel name (lowercase letters, digits, - and _)." },
|
|
4667
|
+
is_private: { type: ["boolean", "string"] }
|
|
4668
|
+
},
|
|
4669
|
+
["name"],
|
|
4670
|
+
"The channel to create."
|
|
4671
|
+
),
|
|
4672
|
+
responses: { "200": ok("The created channel.") }
|
|
4673
|
+
}
|
|
4674
|
+
},
|
|
4675
|
+
"/api/conversations.history": {
|
|
4676
|
+
post: {
|
|
4677
|
+
operationId: "conversations.history",
|
|
4678
|
+
tags: ["conversations"],
|
|
4679
|
+
summary: "Fetch a conversation's message history",
|
|
4680
|
+
requestBody: slackBody({ channel, limit, cursor }, ["channel"], "The conversation to read."),
|
|
4681
|
+
responses: { "200": ok("Messages, has_more, and response_metadata.next_cursor.") }
|
|
4682
|
+
}
|
|
4683
|
+
},
|
|
4684
|
+
"/api/conversations.replies": {
|
|
4685
|
+
post: {
|
|
4686
|
+
operationId: "conversations.replies",
|
|
4687
|
+
tags: ["conversations"],
|
|
4688
|
+
summary: "Fetch a thread of messages",
|
|
4689
|
+
requestBody: slackBody(
|
|
4690
|
+
{ channel, ts: { type: "string", description: "Parent message ts." } },
|
|
4691
|
+
["channel", "ts"],
|
|
4692
|
+
"The thread to read."
|
|
4693
|
+
),
|
|
4694
|
+
responses: { "200": ok("The thread messages.") }
|
|
4695
|
+
}
|
|
4696
|
+
},
|
|
4697
|
+
"/api/conversations.join": {
|
|
4698
|
+
post: {
|
|
4699
|
+
operationId: "conversations.join",
|
|
4700
|
+
tags: ["conversations"],
|
|
4701
|
+
summary: "Join a channel",
|
|
4702
|
+
requestBody: slackBody({ channel }, ["channel"], "The channel to join."),
|
|
4703
|
+
responses: { "200": ok("The joined channel.") }
|
|
4704
|
+
}
|
|
4705
|
+
},
|
|
4706
|
+
"/api/conversations.invite": {
|
|
4707
|
+
post: {
|
|
4708
|
+
operationId: "conversations.invite",
|
|
4709
|
+
tags: ["conversations"],
|
|
4710
|
+
summary: "Invite users to a channel",
|
|
4711
|
+
requestBody: slackBody(
|
|
4712
|
+
{
|
|
4713
|
+
channel,
|
|
4714
|
+
users: { type: "string", description: "Comma-separated user IDs (up to 100)." }
|
|
4715
|
+
},
|
|
4716
|
+
["channel", "users"],
|
|
4717
|
+
"The users to invite."
|
|
4718
|
+
),
|
|
4719
|
+
responses: { "200": ok("The updated channel.") }
|
|
4720
|
+
}
|
|
4721
|
+
},
|
|
4722
|
+
"/api/conversations.members": {
|
|
4723
|
+
post: {
|
|
4724
|
+
operationId: "conversations.members",
|
|
4725
|
+
tags: ["conversations"],
|
|
4726
|
+
summary: "List members of a conversation",
|
|
4727
|
+
requestBody: slackBody({ channel }, ["channel"], "The conversation to list."),
|
|
4728
|
+
responses: { "200": ok("Member user IDs.") }
|
|
4729
|
+
}
|
|
4730
|
+
},
|
|
4731
|
+
"/api/users.list": {
|
|
4732
|
+
post: {
|
|
4733
|
+
operationId: "users.list",
|
|
4734
|
+
tags: ["users"],
|
|
4735
|
+
summary: "List workspace users",
|
|
4736
|
+
requestBody: slackBody({ limit, cursor }, [], "Pagination options."),
|
|
4737
|
+
responses: { "200": ok("Member list with response_metadata.next_cursor.") }
|
|
4738
|
+
}
|
|
4739
|
+
},
|
|
4740
|
+
"/api/users.info": {
|
|
4741
|
+
post: {
|
|
4742
|
+
operationId: "users.info",
|
|
4743
|
+
tags: ["users"],
|
|
4744
|
+
summary: "Get a user's details",
|
|
4745
|
+
requestBody: slackBody({ user: { type: "string" } }, ["user"], "The user to look up."),
|
|
4746
|
+
responses: { "200": ok("The user object.") }
|
|
4747
|
+
}
|
|
4748
|
+
},
|
|
4749
|
+
"/api/users.lookupByEmail": {
|
|
4750
|
+
post: {
|
|
4751
|
+
operationId: "users.lookupByEmail",
|
|
4752
|
+
tags: ["users"],
|
|
4753
|
+
summary: "Find a user by email",
|
|
4754
|
+
requestBody: slackBody({ email: { type: "string" } }, ["email"], "The email to look up."),
|
|
4755
|
+
responses: { "200": ok("The matching user.") }
|
|
4756
|
+
}
|
|
4757
|
+
},
|
|
4758
|
+
"/api/users.profile.get": {
|
|
4759
|
+
post: {
|
|
4760
|
+
operationId: "users.profile.get",
|
|
4761
|
+
tags: ["users"],
|
|
4762
|
+
summary: "Get a user's profile",
|
|
4763
|
+
requestBody: slackBody(
|
|
4764
|
+
{ user: { type: "string", description: "Defaults to the authed user." } },
|
|
4765
|
+
[],
|
|
4766
|
+
"The user whose profile to read."
|
|
4767
|
+
),
|
|
4768
|
+
responses: { "200": ok("The profile.") }
|
|
4769
|
+
}
|
|
4770
|
+
},
|
|
4771
|
+
"/api/users.profile.set": {
|
|
4772
|
+
post: {
|
|
4773
|
+
operationId: "users.profile.set",
|
|
4774
|
+
tags: ["users"],
|
|
4775
|
+
summary: "Set a user's profile fields",
|
|
4776
|
+
requestBody: slackBody(
|
|
4777
|
+
{
|
|
4778
|
+
user: { type: "string", description: "Defaults to the authed user." },
|
|
4779
|
+
profile: {
|
|
4780
|
+
type: ["object", "string"],
|
|
4781
|
+
description: "Profile fields to merge (object, or JSON-encoded string in form bodies)."
|
|
4782
|
+
},
|
|
4783
|
+
name: { type: "string", description: "Single field name (alternative to profile)." },
|
|
4784
|
+
value: { type: "string", description: "Single field value (used with name)." }
|
|
4785
|
+
},
|
|
4786
|
+
[],
|
|
4787
|
+
"Either a profile object or a name/value pair."
|
|
4788
|
+
),
|
|
4789
|
+
responses: { "200": ok("The updated profile.") }
|
|
4790
|
+
}
|
|
4791
|
+
},
|
|
4792
|
+
"/api/users.getPresence": {
|
|
4793
|
+
post: {
|
|
4794
|
+
operationId: "users.getPresence",
|
|
4795
|
+
tags: ["users"],
|
|
4796
|
+
summary: "Get a user's presence",
|
|
4797
|
+
requestBody: slackBody(
|
|
4798
|
+
{ user: { type: "string", description: "Defaults to the authed user." } },
|
|
4799
|
+
[],
|
|
4800
|
+
"The user whose presence to read."
|
|
4801
|
+
),
|
|
4802
|
+
responses: { "200": ok("The presence state.") }
|
|
4803
|
+
}
|
|
4804
|
+
},
|
|
4805
|
+
"/api/users.setPresence": {
|
|
4806
|
+
post: {
|
|
4807
|
+
operationId: "users.setPresence",
|
|
4808
|
+
tags: ["users"],
|
|
4809
|
+
summary: "Set the authed user's presence",
|
|
4810
|
+
requestBody: slackBody(
|
|
4811
|
+
{ presence: { type: "string", enum: ["auto", "away"] } },
|
|
4812
|
+
["presence"],
|
|
4813
|
+
"The manual presence to set."
|
|
4814
|
+
),
|
|
4815
|
+
responses: { "200": ok("Confirmation.") }
|
|
4816
|
+
}
|
|
4817
|
+
},
|
|
4818
|
+
"/api/reactions.add": {
|
|
4819
|
+
post: {
|
|
4820
|
+
operationId: "reactions.add",
|
|
4821
|
+
tags: ["reactions"],
|
|
4822
|
+
summary: "Add a reaction to a message",
|
|
4823
|
+
requestBody: slackBody(
|
|
4824
|
+
{
|
|
4825
|
+
channel,
|
|
4826
|
+
timestamp: { type: "string", description: "Message ts to react to." },
|
|
4827
|
+
name: { type: "string", description: "Emoji name without colons." }
|
|
4828
|
+
},
|
|
4829
|
+
["channel", "timestamp", "name"],
|
|
4830
|
+
"The reaction to add."
|
|
4831
|
+
),
|
|
4832
|
+
responses: { "200": ok("Confirmation.") }
|
|
4833
|
+
}
|
|
4834
|
+
},
|
|
4835
|
+
"/api/reactions.remove": {
|
|
4836
|
+
post: {
|
|
4837
|
+
operationId: "reactions.remove",
|
|
4838
|
+
tags: ["reactions"],
|
|
4839
|
+
summary: "Remove a reaction from a message",
|
|
4840
|
+
requestBody: slackBody(
|
|
4841
|
+
{
|
|
4842
|
+
channel,
|
|
4843
|
+
timestamp: { type: "string" },
|
|
4844
|
+
name: { type: "string", description: "Emoji name without colons." }
|
|
4845
|
+
},
|
|
4846
|
+
["channel", "timestamp", "name"],
|
|
4847
|
+
"The reaction to remove."
|
|
4848
|
+
),
|
|
4849
|
+
responses: { "200": ok("Confirmation.") }
|
|
4850
|
+
}
|
|
4851
|
+
},
|
|
4852
|
+
"/api/reactions.get": {
|
|
4853
|
+
post: {
|
|
4854
|
+
operationId: "reactions.get",
|
|
4855
|
+
tags: ["reactions"],
|
|
4856
|
+
summary: "Get reactions for a message",
|
|
4857
|
+
requestBody: slackBody(
|
|
4858
|
+
{ channel, timestamp: { type: "string" } },
|
|
4859
|
+
["channel", "timestamp"],
|
|
4860
|
+
"The message to inspect."
|
|
4861
|
+
),
|
|
4862
|
+
responses: { "200": ok("The message with its reactions.") }
|
|
4863
|
+
}
|
|
4864
|
+
},
|
|
4865
|
+
"/api/pins.add": {
|
|
4866
|
+
post: {
|
|
4867
|
+
operationId: "pins.add",
|
|
4868
|
+
tags: ["pins"],
|
|
4869
|
+
summary: "Pin a message to a channel",
|
|
4870
|
+
requestBody: slackBody(
|
|
4871
|
+
{ channel, timestamp: { type: "string", description: "Message ts to pin." } },
|
|
4872
|
+
["channel", "timestamp"],
|
|
4873
|
+
"The message to pin."
|
|
4874
|
+
),
|
|
4875
|
+
responses: { "200": ok("Confirmation.") }
|
|
4876
|
+
}
|
|
4877
|
+
},
|
|
4878
|
+
"/api/pins.list": {
|
|
4879
|
+
post: {
|
|
4880
|
+
operationId: "pins.list",
|
|
4881
|
+
tags: ["pins"],
|
|
4882
|
+
summary: "List pinned items in a channel",
|
|
4883
|
+
requestBody: slackBody({ channel }, ["channel"], "The channel to list."),
|
|
4884
|
+
responses: { "200": ok("Pinned items.") }
|
|
4885
|
+
}
|
|
4886
|
+
},
|
|
4887
|
+
"/api/pins.remove": {
|
|
4888
|
+
post: {
|
|
4889
|
+
operationId: "pins.remove",
|
|
4890
|
+
tags: ["pins"],
|
|
4891
|
+
summary: "Unpin a message from a channel",
|
|
4892
|
+
requestBody: slackBody(
|
|
4893
|
+
{ channel, timestamp: { type: "string" } },
|
|
4894
|
+
["channel", "timestamp"],
|
|
4895
|
+
"The message to unpin."
|
|
4896
|
+
),
|
|
4897
|
+
responses: { "200": ok("Confirmation.") }
|
|
4898
|
+
}
|
|
4899
|
+
},
|
|
4900
|
+
"/api/bookmarks.add": {
|
|
4901
|
+
post: {
|
|
4902
|
+
operationId: "bookmarks.add",
|
|
4903
|
+
tags: ["bookmarks"],
|
|
4904
|
+
summary: "Add a bookmark to a channel",
|
|
4905
|
+
requestBody: slackBody(
|
|
4906
|
+
{
|
|
4907
|
+
channel_id: { type: "string" },
|
|
4908
|
+
title: { type: "string" },
|
|
4909
|
+
type: { type: "string", enum: ["link"] },
|
|
4910
|
+
link: { type: "string", description: "http(s) URL to bookmark." },
|
|
4911
|
+
emoji: { type: "string" },
|
|
4912
|
+
access_level: { type: "string", enum: ["read", "write"] },
|
|
4913
|
+
parent_id: { type: "string" }
|
|
4914
|
+
},
|
|
4915
|
+
["channel_id", "title", "type", "link"],
|
|
4916
|
+
"The bookmark to add."
|
|
4917
|
+
),
|
|
4918
|
+
responses: { "200": ok("The created bookmark.") }
|
|
4919
|
+
}
|
|
4920
|
+
},
|
|
4921
|
+
"/api/bookmarks.list": {
|
|
4922
|
+
post: {
|
|
4923
|
+
operationId: "bookmarks.list",
|
|
4924
|
+
tags: ["bookmarks"],
|
|
4925
|
+
summary: "List bookmarks in a channel",
|
|
4926
|
+
requestBody: slackBody({ channel_id: { type: "string" } }, ["channel_id"], "The channel to list."),
|
|
4927
|
+
responses: { "200": ok("Bookmark list.") }
|
|
4928
|
+
}
|
|
4929
|
+
},
|
|
4930
|
+
"/api/files.getUploadURLExternal": {
|
|
4931
|
+
post: {
|
|
4932
|
+
operationId: "files.getUploadURLExternal",
|
|
4933
|
+
tags: ["files"],
|
|
4934
|
+
summary: "Get an upload URL for a file",
|
|
4935
|
+
requestBody: slackBody(
|
|
4936
|
+
{
|
|
4937
|
+
filename: { type: "string" },
|
|
4938
|
+
length: { type: "integer", description: "File size in bytes." },
|
|
4939
|
+
alt_text: { type: "string" },
|
|
4940
|
+
snippet_type: { type: "string" }
|
|
4941
|
+
},
|
|
4942
|
+
["filename", "length"],
|
|
4943
|
+
"The file to stage. POST the raw bytes to the returned upload_url, then call files.completeUploadExternal."
|
|
4944
|
+
),
|
|
4945
|
+
responses: { "200": ok("The upload_url and file_id.") }
|
|
4946
|
+
}
|
|
4947
|
+
},
|
|
4948
|
+
"/api/files.completeUploadExternal": {
|
|
4949
|
+
post: {
|
|
4950
|
+
operationId: "files.completeUploadExternal",
|
|
4951
|
+
tags: ["files"],
|
|
4952
|
+
summary: "Finalize staged uploads and share them",
|
|
4953
|
+
requestBody: slackBody(
|
|
4954
|
+
{
|
|
4955
|
+
files: {
|
|
4956
|
+
type: ["array", "string"],
|
|
4957
|
+
items: {
|
|
4958
|
+
type: "object",
|
|
4959
|
+
properties: { id: { type: "string" }, title: { type: "string" } },
|
|
4960
|
+
required: ["id"]
|
|
4961
|
+
},
|
|
4962
|
+
description: "Staged files to complete (array, or JSON-encoded string in form bodies)."
|
|
4963
|
+
},
|
|
4964
|
+
channel_id: { type: "string", description: "Channel or user ID to share into." },
|
|
4965
|
+
channels: { type: "string", description: "Comma-separated channel IDs to share into." },
|
|
4966
|
+
initial_comment: { type: "string" },
|
|
4967
|
+
thread_ts: { type: "string" },
|
|
4968
|
+
blocks: jsonArray("Layout blocks for the share message")
|
|
4969
|
+
},
|
|
4970
|
+
["files"],
|
|
4971
|
+
"The staged uploads to finalize."
|
|
4972
|
+
),
|
|
4973
|
+
responses: { "200": ok("The completed file objects.") }
|
|
4974
|
+
}
|
|
4975
|
+
},
|
|
4976
|
+
"/api/files.list": {
|
|
4977
|
+
post: {
|
|
4978
|
+
operationId: "files.list",
|
|
4979
|
+
tags: ["files"],
|
|
4980
|
+
summary: "List files",
|
|
4981
|
+
requestBody: slackBody(
|
|
4982
|
+
{
|
|
4983
|
+
channel: { type: "string", description: "Filter to files visible in this channel." },
|
|
4984
|
+
user: { type: "string", description: "Filter to files uploaded by this user." },
|
|
4985
|
+
types: { type: "string", description: "Comma-separated file types. Defaults to all." },
|
|
4986
|
+
ts_from: { type: ["number", "string"] },
|
|
4987
|
+
ts_to: { type: ["number", "string"] },
|
|
4988
|
+
page: { type: "integer" },
|
|
4989
|
+
count: { type: "integer", description: "Page size, capped at 1000." }
|
|
4990
|
+
},
|
|
4991
|
+
[],
|
|
4992
|
+
"Listing filters."
|
|
4993
|
+
),
|
|
4994
|
+
responses: { "200": ok("Files with paging info.") }
|
|
4995
|
+
}
|
|
4996
|
+
},
|
|
4997
|
+
"/api/views.publish": {
|
|
4998
|
+
post: {
|
|
4999
|
+
operationId: "views.publish",
|
|
5000
|
+
tags: ["views"],
|
|
5001
|
+
summary: "Publish a user's App Home view",
|
|
5002
|
+
requestBody: slackBody(
|
|
5003
|
+
{
|
|
5004
|
+
user_id: { type: "string" },
|
|
5005
|
+
view: {
|
|
5006
|
+
type: ["object", "string"],
|
|
5007
|
+
description: "Home view payload (object, or JSON-encoded string in form bodies)."
|
|
5008
|
+
},
|
|
5009
|
+
hash: { type: "string", description: "Expected current view hash for conflict detection." }
|
|
5010
|
+
},
|
|
5011
|
+
["user_id", "view"],
|
|
5012
|
+
"The home view to publish."
|
|
5013
|
+
),
|
|
5014
|
+
responses: { "200": ok("The published view.") }
|
|
5015
|
+
}
|
|
5016
|
+
},
|
|
5017
|
+
"/api/views.open": {
|
|
5018
|
+
post: {
|
|
5019
|
+
operationId: "views.open",
|
|
5020
|
+
tags: ["views"],
|
|
5021
|
+
summary: "Open a modal view",
|
|
5022
|
+
requestBody: slackBody(
|
|
5023
|
+
{
|
|
5024
|
+
trigger_id: { type: "string", description: "Short-lived trigger from an interaction." },
|
|
5025
|
+
interactivity_pointer: { type: "string", description: "Alternative to trigger_id." },
|
|
5026
|
+
view: {
|
|
5027
|
+
type: ["object", "string"],
|
|
5028
|
+
description: "Modal view payload (object, or JSON-encoded string in form bodies)."
|
|
5029
|
+
}
|
|
5030
|
+
},
|
|
5031
|
+
["view"],
|
|
5032
|
+
"The modal to open. Requires trigger_id or interactivity_pointer."
|
|
5033
|
+
),
|
|
5034
|
+
responses: { "200": ok("The opened view.") }
|
|
5035
|
+
}
|
|
5036
|
+
},
|
|
5037
|
+
"/api/team.info": {
|
|
5038
|
+
post: {
|
|
5039
|
+
operationId: "team.info",
|
|
5040
|
+
tags: ["team"],
|
|
5041
|
+
summary: "Get workspace info",
|
|
5042
|
+
responses: { "200": ok("The team object.") }
|
|
5043
|
+
}
|
|
5044
|
+
},
|
|
5045
|
+
"/api/bots.info": {
|
|
5046
|
+
post: {
|
|
5047
|
+
operationId: "bots.info",
|
|
5048
|
+
tags: ["team"],
|
|
5049
|
+
summary: "Get a bot's details",
|
|
5050
|
+
requestBody: slackBody({ bot: { type: "string", description: "Bot ID." } }, ["bot"], "The bot to look up."),
|
|
5051
|
+
responses: { "200": ok("The bot object.") }
|
|
5052
|
+
}
|
|
5053
|
+
},
|
|
5054
|
+
"/api/oauth.v2.access": {
|
|
5055
|
+
post: {
|
|
5056
|
+
operationId: "oauth.v2.access",
|
|
5057
|
+
tags: ["oauth"],
|
|
5058
|
+
summary: "Exchange an OAuth v2 authorization code for tokens",
|
|
5059
|
+
security: [],
|
|
5060
|
+
requestBody: slackBody(
|
|
5061
|
+
{
|
|
5062
|
+
code: { type: "string" },
|
|
5063
|
+
client_id: { type: "string", description: "May also be sent via HTTP Basic auth." },
|
|
5064
|
+
client_secret: { type: "string", description: "May also be sent via HTTP Basic auth." },
|
|
5065
|
+
redirect_uri: { type: "string" }
|
|
5066
|
+
},
|
|
5067
|
+
["code"],
|
|
5068
|
+
"The authorization code exchange."
|
|
5069
|
+
),
|
|
5070
|
+
responses: { "200": ok("Bot and user access tokens.") }
|
|
5071
|
+
}
|
|
5072
|
+
}
|
|
5073
|
+
}
|
|
5074
|
+
};
|
|
5075
|
+
}
|
|
4466
5076
|
var manifest = {
|
|
4467
5077
|
id: "slack",
|
|
4468
5078
|
name: "Slack",
|
|
@@ -4480,9 +5090,10 @@ var manifest = {
|
|
|
4480
5090
|
],
|
|
4481
5091
|
specs: [
|
|
4482
5092
|
{
|
|
4483
|
-
kind: "
|
|
5093
|
+
kind: "openapi",
|
|
4484
5094
|
title: "Slack Web API subset",
|
|
4485
5095
|
coverage: "hand-authored",
|
|
5096
|
+
url: "/openapi.json",
|
|
4486
5097
|
notes: "The Web API is invoked over POST with form-encoded or JSON bodies and returns { ok: boolean, ... }.",
|
|
4487
5098
|
operations: [
|
|
4488
5099
|
{ operationId: "auth.test", method: "POST", path: "/api/auth.test", status: "hand-authored" },
|
|
@@ -4972,6 +5583,7 @@ var slackPlugin = {
|
|
|
4972
5583
|
bookmarksRoutes(ctx);
|
|
4973
5584
|
viewsRoutes(ctx);
|
|
4974
5585
|
inspectorRoutes(ctx);
|
|
5586
|
+
openapiRoutes(ctx);
|
|
4975
5587
|
},
|
|
4976
5588
|
seed(store, baseUrl) {
|
|
4977
5589
|
seedDefaults(store, baseUrl);
|
|
@@ -5106,4 +5718,4 @@ export {
|
|
|
5106
5718
|
seedFromConfig,
|
|
5107
5719
|
slackPlugin
|
|
5108
5720
|
};
|
|
5109
|
-
//# sourceMappingURL=dist-
|
|
5721
|
+
//# sourceMappingURL=dist-FE2JWST3.js.map
|