@zhin.js/adapter-slack 2.0.0 → 3.0.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/CHANGELOG.md +15 -0
- package/README.md +5 -5
- package/lib/adapter.d.ts +9 -8
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +24 -23
- package/lib/adapter.js.map +1 -1
- package/lib/{bot.d.ts → endpoint.d.ts} +8 -6
- package/lib/endpoint.d.ts.map +1 -0
- package/lib/{bot.js → endpoint.js} +79 -34
- package/lib/endpoint.js.map +1 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +59 -49
- package/lib/index.js.map +1 -1
- package/lib/platform-permit.d.ts +17 -0
- package/lib/platform-permit.d.ts.map +1 -0
- package/lib/platform-permit.js +49 -0
- package/lib/platform-permit.js.map +1 -0
- package/lib/types.d.ts +1 -1
- package/lib/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/skills/slack/PERMITS.md +24 -0
- package/src/adapter.ts +23 -21
- package/src/{bot.ts → endpoint.ts} +82 -36
- package/src/index.ts +62 -47
- package/src/platform-permit.ts +64 -0
- package/src/types.ts +1 -1
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js.map +0 -1
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Slack
|
|
2
|
+
* Slack Endpoint 实现
|
|
3
3
|
*/
|
|
4
4
|
import { App as SlackApp, LogLevel } from "@slack/bolt";
|
|
5
5
|
import { WebClient, type ChatPostMessageArguments } from "@slack/web-api";
|
|
6
|
-
import { formatCompact,
|
|
7
|
-
import type {
|
|
6
|
+
import { formatCompact, Endpoint, Message, MessageSegment, segment, SendContent, SendOptions } from 'zhin.js';
|
|
7
|
+
import type { SlackEndpointConfig, SlackMessageEvent } from "./types.js";
|
|
8
8
|
import type { SlackAdapter } from "./adapter.js";
|
|
9
|
+
import { normalizeSlackSenderForPermit } from "./platform-permit.js";
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
export class
|
|
12
|
+
export class SlackEndpoint implements Endpoint<SlackEndpointConfig, SlackMessageEvent> {
|
|
12
13
|
$connected: boolean;
|
|
13
14
|
/** 延迟到 `$connect`,避免子类 mock `$connect` 时仍在构造函数里创建 Bolt/WebClient(会在后台触发 Slack API) */
|
|
14
15
|
private app?: SlackApp;
|
|
@@ -22,7 +23,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
22
23
|
return this.$config.name;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
constructor(public adapter: SlackAdapter, public $config:
|
|
26
|
+
constructor(public adapter: SlackAdapter, public $config: SlackEndpointConfig) {
|
|
26
27
|
this.$connected = false;
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -73,7 +74,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
73
74
|
|
|
74
75
|
// Get bot info
|
|
75
76
|
const authTest = await this.client!.auth.test();
|
|
76
|
-
this.logger.info(formatCompact({
|
|
77
|
+
this.logger.info(formatCompact({ endpoint: this.$config.name, user: authTest.user }));
|
|
77
78
|
|
|
78
79
|
if (!this.$config.socketMode) {
|
|
79
80
|
this.logger.info(formatCompact( { op: "listen", port }));
|
|
@@ -93,13 +94,57 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
93
94
|
try {
|
|
94
95
|
await this.app!.stop();
|
|
95
96
|
this.$connected = false;
|
|
96
|
-
this.logger.info(formatCompact( { op: "disconnect",
|
|
97
|
+
this.logger.info(formatCompact( { op: "disconnect", endpoint: this.$config.name }));
|
|
97
98
|
} catch (error) {
|
|
98
99
|
this.logger.error("Error disconnecting Slack bot:", error);
|
|
99
100
|
throw error;
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
private senderPermitCache = new Map<string, { at: number; role?: string; permissions: string[] }>();
|
|
105
|
+
|
|
106
|
+
private async enrichChannelSender(
|
|
107
|
+
message: Message<SlackMessageEvent>,
|
|
108
|
+
msg: SlackMessageEvent,
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
if (message.$channel.type !== "group" || !("user" in msg) || !msg.user) return;
|
|
111
|
+
const channelId = msg.channel;
|
|
112
|
+
const userId = msg.user;
|
|
113
|
+
const key = `${channelId}:${userId}`;
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
const cached = this.senderPermitCache.get(key);
|
|
116
|
+
if (cached && now - cached.at < 60_000) {
|
|
117
|
+
message.$sender.role = cached.role;
|
|
118
|
+
message.$sender.permissions = cached.permissions;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const user = await this.getUserInfo(userId);
|
|
123
|
+
let isChannelManager = false;
|
|
124
|
+
try {
|
|
125
|
+
const channel = await this.getChannelInfo(channelId);
|
|
126
|
+
if (channel?.creator === userId) isChannelManager = true;
|
|
127
|
+
} catch {
|
|
128
|
+
// ignore
|
|
129
|
+
}
|
|
130
|
+
const normalized = normalizeSlackSenderForPermit({
|
|
131
|
+
isWorkspaceOwner: user.is_owner === true,
|
|
132
|
+
isWorkspaceAdmin: user.is_admin === true,
|
|
133
|
+
isChannelManager,
|
|
134
|
+
});
|
|
135
|
+
const entry = {
|
|
136
|
+
at: now,
|
|
137
|
+
role: normalized.role,
|
|
138
|
+
permissions: normalized.permissions ?? [],
|
|
139
|
+
};
|
|
140
|
+
this.senderPermitCache.set(key, entry);
|
|
141
|
+
message.$sender.role = entry.role;
|
|
142
|
+
message.$sender.permissions = entry.permissions;
|
|
143
|
+
} catch {
|
|
144
|
+
// 保守拒绝
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
103
148
|
private async handleSlackMessage(msg: SlackMessageEvent): Promise<void> {
|
|
104
149
|
// Ignore bot messages and message changes
|
|
105
150
|
if ("subtype" in msg && (msg.subtype === "bot_message" || msg.subtype === "message_changed")) {
|
|
@@ -107,6 +152,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
107
152
|
}
|
|
108
153
|
|
|
109
154
|
const message = this.$formatMessage(msg);
|
|
155
|
+
await this.enrichChannelSender(message, msg);
|
|
110
156
|
this.adapter.emit("message.receive", message);
|
|
111
157
|
this.logger.debug(
|
|
112
158
|
`${this.$config.name} recv ${message.$channel.type}(${message.$channel.id}): ${segment.raw(message.$content)}`
|
|
@@ -129,7 +175,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
129
175
|
const result = Message.from(msg, {
|
|
130
176
|
$id: msg.ts,
|
|
131
177
|
$adapter: "slack",
|
|
132
|
-
$
|
|
178
|
+
$endpoint: this.$config.name,
|
|
133
179
|
$sender: {
|
|
134
180
|
id: userId,
|
|
135
181
|
name: userName,
|
|
@@ -170,7 +216,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
170
216
|
|
|
171
217
|
return await this.adapter.sendMessage({
|
|
172
218
|
context: "slack",
|
|
173
|
-
|
|
219
|
+
endpoint: this.$config.name,
|
|
174
220
|
id: channelId,
|
|
175
221
|
type: "channel",
|
|
176
222
|
content: content,
|
|
@@ -458,10 +504,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
458
504
|
|
|
459
505
|
async $recallMessage(id: string): Promise<void> {
|
|
460
506
|
// Slack requires both channel and ts (timestamp) to delete a message
|
|
461
|
-
// The
|
|
507
|
+
// The Endpoint interface only provides message ID (ts), making recall impossible
|
|
462
508
|
// Users should use message.$recall() instead, which has the full context
|
|
463
509
|
throw new Error(
|
|
464
|
-
"
|
|
510
|
+
"SlackEndpoint.$recallMessage: Message recall not supported without channel information. " +
|
|
465
511
|
"Use message.$recall() method instead, which contains the required context."
|
|
466
512
|
);
|
|
467
513
|
}
|
|
@@ -476,10 +522,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
476
522
|
async inviteToChannel(channel: string, users: string[]): Promise<boolean> {
|
|
477
523
|
try {
|
|
478
524
|
await this.client!.conversations.invite({ channel, users: users.join(',') });
|
|
479
|
-
this.logger.debug(formatCompact( { op: "invite",
|
|
525
|
+
this.logger.debug(formatCompact( { op: "invite", endpoint: this.$id, channel, users: users.join(",") }));
|
|
480
526
|
return true;
|
|
481
527
|
} catch (error) {
|
|
482
|
-
this.logger.error(`Slack
|
|
528
|
+
this.logger.error(`Slack Endpoint ${this.$id} 邀请用户失败:`, error);
|
|
483
529
|
throw error;
|
|
484
530
|
}
|
|
485
531
|
}
|
|
@@ -492,10 +538,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
492
538
|
async kickFromChannel(channel: string, user: string): Promise<boolean> {
|
|
493
539
|
try {
|
|
494
540
|
await this.client!.conversations.kick({ channel, user });
|
|
495
|
-
this.logger.debug(formatCompact( { op: "kick",
|
|
541
|
+
this.logger.debug(formatCompact( { op: "kick", endpoint: this.$id, channel, user }));
|
|
496
542
|
return true;
|
|
497
543
|
} catch (error) {
|
|
498
|
-
this.logger.error(`Slack
|
|
544
|
+
this.logger.error(`Slack Endpoint ${this.$id} 踢出用户失败:`, error);
|
|
499
545
|
throw error;
|
|
500
546
|
}
|
|
501
547
|
}
|
|
@@ -508,10 +554,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
508
554
|
async setChannelTopic(channel: string, topic: string): Promise<boolean> {
|
|
509
555
|
try {
|
|
510
556
|
await this.client!.conversations.setTopic({ channel, topic });
|
|
511
|
-
this.logger.debug(formatCompact( { op: "set_topic",
|
|
557
|
+
this.logger.debug(formatCompact( { op: "set_topic", endpoint: this.$id, channel }));
|
|
512
558
|
return true;
|
|
513
559
|
} catch (error) {
|
|
514
|
-
this.logger.error(`Slack
|
|
560
|
+
this.logger.error(`Slack Endpoint ${this.$id} 设置话题失败:`, error);
|
|
515
561
|
throw error;
|
|
516
562
|
}
|
|
517
563
|
}
|
|
@@ -524,10 +570,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
524
570
|
async setChannelPurpose(channel: string, purpose: string): Promise<boolean> {
|
|
525
571
|
try {
|
|
526
572
|
await this.client!.conversations.setPurpose({ channel, purpose });
|
|
527
|
-
this.logger.debug(formatCompact( { op: "set_purpose",
|
|
573
|
+
this.logger.debug(formatCompact( { op: "set_purpose", endpoint: this.$id, channel }));
|
|
528
574
|
return true;
|
|
529
575
|
} catch (error) {
|
|
530
|
-
this.logger.error(`Slack
|
|
576
|
+
this.logger.error(`Slack Endpoint ${this.$id} 设置目的失败:`, error);
|
|
531
577
|
throw error;
|
|
532
578
|
}
|
|
533
579
|
}
|
|
@@ -539,10 +585,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
539
585
|
async archiveChannel(channel: string): Promise<boolean> {
|
|
540
586
|
try {
|
|
541
587
|
await this.client!.conversations.archive({ channel });
|
|
542
|
-
this.logger.debug(formatCompact( { op: "archive",
|
|
588
|
+
this.logger.debug(formatCompact( { op: "archive", endpoint: this.$id, channel }));
|
|
543
589
|
return true;
|
|
544
590
|
} catch (error) {
|
|
545
|
-
this.logger.error(`Slack
|
|
591
|
+
this.logger.error(`Slack Endpoint ${this.$id} 归档频道失败:`, error);
|
|
546
592
|
throw error;
|
|
547
593
|
}
|
|
548
594
|
}
|
|
@@ -554,10 +600,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
554
600
|
async unarchiveChannel(channel: string): Promise<boolean> {
|
|
555
601
|
try {
|
|
556
602
|
await this.client!.conversations.unarchive({ channel });
|
|
557
|
-
this.logger.debug(formatCompact( { op: "unarchive",
|
|
603
|
+
this.logger.debug(formatCompact( { op: "unarchive", endpoint: this.$id, channel }));
|
|
558
604
|
return true;
|
|
559
605
|
} catch (error) {
|
|
560
|
-
this.logger.error(`Slack
|
|
606
|
+
this.logger.error(`Slack Endpoint ${this.$id} 取消归档失败:`, error);
|
|
561
607
|
throw error;
|
|
562
608
|
}
|
|
563
609
|
}
|
|
@@ -570,10 +616,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
570
616
|
async renameChannel(channel: string, name: string): Promise<boolean> {
|
|
571
617
|
try {
|
|
572
618
|
await this.client!.conversations.rename({ channel, name });
|
|
573
|
-
this.logger.debug(formatCompact( { op: "rename",
|
|
619
|
+
this.logger.debug(formatCompact( { op: "rename", endpoint: this.$id, channel, name }));
|
|
574
620
|
return true;
|
|
575
621
|
} catch (error) {
|
|
576
|
-
this.logger.error(`Slack
|
|
622
|
+
this.logger.error(`Slack Endpoint ${this.$id} 重命名频道失败:`, error);
|
|
577
623
|
throw error;
|
|
578
624
|
}
|
|
579
625
|
}
|
|
@@ -587,7 +633,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
587
633
|
const result = await this.client!.conversations.members({ channel });
|
|
588
634
|
return result.members || [];
|
|
589
635
|
} catch (error) {
|
|
590
|
-
this.logger.error(`Slack
|
|
636
|
+
this.logger.error(`Slack Endpoint ${this.$id} 获取成员列表失败:`, error);
|
|
591
637
|
throw error;
|
|
592
638
|
}
|
|
593
639
|
}
|
|
@@ -601,7 +647,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
601
647
|
const result = await this.client!.conversations.info({ channel });
|
|
602
648
|
return result.channel;
|
|
603
649
|
} catch (error) {
|
|
604
|
-
this.logger.error(`Slack
|
|
650
|
+
this.logger.error(`Slack Endpoint ${this.$id} 获取频道信息失败:`, error);
|
|
605
651
|
throw error;
|
|
606
652
|
}
|
|
607
653
|
}
|
|
@@ -615,7 +661,7 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
615
661
|
const result = await this.client!.users.info({ user });
|
|
616
662
|
return result.user;
|
|
617
663
|
} catch (error) {
|
|
618
|
-
this.logger.error(`Slack
|
|
664
|
+
this.logger.error(`Slack Endpoint ${this.$id} 获取用户信息失败:`, error);
|
|
619
665
|
throw error;
|
|
620
666
|
}
|
|
621
667
|
}
|
|
@@ -629,10 +675,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
629
675
|
async addReaction(channel: string, timestamp: string, name: string): Promise<boolean> {
|
|
630
676
|
try {
|
|
631
677
|
await this.client!.reactions.add({ channel, timestamp, name });
|
|
632
|
-
this.logger.debug(formatCompact( { op: "reaction_add",
|
|
678
|
+
this.logger.debug(formatCompact( { op: "reaction_add", endpoint: this.$id, name }));
|
|
633
679
|
return true;
|
|
634
680
|
} catch (error) {
|
|
635
|
-
this.logger.error(`Slack
|
|
681
|
+
this.logger.error(`Slack Endpoint ${this.$id} 添加反应失败:`, error);
|
|
636
682
|
throw error;
|
|
637
683
|
}
|
|
638
684
|
}
|
|
@@ -646,10 +692,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
646
692
|
async removeReaction(channel: string, timestamp: string, name: string): Promise<boolean> {
|
|
647
693
|
try {
|
|
648
694
|
await this.client!.reactions.remove({ channel, timestamp, name });
|
|
649
|
-
this.logger.debug(formatCompact( { op: "reaction_remove",
|
|
695
|
+
this.logger.debug(formatCompact( { op: "reaction_remove", endpoint: this.$id, name }));
|
|
650
696
|
return true;
|
|
651
697
|
} catch (error) {
|
|
652
|
-
this.logger.error(`Slack
|
|
698
|
+
this.logger.error(`Slack Endpoint ${this.$id} 移除反应失败:`, error);
|
|
653
699
|
throw error;
|
|
654
700
|
}
|
|
655
701
|
}
|
|
@@ -662,10 +708,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
662
708
|
async pinMessage(channel: string, timestamp: string): Promise<boolean> {
|
|
663
709
|
try {
|
|
664
710
|
await this.client!.pins.add({ channel, timestamp });
|
|
665
|
-
this.logger.debug(formatCompact( { op: "pin",
|
|
711
|
+
this.logger.debug(formatCompact( { op: "pin", endpoint: this.$id, channel }));
|
|
666
712
|
return true;
|
|
667
713
|
} catch (error) {
|
|
668
|
-
this.logger.error(`Slack
|
|
714
|
+
this.logger.error(`Slack Endpoint ${this.$id} 置顶消息失败:`, error);
|
|
669
715
|
throw error;
|
|
670
716
|
}
|
|
671
717
|
}
|
|
@@ -678,10 +724,10 @@ export class SlackBot implements Bot<SlackBotConfig, SlackMessageEvent> {
|
|
|
678
724
|
async unpinMessage(channel: string, timestamp: string): Promise<boolean> {
|
|
679
725
|
try {
|
|
680
726
|
await this.client!.pins.remove({ channel, timestamp });
|
|
681
|
-
this.logger.debug(formatCompact( { op: "unpin",
|
|
727
|
+
this.logger.debug(formatCompact( { op: "unpin", endpoint: this.$id, channel }));
|
|
682
728
|
return true;
|
|
683
729
|
} catch (error) {
|
|
684
|
-
this.logger.error(`Slack
|
|
730
|
+
this.logger.error(`Slack Endpoint ${this.$id} 取消置顶失败:`, error);
|
|
685
731
|
throw error;
|
|
686
732
|
}
|
|
687
733
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { usePlugin, type Plugin, type IGroupManagement, createGroupManagementTools, type ToolFeature } from "zhin.js";
|
|
5
5
|
import { SlackAdapter } from "./adapter.js";
|
|
6
|
+
import {
|
|
7
|
+
platformPermit,
|
|
8
|
+
registerSlackPlatformPermitChecker,
|
|
9
|
+
slackGroupPermitResolver,
|
|
10
|
+
} from "./platform-permit.js";
|
|
6
11
|
|
|
7
12
|
declare module "zhin.js" {
|
|
8
13
|
interface Adapters {
|
|
@@ -11,7 +16,7 @@ declare module "zhin.js" {
|
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
export * from "./types.js";
|
|
14
|
-
export {
|
|
19
|
+
export { SlackEndpoint } from "./endpoint.js";
|
|
15
20
|
export { SlackAdapter } from "./adapter.js";
|
|
16
21
|
|
|
17
22
|
const plugin = usePlugin();
|
|
@@ -19,7 +24,7 @@ const { provide, useContext } = plugin;
|
|
|
19
24
|
|
|
20
25
|
provide({
|
|
21
26
|
name: "slack",
|
|
22
|
-
description: "Slack
|
|
27
|
+
description: "Slack Endpoint Adapter",
|
|
23
28
|
mounted: async (p: Plugin) => {
|
|
24
29
|
const adapter = new SlackAdapter(p);
|
|
25
30
|
await adapter.start();
|
|
@@ -31,16 +36,19 @@ provide({
|
|
|
31
36
|
});
|
|
32
37
|
|
|
33
38
|
useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
|
|
39
|
+
const disposers: (() => void)[] = [];
|
|
40
|
+
disposers.push(registerSlackPlatformPermitChecker());
|
|
34
41
|
const groupTools = createGroupManagementTools(
|
|
35
42
|
slack as unknown as IGroupManagement,
|
|
36
43
|
'slack',
|
|
44
|
+
{ permitResolver: slackGroupPermitResolver, registerChecker: false },
|
|
37
45
|
);
|
|
38
|
-
|
|
46
|
+
disposers.push(...groupTools.map(t => toolService.addTool(t, plugin.name)));
|
|
39
47
|
|
|
40
|
-
function
|
|
41
|
-
const
|
|
42
|
-
if (!
|
|
43
|
-
return
|
|
48
|
+
function getEndpoint(endpointId: string) {
|
|
49
|
+
const endpoint = slack.endpoints.get(endpointId);
|
|
50
|
+
if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
|
|
51
|
+
return endpoint;
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
disposers.push(toolService.addTool({
|
|
@@ -49,17 +57,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
|
|
|
49
57
|
parameters: {
|
|
50
58
|
type: 'object',
|
|
51
59
|
properties: {
|
|
52
|
-
|
|
60
|
+
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
53
61
|
channel: { type: 'string', description: '频道 ID' },
|
|
54
62
|
users: { type: 'string', description: '用户 ID 列表(逗号分隔)' },
|
|
55
63
|
},
|
|
56
|
-
required: ['
|
|
64
|
+
required: ['endpoint_id', 'channel', 'users'],
|
|
57
65
|
},
|
|
58
66
|
platforms: ['slack'],
|
|
59
67
|
tags: ['slack'],
|
|
68
|
+
permissions: [platformPermit('channel_manager')],
|
|
60
69
|
execute: async (args: Record<string, any>) => {
|
|
61
|
-
const
|
|
62
|
-
const success = await
|
|
70
|
+
const endpoint = getEndpoint(args.endpoint_id);
|
|
71
|
+
const success = await endpoint.inviteToChannel(args.channel, args.users.split(','));
|
|
63
72
|
return { success, message: success ? '已邀请用户加入频道' : '操作失败' };
|
|
64
73
|
},
|
|
65
74
|
}, plugin.name));
|
|
@@ -70,17 +79,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
|
|
|
70
79
|
parameters: {
|
|
71
80
|
type: 'object',
|
|
72
81
|
properties: {
|
|
73
|
-
|
|
82
|
+
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
74
83
|
channel: { type: 'string', description: '频道 ID' },
|
|
75
84
|
topic: { type: 'string', description: '新话题' },
|
|
76
85
|
},
|
|
77
|
-
required: ['
|
|
86
|
+
required: ['endpoint_id', 'channel', 'topic'],
|
|
78
87
|
},
|
|
79
88
|
platforms: ['slack'],
|
|
80
89
|
tags: ['slack'],
|
|
90
|
+
permissions: [platformPermit('channel_manager')],
|
|
81
91
|
execute: async (args: Record<string, any>) => {
|
|
82
|
-
const
|
|
83
|
-
const success = await
|
|
92
|
+
const endpoint = getEndpoint(args.endpoint_id);
|
|
93
|
+
const success = await endpoint.setChannelTopic(args.channel, args.topic);
|
|
84
94
|
return { success, message: success ? '已设置频道话题' : '操作失败' };
|
|
85
95
|
},
|
|
86
96
|
}, plugin.name));
|
|
@@ -91,16 +101,17 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
|
|
|
91
101
|
parameters: {
|
|
92
102
|
type: 'object',
|
|
93
103
|
properties: {
|
|
94
|
-
|
|
104
|
+
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
95
105
|
channel: { type: 'string', description: '频道 ID' },
|
|
96
106
|
},
|
|
97
|
-
required: ['
|
|
107
|
+
required: ['endpoint_id', 'channel'],
|
|
98
108
|
},
|
|
99
109
|
platforms: ['slack'],
|
|
100
110
|
tags: ['slack'],
|
|
111
|
+
permissions: [platformPermit('workspace_admin')],
|
|
101
112
|
execute: async (args: Record<string, any>) => {
|
|
102
|
-
const
|
|
103
|
-
const success = await
|
|
113
|
+
const endpoint = getEndpoint(args.endpoint_id);
|
|
114
|
+
const success = await endpoint.archiveChannel(args.channel);
|
|
104
115
|
return { success, message: success ? '已归档频道' : '操作失败' };
|
|
105
116
|
},
|
|
106
117
|
}, plugin.name));
|
|
@@ -111,17 +122,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
|
|
|
111
122
|
parameters: {
|
|
112
123
|
type: 'object',
|
|
113
124
|
properties: {
|
|
114
|
-
|
|
125
|
+
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
115
126
|
channel: { type: 'string', description: '频道 ID' },
|
|
116
127
|
timestamp: { type: 'string', description: '消息时间戳' },
|
|
117
128
|
},
|
|
118
|
-
required: ['
|
|
129
|
+
required: ['endpoint_id', 'channel', 'timestamp'],
|
|
119
130
|
},
|
|
120
131
|
platforms: ['slack'],
|
|
121
132
|
tags: ['slack'],
|
|
133
|
+
permissions: [platformPermit('channel_manager')],
|
|
122
134
|
execute: async (args: Record<string, any>) => {
|
|
123
|
-
const
|
|
124
|
-
const success = await
|
|
135
|
+
const endpoint = getEndpoint(args.endpoint_id);
|
|
136
|
+
const success = await endpoint.pinMessage(args.channel, args.timestamp);
|
|
125
137
|
return { success, message: success ? '已置顶消息' : '操作失败' };
|
|
126
138
|
},
|
|
127
139
|
}, plugin.name));
|
|
@@ -132,18 +144,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
|
|
|
132
144
|
parameters: {
|
|
133
145
|
type: 'object',
|
|
134
146
|
properties: {
|
|
135
|
-
|
|
147
|
+
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
136
148
|
channel: { type: 'string', description: '频道 ID' },
|
|
137
149
|
timestamp: { type: 'string', description: '消息时间戳' },
|
|
138
150
|
emoji: { type: 'string', description: '表情名称(不含冒号)' },
|
|
139
151
|
},
|
|
140
|
-
required: ['
|
|
152
|
+
required: ['endpoint_id', 'channel', 'timestamp', 'emoji'],
|
|
141
153
|
},
|
|
142
154
|
platforms: ['slack'],
|
|
143
155
|
tags: ['slack'],
|
|
144
156
|
execute: async (args: Record<string, any>) => {
|
|
145
|
-
const
|
|
146
|
-
const success = await
|
|
157
|
+
const endpoint = getEndpoint(args.endpoint_id);
|
|
158
|
+
const success = await endpoint.addReaction(args.channel, args.timestamp, args.emoji);
|
|
147
159
|
return { success, message: success ? `已添加反应 :${args.emoji}:` : '操作失败' };
|
|
148
160
|
},
|
|
149
161
|
}, plugin.name));
|
|
@@ -154,18 +166,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
|
|
|
154
166
|
parameters: {
|
|
155
167
|
type: 'object',
|
|
156
168
|
properties: {
|
|
157
|
-
|
|
169
|
+
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
158
170
|
channel_id: { type: 'string', description: '频道 ID' },
|
|
159
171
|
timestamp: { type: 'string', description: '消息时间戳' },
|
|
160
172
|
name: { type: 'string', description: '表情名称(如 thumbsup、heart)' },
|
|
161
173
|
},
|
|
162
|
-
required: ['
|
|
174
|
+
required: ['endpoint_id', 'channel_id', 'timestamp', 'name'],
|
|
163
175
|
},
|
|
164
176
|
platforms: ['slack'],
|
|
165
177
|
tags: ['slack'],
|
|
166
178
|
execute: async (args: Record<string, any>) => {
|
|
167
|
-
const
|
|
168
|
-
const success = await
|
|
179
|
+
const endpoint = getEndpoint(args.endpoint_id);
|
|
180
|
+
const success = await endpoint.removeReaction(args.channel_id, args.timestamp, args.name);
|
|
169
181
|
return { success, message: success ? `已移除反应 :${args.name}:` : '操作失败' };
|
|
170
182
|
},
|
|
171
183
|
}, plugin.name));
|
|
@@ -176,17 +188,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
|
|
|
176
188
|
parameters: {
|
|
177
189
|
type: 'object',
|
|
178
190
|
properties: {
|
|
179
|
-
|
|
191
|
+
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
180
192
|
channel_id: { type: 'string', description: '频道 ID' },
|
|
181
193
|
timestamp: { type: 'string', description: '消息时间戳' },
|
|
182
194
|
},
|
|
183
|
-
required: ['
|
|
195
|
+
required: ['endpoint_id', 'channel_id', 'timestamp'],
|
|
184
196
|
},
|
|
185
197
|
platforms: ['slack'],
|
|
186
198
|
tags: ['slack'],
|
|
199
|
+
permissions: [platformPermit('channel_manager')],
|
|
187
200
|
execute: async (args: Record<string, any>) => {
|
|
188
|
-
const
|
|
189
|
-
const success = await
|
|
201
|
+
const endpoint = getEndpoint(args.endpoint_id);
|
|
202
|
+
const success = await endpoint.unpinMessage(args.channel_id, args.timestamp);
|
|
190
203
|
return { success, message: success ? '已取消置顶' : '操作失败' };
|
|
191
204
|
},
|
|
192
205
|
}, plugin.name));
|
|
@@ -197,16 +210,16 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
|
|
|
197
210
|
parameters: {
|
|
198
211
|
type: 'object',
|
|
199
212
|
properties: {
|
|
200
|
-
|
|
213
|
+
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
201
214
|
user_id: { type: 'string', description: '用户 ID' },
|
|
202
215
|
},
|
|
203
|
-
required: ['
|
|
216
|
+
required: ['endpoint_id', 'user_id'],
|
|
204
217
|
},
|
|
205
218
|
platforms: ['slack'],
|
|
206
219
|
tags: ['slack'],
|
|
207
220
|
execute: async (args: Record<string, any>) => {
|
|
208
|
-
const
|
|
209
|
-
const user = await
|
|
221
|
+
const endpoint = getEndpoint(args.endpoint_id);
|
|
222
|
+
const user = await endpoint.getUserInfo(args.user_id);
|
|
210
223
|
return {
|
|
211
224
|
id: user.id,
|
|
212
225
|
name: user.name,
|
|
@@ -226,17 +239,18 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
|
|
|
226
239
|
parameters: {
|
|
227
240
|
type: 'object',
|
|
228
241
|
properties: {
|
|
229
|
-
|
|
242
|
+
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
230
243
|
channel_id: { type: 'string', description: '频道 ID' },
|
|
231
244
|
purpose: { type: 'string', description: '频道用途描述' },
|
|
232
245
|
},
|
|
233
|
-
required: ['
|
|
246
|
+
required: ['endpoint_id', 'channel_id', 'purpose'],
|
|
234
247
|
},
|
|
235
248
|
platforms: ['slack'],
|
|
236
249
|
tags: ['slack'],
|
|
250
|
+
permissions: [platformPermit('channel_manager')],
|
|
237
251
|
execute: async (args: Record<string, any>) => {
|
|
238
|
-
const
|
|
239
|
-
const success = await
|
|
252
|
+
const endpoint = getEndpoint(args.endpoint_id);
|
|
253
|
+
const success = await endpoint.setChannelPurpose(args.channel_id, args.purpose);
|
|
240
254
|
return { success, message: success ? '频道用途已更新' : '操作失败' };
|
|
241
255
|
},
|
|
242
256
|
}, plugin.name));
|
|
@@ -247,16 +261,17 @@ useContext('tool', 'slack', (toolService: ToolFeature, slack: SlackAdapter) => {
|
|
|
247
261
|
parameters: {
|
|
248
262
|
type: 'object',
|
|
249
263
|
properties: {
|
|
250
|
-
|
|
264
|
+
endpoint_id: { type: 'string', description: 'Endpoint 名称', contextKey: 'endpointId' },
|
|
251
265
|
channel_id: { type: 'string', description: '频道 ID' },
|
|
252
266
|
},
|
|
253
|
-
required: ['
|
|
267
|
+
required: ['endpoint_id', 'channel_id'],
|
|
254
268
|
},
|
|
255
269
|
platforms: ['slack'],
|
|
256
270
|
tags: ['slack'],
|
|
271
|
+
permissions: [platformPermit('workspace_admin')],
|
|
257
272
|
execute: async (args: Record<string, any>) => {
|
|
258
|
-
const
|
|
259
|
-
const success = await
|
|
273
|
+
const endpoint = getEndpoint(args.endpoint_id);
|
|
274
|
+
const success = await endpoint.unarchiveChannel(args.channel_id);
|
|
260
275
|
return { success, message: success ? '频道已恢复' : '操作失败' };
|
|
261
276
|
},
|
|
262
277
|
}, plugin.name));
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack platform permit — workspace + channel 权限
|
|
3
|
+
*/
|
|
4
|
+
import type { Message } from 'zhin.js';
|
|
5
|
+
import { registerPlatformPermitChecker } from 'zhin.js';
|
|
6
|
+
|
|
7
|
+
const ADAPTER = 'slack';
|
|
8
|
+
|
|
9
|
+
export function platformPermit(perm: string): string {
|
|
10
|
+
return `platform(${ADAPTER},${perm})`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const FACTORY_PERM_MAP: Record<string, string> = {
|
|
14
|
+
group_admin: 'channel_manager',
|
|
15
|
+
group_owner: 'workspace_owner',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function slackGroupPermitResolver(logicalPerm: string): string {
|
|
19
|
+
return platformPermit(FACTORY_PERM_MAP[logicalPerm] ?? logicalPerm);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function normalizeSlackSenderForPermit(input: {
|
|
23
|
+
isWorkspaceOwner?: boolean;
|
|
24
|
+
isWorkspaceAdmin?: boolean;
|
|
25
|
+
isChannelManager?: boolean;
|
|
26
|
+
}): { role?: string; permissions?: string[] } {
|
|
27
|
+
const permissions: string[] = [];
|
|
28
|
+
if (input.isWorkspaceOwner) {
|
|
29
|
+
permissions.push('workspace_owner', 'workspace_admin', 'channel_manager');
|
|
30
|
+
return { role: 'owner', permissions };
|
|
31
|
+
}
|
|
32
|
+
if (input.isWorkspaceAdmin) {
|
|
33
|
+
permissions.push('workspace_admin', 'channel_manager');
|
|
34
|
+
return { role: 'admin', permissions };
|
|
35
|
+
}
|
|
36
|
+
if (input.isChannelManager) {
|
|
37
|
+
permissions.push('channel_manager');
|
|
38
|
+
return { role: 'channel_admin', permissions };
|
|
39
|
+
}
|
|
40
|
+
return { role: 'member', permissions };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function checkSlackPlatformPermit(perm: string, message: Message<any>): boolean {
|
|
44
|
+
const sender = message.$sender as { role?: string; permissions?: string[] };
|
|
45
|
+
const permissions = sender.permissions ?? [];
|
|
46
|
+
const role = sender.role;
|
|
47
|
+
const has = (t: string) => permissions.includes(t);
|
|
48
|
+
|
|
49
|
+
switch (perm) {
|
|
50
|
+
case 'workspace_owner':
|
|
51
|
+
return role === 'owner' || has('workspace_owner');
|
|
52
|
+
case 'workspace_admin':
|
|
53
|
+
return role === 'owner' || role === 'admin' || has('workspace_admin') || has('workspace_owner');
|
|
54
|
+
case 'channel_manager':
|
|
55
|
+
return role === 'owner' || role === 'admin' || role === 'channel_admin'
|
|
56
|
+
|| has('channel_manager') || has('workspace_admin') || has('workspace_owner');
|
|
57
|
+
default:
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function registerSlackPlatformPermitChecker(): () => void {
|
|
63
|
+
return registerPlatformPermitChecker(ADAPTER, checkSlackPlatformPermit);
|
|
64
|
+
}
|
package/src/types.ts
CHANGED
package/lib/bot.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"AAKA,OAAO,EAAiB,GAAG,EAAE,OAAO,EAAwC,WAAW,EAAE,MAAM,SAAS,CAAC;AACzG,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,qBAAa,QAAS,YAAW,GAAG,CAAC,cAAc,EAAE,iBAAiB,CAAC;;IAclD,OAAO,EAAE,YAAY;IAAS,OAAO,EAAE,cAAc;IAbxE,UAAU,EAAE,OAAO,CAAC;IACpB,sFAAsF;IACtF,OAAO,CAAC,GAAG,CAAC,CAAW;IACvB,OAAO,CAAC,MAAM,CAAC,CAAY;IAE3B,IAAI,MAAM,6BAET;IAED,IAAI,GAAG,WAEN;gBAEkB,OAAO,EAAE,YAAY,EAAS,OAAO,EAAE,cAAc;IA0BlE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAqCzB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;YAepB,kBAAkB;IAahC,cAAc,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAoElE,OAAO,CAAC,mBAAmB;IAiF3B,OAAO,CAAC,cAAc;IA+FhB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;YAgB3C,oBAAoB;IAmF5B,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/C;;;;OAIG;IACG,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAWzE;;;;OAIG;IACG,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWtE;;;;OAIG;IACG,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWvE;;;;OAIG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAW3E;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWvD;;;OAGG;IACG,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWzD;;;;OAIG;IACG,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWpE;;;OAGG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAU3D;;;OAGG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAUnD;;;OAGG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAU7C;;;;;OAKG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWrF;;;;;OAKG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWxF;;;;OAIG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWtE;;;;OAIG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAUzE"}
|