@zhin.js/adapter-slack 4.1.0 → 4.1.1

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.
Files changed (118) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +58 -250
  3. package/adapters/slack.ts +34 -0
  4. package/agent/tools/add_reaction.ts +2 -2
  5. package/agent/tools/archive_channel.ts +2 -2
  6. package/agent/tools/edit_message.ts +5 -4
  7. package/agent/tools/invite_to_channel.ts +2 -2
  8. package/agent/tools/pin_message.ts +2 -2
  9. package/agent/tools/remove_reaction.ts +2 -2
  10. package/agent/tools/set_purpose.ts +2 -2
  11. package/agent/tools/set_topic.ts +2 -2
  12. package/agent/tools/unarchive.ts +2 -2
  13. package/agent/tools/unpin_message.ts +2 -2
  14. package/agent/tools/user_info.ts +2 -2
  15. package/lib/endpoint.d.ts +147 -0
  16. package/lib/endpoint.js +335 -0
  17. package/lib/index.d.ts +11 -0
  18. package/lib/index.js +11 -0
  19. package/lib/markdown-to-mrkdwn.d.ts +9 -0
  20. package/lib/markdown-to-mrkdwn.js +121 -0
  21. package/lib/mrkdwn-to-markdown.d.ts +4 -0
  22. package/lib/{src/mrkdwn-to-markdown.js → mrkdwn-to-markdown.js} +0 -1
  23. package/lib/platform-permit.d.ts +16 -0
  24. package/lib/{src/platform-permit.js → platform-permit.js} +1 -2
  25. package/lib/protocol.d.ts +143 -0
  26. package/lib/protocol.js +225 -0
  27. package/lib/slack-agent-deps.d.ts +28 -0
  28. package/lib/slack-agent-deps.js +30 -0
  29. package/lib/slack-inbound-filter.d.ts +12 -0
  30. package/lib/{src/slack-inbound-filter.js → slack-inbound-filter.js} +0 -1
  31. package/lib/slack-message-ref.d.ts +7 -0
  32. package/lib/{src/slack-message-ref.js → slack-message-ref.js} +0 -1
  33. package/lib/slack-outbound.d.ts +24 -0
  34. package/lib/slack-outbound.js +109 -0
  35. package/lib/slack-reaction.d.ts +1 -0
  36. package/lib/{src/slack-reaction.js → slack-reaction.js} +0 -1
  37. package/lib/slack-response-url.d.ts +5 -0
  38. package/lib/{src/slack-response-url.js → slack-response-url.js} +0 -1
  39. package/lib/webhook.d.ts +14 -0
  40. package/lib/webhook.js +69 -0
  41. package/package.json +41 -20
  42. package/plugin.ts +13 -0
  43. package/schema.json +38 -0
  44. package/src/endpoint.ts +317 -227
  45. package/src/index.ts +62 -66
  46. package/src/markdown-to-mrkdwn.ts +63 -4
  47. package/src/platform-permit.ts +1 -1
  48. package/src/protocol.ts +380 -0
  49. package/src/slack-agent-deps.ts +46 -11
  50. package/src/slack-inbound-filter.ts +1 -1
  51. package/src/slack-outbound.ts +32 -134
  52. package/src/webhook.ts +103 -0
  53. package/lib/agent/tools/add_reaction.js +0 -21
  54. package/lib/agent/tools/add_reaction.js.map +0 -1
  55. package/lib/agent/tools/archive_channel.js +0 -21
  56. package/lib/agent/tools/archive_channel.js.map +0 -1
  57. package/lib/agent/tools/edit_message.js +0 -22
  58. package/lib/agent/tools/edit_message.js.map +0 -1
  59. package/lib/agent/tools/invite_to_channel.js +0 -22
  60. package/lib/agent/tools/invite_to_channel.js.map +0 -1
  61. package/lib/agent/tools/pin_message.js +0 -22
  62. package/lib/agent/tools/pin_message.js.map +0 -1
  63. package/lib/agent/tools/remove_reaction.js +0 -21
  64. package/lib/agent/tools/remove_reaction.js.map +0 -1
  65. package/lib/agent/tools/set_purpose.js +0 -22
  66. package/lib/agent/tools/set_purpose.js.map +0 -1
  67. package/lib/agent/tools/set_topic.js +0 -22
  68. package/lib/agent/tools/set_topic.js.map +0 -1
  69. package/lib/agent/tools/unarchive.js +0 -21
  70. package/lib/agent/tools/unarchive.js.map +0 -1
  71. package/lib/agent/tools/unpin_message.js +0 -22
  72. package/lib/agent/tools/unpin_message.js.map +0 -1
  73. package/lib/agent/tools/user_info.js +0 -28
  74. package/lib/agent/tools/user_info.js.map +0 -1
  75. package/lib/src/adapter.js +0 -62
  76. package/lib/src/adapter.js.map +0 -1
  77. package/lib/src/endpoint.js +0 -312
  78. package/lib/src/endpoint.js.map +0 -1
  79. package/lib/src/event-dispatcher.js +0 -233
  80. package/lib/src/event-dispatcher.js.map +0 -1
  81. package/lib/src/index.js +0 -51
  82. package/lib/src/index.js.map +0 -1
  83. package/lib/src/markdown-to-mrkdwn.js +0 -61
  84. package/lib/src/markdown-to-mrkdwn.js.map +0 -1
  85. package/lib/src/mrkdwn-to-markdown.js.map +0 -1
  86. package/lib/src/platform-permit.js.map +0 -1
  87. package/lib/src/segment-mapper.js +0 -2
  88. package/lib/src/segment-mapper.js.map +0 -1
  89. package/lib/src/signing.js +0 -21
  90. package/lib/src/signing.js.map +0 -1
  91. package/lib/src/slack-agent-deps.js +0 -10
  92. package/lib/src/slack-agent-deps.js.map +0 -1
  93. package/lib/src/slack-inbound-filter.js.map +0 -1
  94. package/lib/src/slack-inbound.js +0 -127
  95. package/lib/src/slack-inbound.js.map +0 -1
  96. package/lib/src/slack-message-ref.js.map +0 -1
  97. package/lib/src/slack-outbound.js +0 -223
  98. package/lib/src/slack-outbound.js.map +0 -1
  99. package/lib/src/slack-reaction.js.map +0 -1
  100. package/lib/src/slack-response-url.js.map +0 -1
  101. package/lib/src/slack-side-events.js +0 -64
  102. package/lib/src/slack-side-events.js.map +0 -1
  103. package/lib/src/transport-http.js +0 -83
  104. package/lib/src/transport-http.js.map +0 -1
  105. package/lib/src/transport-socket.js +0 -68
  106. package/lib/src/transport-socket.js.map +0 -1
  107. package/lib/src/types.js +0 -5
  108. package/lib/src/types.js.map +0 -1
  109. package/plugin.yml +0 -3
  110. package/src/adapter.ts +0 -75
  111. package/src/event-dispatcher.ts +0 -255
  112. package/src/segment-mapper.ts +0 -1
  113. package/src/signing.ts +0 -28
  114. package/src/slack-inbound.ts +0 -140
  115. package/src/slack-side-events.ts +0 -74
  116. package/src/transport-http.ts +0 -95
  117. package/src/transport-socket.ts +0 -77
  118. package/src/types.ts +0 -113
@@ -1,64 +0,0 @@
1
- /**
2
- * Slack 事件 → zhin Notice 映射
3
- */
4
- import { mapNoticeParts, buildNotice, senderFromId } from 'zhin.js';
5
- export function isSlackNoticeEvent(eventType) {
6
- return SLACK_NOTICE_EVENT_TYPES.has(eventType);
7
- }
8
- const SLACK_NOTICE_EVENT_TYPES = new Set([
9
- 'member_joined_channel',
10
- 'member_left_channel',
11
- 'reaction_added',
12
- 'reaction_removed',
13
- 'message_deleted',
14
- 'channel_archive',
15
- 'channel_unarchive',
16
- 'channel_rename',
17
- 'channel_created',
18
- 'channel_deleted',
19
- 'pin_added',
20
- 'pin_removed',
21
- 'app_home_opened',
22
- 'team_join',
23
- ]);
24
- export function formatSlackNotice(event, endpointName) {
25
- const eventType = event.type;
26
- const { scene_type, sub_type } = mapNoticeParts('slack', eventType);
27
- const channelId = resolveNoticeChannelId(event);
28
- const ts = event.event_ts
29
- ? parseFloat(event.event_ts) * 1000
30
- : Date.now();
31
- const actorId = event.user ?? event.inviter;
32
- const targetId = event.item_user ??
33
- event.user ??
34
- undefined;
35
- return buildNotice(event, {
36
- $id: resolveSlackSideEventDedupeKey(event),
37
- $adapter: 'slack',
38
- $endpoint: endpointName,
39
- $type: 'notice',
40
- $scene_id: channelId,
41
- $scene_type: scene_type,
42
- $sub_type: sub_type,
43
- $actor: senderFromId(actorId),
44
- $target: actorId !== targetId ? senderFromId(targetId) : undefined,
45
- $timestamp: ts,
46
- });
47
- }
48
- export function resolveSlackSideEventDedupeKey(event) {
49
- const ts = event.event_ts ?? event.ts ?? '';
50
- const type = event.type;
51
- const channel = event.channel ?? '';
52
- const user = event.user ?? '';
53
- return `slack:${ts}_${type}_${channel}_${user}`;
54
- }
55
- function resolveNoticeChannelId(event) {
56
- if (event.channel)
57
- return String(event.channel);
58
- const item = event.item;
59
- if (item?.channel)
60
- return String(item.channel);
61
- const channel = event.channel_id ?? event.channel?.id;
62
- return channel ? String(channel) : '';
63
- }
64
- //# sourceMappingURL=slack-side-events.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"slack-side-events.js","sourceRoot":"","sources":["../../src/slack-side-events.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGpE,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,OAAO,wBAAwB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAC;IACvC,uBAAuB;IACvB,qBAAqB;IACrB,gBAAgB;IAChB,kBAAkB;IAClB,iBAAiB;IACjB,iBAAiB;IACjB,mBAAmB;IACnB,gBAAgB;IAChB,iBAAiB;IACjB,iBAAiB;IACjB,WAAW;IACX,aAAa;IACb,iBAAiB;IACjB,WAAW;CACZ,CAAC,CAAC;AAEH,MAAM,UAAU,iBAAiB,CAC/B,KAAiB,EACjB,YAAoB;IAEpB,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC;IAC7B,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAEpE,MAAM,SAAS,GAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,EAAE,GAAG,KAAK,CAAC,QAAQ;QACvB,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,IAAI;QACnC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IAEf,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,IAAK,KAAa,CAAC,OAAO,CAAC;IACrD,MAAM,QAAQ,GACX,KAAa,CAAC,SAAS;QACvB,KAAa,CAAC,IAAI;QACnB,SAAS,CAAC;IAEZ,OAAO,WAAW,CAAC,KAAK,EAAE;QACxB,GAAG,EAAE,8BAA8B,CAAC,KAAK,CAAC;QAC1C,QAAQ,EAAE,OAAO;QACjB,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,QAAQ;QACf,SAAS,EAAE,SAAS;QACpB,WAAW,EAAE,UAAU;QACvB,SAAS,EAAE,QAAQ;QACnB,MAAM,EAAE,YAAY,CAAC,OAAO,CAAC;QAC7B,OAAO,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;QAClE,UAAU,EAAE,EAAE;KACf,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,KAAiB;IAC9D,MAAM,EAAE,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;IAC5C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;IAC9B,OAAO,SAAS,EAAE,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI,EAAE,CAAC;AAClD,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAiB;IAC/C,IAAI,KAAK,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChD,MAAM,IAAI,GAAI,KAAa,CAAC,IAAI,CAAC;IACjC,IAAI,IAAI,EAAE,OAAO;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAI,KAAa,CAAC,UAAU,IAAK,KAAa,CAAC,OAAO,EAAE,EAAE,CAAC;IACxE,OAAO,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACxC,CAAC"}
@@ -1,83 +0,0 @@
1
- /**
2
- * HTTP 传输 — 在 zhin Router 上注册 /slack/events webhook
3
- */
4
- import { verifySlackSignature } from './signing.js';
5
- export class SlackHttpTransport {
6
- config;
7
- dispatcher;
8
- logger;
9
- constructor(config, dispatcher, logger) {
10
- this.config = config;
11
- this.dispatcher = dispatcher;
12
- this.logger = logger;
13
- }
14
- registerRoutes(router) {
15
- const signingSecret = this.config.signingSecret;
16
- router.post('/slack/events', async (ctx) => {
17
- const rawBody = await this.getRawBody(ctx);
18
- const timestamp = ctx.get?.('X-Slack-Request-Timestamp') ?? ctx.headers?.['x-slack-request-timestamp'] ?? '';
19
- const signature = ctx.get?.('X-Slack-Signature') ?? ctx.headers?.['x-slack-signature'] ?? '';
20
- if (!verifySlackSignature(signingSecret, rawBody, timestamp, signature)) {
21
- ctx.status = 401;
22
- ctx.body = 'Invalid signature';
23
- return;
24
- }
25
- const contentType = ctx.get?.('content-type') ?? ctx.headers?.['content-type'] ?? '';
26
- let body;
27
- if (contentType.includes('application/x-www-form-urlencoded')) {
28
- const params = new URLSearchParams(rawBody);
29
- const payloadStr = params.get('payload');
30
- if (payloadStr) {
31
- body = JSON.parse(payloadStr);
32
- ctx.status = 200;
33
- ctx.body = '';
34
- this.handleInteraction(body);
35
- return;
36
- }
37
- body = Object.fromEntries(params);
38
- if (body.command) {
39
- ctx.status = 200;
40
- ctx.body = '';
41
- this.dispatcher.routeSlashCommand(body);
42
- return;
43
- }
44
- }
45
- else {
46
- body = JSON.parse(rawBody);
47
- }
48
- const envelope = body;
49
- if (envelope.type === 'url_verification') {
50
- ctx.status = 200;
51
- ctx.body = { challenge: envelope.challenge };
52
- return;
53
- }
54
- if (envelope.type === 'event_callback' && envelope.event) {
55
- ctx.status = 200;
56
- ctx.body = '';
57
- this.dispatcher.routeEvent(envelope.event);
58
- return;
59
- }
60
- ctx.status = 200;
61
- ctx.body = '';
62
- });
63
- }
64
- async getRawBody(ctx) {
65
- if (ctx.request?.rawBody)
66
- return ctx.request.rawBody;
67
- if (typeof ctx.request?.body === 'string')
68
- return ctx.request.body;
69
- if (ctx.req) {
70
- return new Promise((resolve, reject) => {
71
- let data = '';
72
- ctx.req.on('data', (chunk) => { data += chunk.toString(); });
73
- ctx.req.on('end', () => resolve(data));
74
- ctx.req.on('error', reject);
75
- });
76
- }
77
- return JSON.stringify(ctx.request?.body ?? {});
78
- }
79
- handleInteraction(payload) {
80
- this.dispatcher.routeInteraction(payload);
81
- }
82
- }
83
- //# sourceMappingURL=transport-http.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"transport-http.js","sourceRoot":"","sources":["../../src/transport-http.ts"],"names":[],"mappings":"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"}
@@ -1,68 +0,0 @@
1
- /**
2
- * Socket Mode 传输 — 包装 @slack/socket-mode SocketModeClient
3
- */
4
- import { SocketModeClient } from '@slack/socket-mode';
5
- export class SlackSocketTransport {
6
- config;
7
- dispatcher;
8
- logger;
9
- socketClient;
10
- connected = false;
11
- constructor(config, dispatcher, logger) {
12
- this.config = config;
13
- this.dispatcher = dispatcher;
14
- this.logger = logger;
15
- if (!config.appToken) {
16
- throw new Error('Socket Mode requires appToken (xapp-...)');
17
- }
18
- this.socketClient = new SocketModeClient({
19
- appToken: config.appToken,
20
- clientPingTimeout: config.clientPingTimeout ?? 15_000,
21
- });
22
- }
23
- async connect() {
24
- this.socketClient.on('slack_event', async ({ ack, body }) => {
25
- await ack();
26
- this.handleEnvelope(body);
27
- });
28
- this.socketClient.on('interactive', async ({ ack, body }) => {
29
- await ack();
30
- this.handleInteraction(body);
31
- });
32
- this.socketClient.on('slash_commands', async ({ ack, body }) => {
33
- await ack();
34
- this.handleSlashCommand(body);
35
- });
36
- await this.socketClient.start();
37
- this.connected = true;
38
- }
39
- async disconnect() {
40
- if (!this.connected)
41
- return;
42
- try {
43
- await this.socketClient.disconnect();
44
- }
45
- catch { /* ignore */ }
46
- this.connected = false;
47
- }
48
- get isConnected() { return this.connected; }
49
- handleEnvelope(body) {
50
- const envelope = body;
51
- if (envelope?.type === 'event_callback' && envelope.event) {
52
- this.dispatcher.routeEvent(envelope.event);
53
- }
54
- }
55
- handleInteraction(body) {
56
- const payload = body;
57
- if (payload?.type) {
58
- this.dispatcher.routeInteraction(payload);
59
- }
60
- }
61
- handleSlashCommand(body) {
62
- const cmd = body;
63
- if (cmd?.command) {
64
- this.dispatcher.routeSlashCommand(cmd);
65
- }
66
- }
67
- }
68
- //# sourceMappingURL=transport-socket.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"transport-socket.js","sourceRoot":"","sources":["../../src/transport-socket.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAKtD,MAAM,OAAO,oBAAoB;IAKrB;IACA;IACA;IANF,YAAY,CAAmB;IAC/B,SAAS,GAAG,KAAK,CAAC;IAE1B,YACU,MAA2B,EAC3B,UAAgC,EAChC,MAAc;QAFd,WAAM,GAAN,MAAM,CAAqB;QAC3B,eAAU,GAAV,UAAU,CAAsB;QAChC,WAAM,GAAN,MAAM,CAAQ;QAEtB,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;QACD,IAAI,CAAC,YAAY,GAAG,IAAI,gBAAgB,CAAC;YACvC,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,iBAAiB,EAAE,MAAM,CAAC,iBAAiB,IAAI,MAAM;SACtD,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,aAAa,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE;YAC1D,MAAM,GAAG,EAAE,CAAC;YACZ,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,aAAa,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE;YAC1D,MAAM,GAAG,EAAE,CAAC;YACZ,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC,gBAAgB,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE;YAC7D,MAAM,GAAG,EAAE,CAAC;YACZ,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QAEH,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,CAAC;IAED,KAAK,CAAC,UAAU;QACd,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO;QAC5B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QACxB,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;IACzB,CAAC;IAED,IAAI,WAAW,KAAc,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAE7C,cAAc,CAAC,IAAa;QAClC,MAAM,QAAQ,GAAG,IAA0B,CAAC;QAC5C,IAAI,QAAQ,EAAE,IAAI,KAAK,gBAAgB,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;YAC1D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAEO,iBAAiB,CAAC,IAAa;QACrC,MAAM,OAAO,GAAG,IAA+B,CAAC;QAChD,IAAI,OAAO,EAAE,IAAI,EAAE,CAAC;YAClB,IAAI,CAAC,UAAU,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAEO,kBAAkB,CAAC,IAAa;QACtC,MAAM,GAAG,GAAG,IAAyB,CAAC;QACtC,IAAI,GAAG,EAAE,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;CACF"}
package/lib/src/types.js DELETED
@@ -1,5 +0,0 @@
1
- /**
2
- * Slack 适配器类型定义
3
- */
4
- export {};
5
- //# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
package/plugin.yml DELETED
@@ -1,3 +0,0 @@
1
- name: adapter-slack
2
- description: Slack 适配器:工作区管理、频道话题/用途、归档、置顶、表情反应
3
- version: 1.0.44
package/src/adapter.ts DELETED
@@ -1,75 +0,0 @@
1
- /**
2
- * Slack 适配器
3
- */
4
- import { Adapter, Plugin, OUTBOUND_RICH_SEGMENT_POLICY_IM_FULL, type EditMessageOptions } from 'zhin.js';
5
- import { SlackEndpoint } from './endpoint.js';
6
- import type { SlackEndpointConfig } from './types.js';
7
-
8
- export class SlackAdapter extends Adapter<SlackEndpoint> {
9
- static override readonly capabilities = ['inbound', 'outbound'] as const;
10
- static override outboundRichSegmentPolicy = OUTBOUND_RICH_SEGMENT_POLICY_IM_FULL;
11
- static override interactivePolicy = 'native' as const;
12
-
13
- constructor(plugin: Plugin) {
14
- super(plugin, 'slack', []);
15
- }
16
-
17
- createEndpoint(config: SlackEndpointConfig): SlackEndpoint {
18
- return new SlackEndpoint(this, config);
19
- }
20
-
21
- async editMessage(options: EditMessageOptions): Promise<string>;
22
- async editMessage(endpointId: string, channel: string, messageId: string, content: EditMessageOptions['content']): Promise<void>;
23
- async editMessage(
24
- arg1: EditMessageOptions | string,
25
- channel?: string,
26
- messageId?: string,
27
- content?: EditMessageOptions['content'],
28
- ): Promise<string | void> {
29
- if (typeof arg1 === 'object') {
30
- return super.editMessage(arg1);
31
- }
32
- const endpoint = this.endpoints.get(arg1);
33
- if (!endpoint) throw new Error(`Endpoint ${arg1} 不存在`);
34
- await endpoint.$editMessage({
35
- messageId: messageId!,
36
- context: 'slack',
37
- endpoint: arg1,
38
- id: channel!,
39
- type: 'group',
40
- content: content!,
41
- });
42
- }
43
-
44
- // ── ISceneManagement 标准群管方法 ──────────────────────────────────
45
-
46
- async removeMember(endpointId: string, sceneId: string, userId: string) {
47
- const endpoint = this.endpoints.get(endpointId);
48
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
49
- return endpoint.kickFromChannel(sceneId, userId);
50
- }
51
-
52
- async renameScene(endpointId: string, sceneId: string, name: string) {
53
- const endpoint = this.endpoints.get(endpointId);
54
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
55
- return endpoint.renameChannel(sceneId, name);
56
- }
57
-
58
- async listMembers(endpointId: string, sceneId: string) {
59
- const endpoint = this.endpoints.get(endpointId);
60
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
61
- return endpoint.getChannelMembers(sceneId);
62
- }
63
-
64
- async getSceneInfo(endpointId: string, sceneId: string) {
65
- const endpoint = this.endpoints.get(endpointId);
66
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
67
- return endpoint.getChannelInfo(sceneId);
68
- }
69
-
70
- // ── 生命周期 ───────────────────────────────────────────────────────
71
-
72
- async start(): Promise<void> {
73
- await super.start();
74
- }
75
- }
@@ -1,255 +0,0 @@
1
- /**
2
- * Slack 统一事件分发 — 两种传输模式共用
3
- */
4
- import { Message, segment, formatCompact, expandInteractiveSegmentsInContent } from 'zhin.js';
5
- import { toCanonicalSegments } from './segment-mapper.js';
6
- import { parseSlackMessageToSegments, resolveSlackChannelType } from './slack-inbound.js';
7
- import { isSlackNoticeEvent, formatSlackNotice } from './slack-side-events.js';
8
- import { normalizeSlackSenderForPermit } from './platform-permit.js';
9
- import type { SlackEvent, SlackMessageEvent, SlackInteractionPayload, SlackSlashCommand } from './types.js';
10
- import type { SlackEndpoint } from './endpoint.js';
11
- import { formatSlackMessageRef } from './slack-message-ref.js';
12
- import { createSlackInboundFilterState, shouldDropSlackInboundMessage } from './slack-inbound-filter.js';
13
- import { postSlackEphemeral } from './slack-response-url.js';
14
-
15
- export class SlackEventDispatcher {
16
- private readonly inboundFilter = createSlackInboundFilterState();
17
-
18
- constructor(private endpoint: SlackEndpoint) {}
19
-
20
- private get adapter() { return this.endpoint.adapter; }
21
- private get logger() { return this.endpoint.logger; }
22
-
23
- async routeEvent(event: SlackEvent): Promise<void> {
24
- const type = event.type;
25
-
26
- if (type === 'message' || type === 'app_mention') {
27
- await this.handleMessage(event as SlackMessageEvent);
28
- return;
29
- }
30
-
31
- if (type === 'assistant_thread_started') {
32
- this.handleAssistantThreadStarted(event);
33
- return;
34
- }
35
-
36
- if (type === 'assistant_thread_context_changed') {
37
- return;
38
- }
39
-
40
- if (isSlackNoticeEvent(type)) {
41
- this.handleNotice(event);
42
- return;
43
- }
44
- }
45
-
46
- routeInteraction(payload: SlackInteractionPayload): void {
47
- if (payload.type === 'block_actions' && payload.actions?.length) {
48
- this.handleBlockAction(payload);
49
- }
50
- }
51
-
52
- routeSlashCommand(cmd: SlackSlashCommand): void {
53
- this.handleSlashCommand(cmd);
54
- }
55
-
56
- private async handleMessage(event: SlackMessageEvent): Promise<void> {
57
- if (shouldDropSlackInboundMessage(event, this.inboundFilter, this.endpoint.$platformUserId)) {
58
- return;
59
- }
60
-
61
- const messageRef = formatSlackMessageRef(event.channel, event.ts);
62
- this.endpoint.trackMessageChannel(event.ts, event.channel);
63
-
64
- const wire = parseSlackMessageToSegments(event);
65
- const content = toCanonicalSegments(wire);
66
- const channelType = resolveSlackChannelType(event);
67
-
68
- const userId = event.user ?? '';
69
- const userName = (event as any).username ?? (userId || 'Unknown');
70
-
71
- const message = Message.from(event, {
72
- $id: messageRef,
73
- $adapter: 'slack',
74
- $endpoint: this.endpoint.$config.name,
75
- $sender: { id: userId, name: userName },
76
- $channel: { id: event.channel, type: channelType },
77
- $content: content,
78
- $raw: event.text ?? '',
79
- $timestamp: parseFloat(event.ts) * 1000,
80
- $quote_id: event.thread_ts && event.thread_ts !== event.ts ? event.thread_ts : undefined,
81
- $recall: async () => {
82
- await this.endpoint.client!.chat.delete({ channel: event.channel, ts: event.ts });
83
- },
84
- $reply: async (replyContent, quote?) => {
85
- if (!Array.isArray(replyContent)) replyContent = [replyContent];
86
- const threadTs = quote
87
- ? (typeof quote === 'boolean' ? event.ts : quote)
88
- : (event.thread_ts ?? undefined);
89
- return await this.adapter.sendMessage({
90
- context: 'slack',
91
- endpoint: this.endpoint.$config.name,
92
- id: event.channel,
93
- type: channelType,
94
- content: replyContent,
95
- ...(threadTs ? { threadId: threadTs } : {}),
96
- });
97
- },
98
- });
99
-
100
- await this.enrichSender(message, event);
101
- this.adapter.emit('message.receive', message);
102
- this.logger.debug(
103
- `${this.endpoint.$config.name} recv ${message.$channel.type}(${message.$channel.id}): ${segment.raw(message.$content)}`,
104
- );
105
- }
106
-
107
- private handleNotice(event: SlackEvent): void {
108
- const notice = formatSlackNotice(event, this.endpoint.$config.name);
109
- this.adapter.emit('notice.receive' as any, notice);
110
- this.logger.debug(formatCompact({
111
- notice: `${notice.$scene_type}.${notice.$sub_type}`,
112
- endpoint: this.endpoint.$id,
113
- scene: notice.$scene_id,
114
- }));
115
- }
116
-
117
- private handleBlockAction(payload: SlackInteractionPayload): void {
118
- const action = payload.actions![0];
119
- const channelId = payload.channel?.id ?? '';
120
- const userId = payload.user.id;
121
- const userName = payload.user.username ?? payload.user.name ?? userId;
122
- const messageTs = payload.message?.ts ?? '';
123
-
124
- if (payload.response_url) {
125
- postSlackEphemeral(payload.response_url, '已收到', this.logger);
126
- }
127
-
128
- const actionSegment = {
129
- type: 'action',
130
- data: {
131
- id: action.action_id,
132
- value: action.value ?? '',
133
- payload: JSON.stringify(action),
134
- text: action.text?.text ?? '',
135
- },
136
- };
137
-
138
- const message = Message.from(payload as any, {
139
- $id: action.action_ts ?? messageTs,
140
- $adapter: 'slack',
141
- $endpoint: this.endpoint.$config.name,
142
- $sender: { id: userId, name: userName },
143
- $channel: { id: channelId, type: 'group' },
144
- $content: toCanonicalSegments([actionSegment]),
145
- $raw: action.text?.text ?? '',
146
- $timestamp: Date.now(),
147
- $reply: async (content) => {
148
- if (!Array.isArray(content)) content = [content];
149
- return await this.adapter.sendMessage({
150
- context: 'slack',
151
- endpoint: this.endpoint.$config.name,
152
- id: channelId,
153
- type: 'group',
154
- content,
155
- ...(messageTs ? { threadId: messageTs } : {}),
156
- });
157
- },
158
- });
159
-
160
- this.adapter.emit('message.receive', message);
161
- }
162
-
163
- private handleSlashCommand(cmd: SlackSlashCommand): void {
164
- const text = `${cmd.command} ${cmd.text}`.trim();
165
-
166
- postSlackEphemeral(cmd.response_url, '处理中…', this.logger);
167
-
168
- const message = Message.from(cmd as any, {
169
- $id: cmd.trigger_id,
170
- $adapter: 'slack',
171
- $endpoint: this.endpoint.$config.name,
172
- $sender: { id: cmd.user_id, name: cmd.user_name },
173
- $channel: { id: cmd.channel_id, type: 'group' },
174
- $content: [{ type: 'text', data: { text } }],
175
- $raw: text,
176
- $timestamp: Date.now(),
177
- $reply: async (content) => {
178
- if (!Array.isArray(content)) content = [content];
179
- return await this.adapter.sendMessage({
180
- context: 'slack',
181
- endpoint: this.endpoint.$config.name,
182
- id: cmd.channel_id,
183
- type: 'group',
184
- content,
185
- });
186
- },
187
- });
188
-
189
- this.adapter.emit('message.receive', message);
190
- }
191
-
192
- private handleAssistantThreadStarted(event: SlackEvent): void {
193
- const thread = (event as any).assistant_thread;
194
- if (!thread) return;
195
-
196
- const userId = thread.user_id ?? '';
197
- const channelId = thread.channel_id ?? '';
198
- const threadTs = thread.thread_ts ?? '';
199
-
200
- const message = Message.from(event, {
201
- $id: threadTs,
202
- $adapter: 'slack',
203
- $endpoint: this.endpoint.$config.name,
204
- $sender: { id: userId, name: userId },
205
- $channel: { id: channelId, type: 'private' },
206
- $content: [{ type: 'text', data: { text: '' } }],
207
- $raw: '',
208
- $timestamp: Date.now(),
209
- $reply: async (content) => {
210
- if (!Array.isArray(content)) content = [content];
211
- return await this.adapter.sendMessage({
212
- context: 'slack',
213
- endpoint: this.endpoint.$config.name,
214
- id: channelId,
215
- type: 'private',
216
- content,
217
- threadId: threadTs,
218
- });
219
- },
220
- });
221
-
222
- this.adapter.emit('message.receive', message);
223
- }
224
-
225
- private async enrichSender(message: Message<any>, event: SlackEvent): Promise<void> {
226
- if (message.$channel.type !== 'group' || !event.user) return;
227
- const channelId = event.channel ?? '';
228
- const userId = event.user;
229
- const key = `${channelId}:${userId}`;
230
- const now = Date.now();
231
- const cached = this.endpoint.senderPermitCache.get(key);
232
- if (cached && now - cached.at < 60_000) {
233
- message.$sender.role = cached.role;
234
- message.$sender.permissions = cached.permissions;
235
- return;
236
- }
237
- try {
238
- const user = await this.endpoint.getUserInfo(userId);
239
- let isChannelManager = false;
240
- try {
241
- const channel = await this.endpoint.getChannelInfo(channelId);
242
- if (channel?.creator === userId) isChannelManager = true;
243
- } catch { /* ignore */ }
244
- const normalized = normalizeSlackSenderForPermit({
245
- isWorkspaceOwner: user.is_owner === true,
246
- isWorkspaceAdmin: user.is_admin === true,
247
- isChannelManager,
248
- });
249
- const entry = { at: now, role: normalized.role, permissions: normalized.permissions ?? [] };
250
- this.endpoint.senderPermitCache.set(key, entry);
251
- message.$sender.role = entry.role;
252
- message.$sender.permissions = entry.permissions;
253
- } catch { /* conservative deny */ }
254
- }
255
- }
@@ -1 +0,0 @@
1
- export { toCanonicalSegments, fromCanonicalSegments } from 'zhin.js';
package/src/signing.ts DELETED
@@ -1,28 +0,0 @@
1
- /**
2
- * Slack 请求签名验证 — HMAC-SHA256
3
- * https://api.slack.com/authentication/verifying-requests-from-slack
4
- */
5
- import { createHmac, timingSafeEqual } from 'node:crypto';
6
-
7
- const SLACK_VERSION = 'v0';
8
- const MAX_TIMESTAMP_DRIFT_SECONDS = 300;
9
-
10
- export function verifySlackSignature(
11
- signingSecret: string,
12
- rawBody: string,
13
- timestamp: string,
14
- signature: string,
15
- ): boolean {
16
- const now = Math.floor(Date.now() / 1000);
17
- const ts = parseInt(timestamp, 10);
18
- if (Number.isNaN(ts) || Math.abs(now - ts) > MAX_TIMESTAMP_DRIFT_SECONDS) {
19
- return false;
20
- }
21
-
22
- const baseString = `${SLACK_VERSION}:${timestamp}:${rawBody}`;
23
- const hmac = createHmac('sha256', signingSecret).update(baseString).digest('hex');
24
- const expected = `${SLACK_VERSION}=${hmac}`;
25
-
26
- if (expected.length !== signature.length) return false;
27
- return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
28
- }