@zhin.js/adapter-lark 6.0.2 → 6.0.5
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 +95 -0
- package/README.md +2 -2
- package/adapters/lark.js +2 -2
- package/adapters/lark.ts +2 -2
- package/lib/endpoint.d.ts +2 -5
- package/lib/endpoint.js +34 -37
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- package/lib/lark-agent-deps.d.ts +2 -2
- package/lib/lark-agent-deps.js +7 -7
- package/lib/platform-permit.d.ts +2 -3
- package/lib/platform-permit.js +3 -11
- package/lib/protocol.d.ts +9 -3
- package/lib/protocol.js +14 -5
- package/package.json +17 -15
- package/plugin.js +6 -3
- package/schema.json +41 -5
- package/src/endpoint.ts +36 -39
- package/src/index.ts +1 -2
- package/src/lark-agent-deps.ts +8 -8
- package/src/platform-permit.ts +4 -8
- package/src/protocol.ts +17 -7
- /package/commands/endpoint/add/{[name:string].js → [id].js} +0 -0
- /package/commands/endpoint/add/{[name:string].ts → [id].ts} +0 -0
- /package/commands/endpoint/remove/{[name:string].js → [id].js} +0 -0
- /package/commands/endpoint/remove/{[name:string].ts → [id].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,100 @@
|
|
|
1
1
|
# @zhin.js/adapter-lark
|
|
2
2
|
|
|
3
|
+
## 6.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 51015d6: refactor(adapters): migrate platform-permit to PermissionSubject
|
|
8
|
+
|
|
9
|
+
All adapter platform-permit checkers now accept PermissionSubject
|
|
10
|
+
(duck-typed from Message/CommandSession) instead of Message directly.
|
|
11
|
+
registerPlatformPermitChecker is replaced by host.registerPlatform in
|
|
12
|
+
the adapter plugin setup. activity-feedback service updated for new
|
|
13
|
+
PermissionHost integration.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [6f9c366]
|
|
16
|
+
- Updated dependencies [373a56b]
|
|
17
|
+
- Updated dependencies [0de46a8]
|
|
18
|
+
- Updated dependencies [c106ecc]
|
|
19
|
+
- Updated dependencies [ca92e03]
|
|
20
|
+
- Updated dependencies [b0f37ae]
|
|
21
|
+
- Updated dependencies [ba08a2f]
|
|
22
|
+
- Updated dependencies [b08f7fe]
|
|
23
|
+
- Updated dependencies [daffd4c]
|
|
24
|
+
- Updated dependencies [36c7400]
|
|
25
|
+
- Updated dependencies [a9fa72e]
|
|
26
|
+
- Updated dependencies [c8de3ef]
|
|
27
|
+
- Updated dependencies [60f0fc8]
|
|
28
|
+
- Updated dependencies [574c990]
|
|
29
|
+
- Updated dependencies [d096f16]
|
|
30
|
+
- Updated dependencies [3f29623]
|
|
31
|
+
- Updated dependencies [2916852]
|
|
32
|
+
- Updated dependencies [d047869]
|
|
33
|
+
- Updated dependencies [162fa34]
|
|
34
|
+
- Updated dependencies [e62561e]
|
|
35
|
+
- Updated dependencies [3eeeb46]
|
|
36
|
+
- Updated dependencies [85d0f82]
|
|
37
|
+
- Updated dependencies [61bfc1c]
|
|
38
|
+
- Updated dependencies [04bad47]
|
|
39
|
+
- Updated dependencies [e40b048]
|
|
40
|
+
- Updated dependencies [5a5b1bb]
|
|
41
|
+
- Updated dependencies [f1708c3]
|
|
42
|
+
- Updated dependencies [d254a81]
|
|
43
|
+
- Updated dependencies [7123c47]
|
|
44
|
+
- Updated dependencies [05befc1]
|
|
45
|
+
- Updated dependencies [0663b6a]
|
|
46
|
+
- Updated dependencies [f28f9b3]
|
|
47
|
+
- Updated dependencies [9f340f7]
|
|
48
|
+
- Updated dependencies [e53444f]
|
|
49
|
+
- Updated dependencies [5eedd26]
|
|
50
|
+
- Updated dependencies [92b0dd7]
|
|
51
|
+
- Updated dependencies [f919b6f]
|
|
52
|
+
- Updated dependencies [098e411]
|
|
53
|
+
- Updated dependencies [e1b7c01]
|
|
54
|
+
- Updated dependencies [9b94f87]
|
|
55
|
+
- Updated dependencies [a7df753]
|
|
56
|
+
- @zhin.js/agent@1.1.5
|
|
57
|
+
- @zhin.js/host-http@1.0.8
|
|
58
|
+
- @zhin.js/permission@1.0.1
|
|
59
|
+
- @zhin.js/im-contract@1.0.3
|
|
60
|
+
- @zhin.js/adapter@1.1.7
|
|
61
|
+
- @zhin.js/plugin-runtime@1.1.5
|
|
62
|
+
- @zhin.js/command@1.0.9
|
|
63
|
+
- @zhin.js/core@1.5.4
|
|
64
|
+
- zhin.js@6.0.4
|
|
65
|
+
|
|
66
|
+
## 6.0.4
|
|
67
|
+
|
|
68
|
+
### Patch Changes
|
|
69
|
+
|
|
70
|
+
- f8c7a54: fix: im
|
|
71
|
+
- Updated dependencies [f8c7a54]
|
|
72
|
+
- @zhin.js/logger@1.0.76
|
|
73
|
+
- @zhin.js/host-http@1.0.7
|
|
74
|
+
- @zhin.js/adapter@1.1.6
|
|
75
|
+
- @zhin.js/agent@1.1.4
|
|
76
|
+
- @zhin.js/command@1.0.8
|
|
77
|
+
- @zhin.js/core@1.5.3
|
|
78
|
+
- @zhin.js/im-contract@1.0.2
|
|
79
|
+
- @zhin.js/plugin-runtime@1.1.4
|
|
80
|
+
- zhin.js@6.0.3
|
|
81
|
+
|
|
82
|
+
## 6.0.3
|
|
83
|
+
|
|
84
|
+
### Patch Changes
|
|
85
|
+
|
|
86
|
+
- Updated dependencies [afc0e66]
|
|
87
|
+
- Updated dependencies [2e41ad5]
|
|
88
|
+
- Updated dependencies [9f57124]
|
|
89
|
+
- @zhin.js/core@1.5.2
|
|
90
|
+
- @zhin.js/adapter@1.1.5
|
|
91
|
+
- @zhin.js/im-contract@1.0.1
|
|
92
|
+
- @zhin.js/plugin-runtime@1.1.3
|
|
93
|
+
- @zhin.js/command@1.0.7
|
|
94
|
+
- @zhin.js/host-http@1.0.6
|
|
95
|
+
- @zhin.js/agent@1.1.3
|
|
96
|
+
- zhin.js@6.0.2
|
|
97
|
+
|
|
3
98
|
## 6.0.2
|
|
4
99
|
|
|
5
100
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -24,8 +24,8 @@ pnpm add @zhin.js/adapter-lark
|
|
|
24
24
|
- `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
|
|
25
25
|
- 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
|
|
26
26
|
|
|
27
|
-
入站:`gateway.receive({
|
|
28
|
-
出站:`send({
|
|
27
|
+
入站:`gateway.receive({ conversation, message: { conversation, id }, content: text, sender, metadata })`
|
|
28
|
+
出站:`send({ conversation, payload })` → `im/v1/messages`
|
|
29
29
|
|
|
30
30
|
入站 `metadata.mentioned`:**未接线**。消息事件的 `mentions[]` 元素含 `id.open_id`,但本适配器拿不到 bot 自身的 open_id——配置(`appId` / `appSecret` / `name` 等)不含 bot open_id,代码也未调用 `bot/v3/info` 获取应用信息,故无可靠判据比对 mentions。
|
|
31
31
|
|
package/adapters/lark.js
CHANGED
|
@@ -21,8 +21,8 @@ export default defineAdapter({
|
|
|
21
21
|
create(context) {
|
|
22
22
|
const config = resolveLarkConfig(context.config);
|
|
23
23
|
// 注册到插件运行时状态(lark.endpoint list 的"运行中"数据源)
|
|
24
|
-
context.use(larkRuntimeStateToken).endpoints.set(config.
|
|
25
|
-
|
|
24
|
+
context.use(larkRuntimeStateToken).endpoints.set(config.id, {
|
|
25
|
+
id: config.id,
|
|
26
26
|
mode: 'webhook',
|
|
27
27
|
});
|
|
28
28
|
return new LarkEndpoint({
|
package/adapters/lark.ts
CHANGED
|
@@ -26,8 +26,8 @@ export default defineAdapter<LarkAdapterConfig>({
|
|
|
26
26
|
create(context) {
|
|
27
27
|
const config = resolveLarkConfig(context.config);
|
|
28
28
|
// 注册到插件运行时状态(lark.endpoint list 的"运行中"数据源)
|
|
29
|
-
context.use(larkRuntimeStateToken).endpoints.set(config.
|
|
30
|
-
|
|
29
|
+
context.use(larkRuntimeStateToken).endpoints.set(config.id, {
|
|
30
|
+
id: config.id,
|
|
31
31
|
mode: 'webhook',
|
|
32
32
|
});
|
|
33
33
|
return new LarkEndpoint({
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
1
|
+
import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
|
|
2
2
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
4
4
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
@@ -40,10 +40,7 @@ export declare class LarkEndpoint implements EndpointInstance {
|
|
|
40
40
|
open(): void;
|
|
41
41
|
close(): void;
|
|
42
42
|
stop(): Promise<void>;
|
|
43
|
-
send({
|
|
44
|
-
readonly target: string;
|
|
45
|
-
readonly payload: unknown;
|
|
46
|
-
}): Promise<string>;
|
|
43
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
47
44
|
recallMessage(messageId: string): Promise<void>;
|
|
48
45
|
/** POST /im/v1/images(image_type=message),返回 image_key;失败返回 null。 */
|
|
49
46
|
uploadImage(media: Parameters<typeof resolveMediaBinary>[0]): Promise<string | null>;
|
package/lib/endpoint.js
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { readFile } from 'node:fs/promises';
|
|
5
5
|
import { basename } from 'node:path';
|
|
6
|
-
import { formatCompact,
|
|
6
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
7
7
|
import { registerLarkAgentEndpoint } from './lark-agent-deps.js';
|
|
8
8
|
import { buildImageUploadForm, readOutboundImageMedia, resolveMediaBinary, } from './media-upload.js';
|
|
9
|
-
import { formatInboundContent, formatOutboundBody, generateMessageId, resolveChatType, resolveSender,
|
|
9
|
+
import { formatInboundContent, formatOutboundBody, generateMessageId, larkInboundConversation, resolveChatType, resolveSender, } from './protocol.js';
|
|
10
10
|
import { registerLarkWebhookRoutes } from './webhook.js';
|
|
11
|
-
const logger = getLogger('lark');
|
|
12
11
|
/** 出站 HTTP 调用统一 30s 超时。 */
|
|
13
12
|
const OUTBOUND_TIMEOUT_MS = 30_000;
|
|
14
13
|
export class LarkEndpoint {
|
|
14
|
+
#logger;
|
|
15
15
|
#options;
|
|
16
16
|
#fetch;
|
|
17
17
|
/**
|
|
@@ -45,6 +45,7 @@ export class LarkEndpoint {
|
|
|
45
45
|
#started = false;
|
|
46
46
|
#unregisterAgent;
|
|
47
47
|
constructor(options) {
|
|
48
|
+
this.#logger = getAdapterLogger('lark', options.config.id);
|
|
48
49
|
this.#options = options;
|
|
49
50
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
50
51
|
}
|
|
@@ -62,25 +63,25 @@ export class LarkEndpoint {
|
|
|
62
63
|
try {
|
|
63
64
|
if (!this.#options.config.encryptKey && !this.#options.config.verificationToken) {
|
|
64
65
|
// encryptKey / verificationToken 都未配置时 webhook 完全无鉴权,任何人可伪造事件
|
|
65
|
-
logger.warn(formatCompact({
|
|
66
|
-
endpoint: this.#options.config.
|
|
66
|
+
this.#logger.warn(formatCompact({
|
|
67
|
+
endpoint: this.#options.config.id,
|
|
67
68
|
op: 'webhook',
|
|
68
69
|
ok: false,
|
|
69
70
|
error: 'neither encryptKey nor verificationToken configured; webhook is unauthenticated',
|
|
70
71
|
}));
|
|
71
72
|
}
|
|
72
73
|
await this.#refreshAccessToken();
|
|
73
|
-
this.#unregisterAgent = registerLarkAgentEndpoint(this.#options.config.
|
|
74
|
+
this.#unregisterAgent = registerLarkAgentEndpoint(this.#options.config.id, this);
|
|
74
75
|
this.#routeReleases.push(...registerLarkWebhookRoutes(this.#options.http, this));
|
|
75
|
-
logger.debug(formatCompact({
|
|
76
|
-
endpoint: this.#options.config.
|
|
76
|
+
this.#logger.debug(formatCompact({
|
|
77
|
+
endpoint: this.#options.config.id,
|
|
77
78
|
op: 'webhook',
|
|
78
79
|
path: this.#options.config.webhookPath,
|
|
79
80
|
}));
|
|
80
81
|
}
|
|
81
82
|
catch (error) {
|
|
82
83
|
await this.stop();
|
|
83
|
-
logger.error('Failed to connect Lark endpoint:', error);
|
|
84
|
+
this.#logger.error('Failed to connect Lark endpoint:', error);
|
|
84
85
|
throw error;
|
|
85
86
|
}
|
|
86
87
|
}
|
|
@@ -97,16 +98,16 @@ export class LarkEndpoint {
|
|
|
97
98
|
this.#unregisterAgent?.();
|
|
98
99
|
this.#unregisterAgent = undefined;
|
|
99
100
|
this.#started = false;
|
|
100
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
101
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
101
102
|
}
|
|
102
|
-
async send({
|
|
103
|
+
async send({ conversation, payload }) {
|
|
103
104
|
const materialized = await this.#materializeOutboundMedia(payload);
|
|
104
105
|
const content = formatOutboundBody(materialized);
|
|
105
106
|
const data = await this.#request('/im/v1/messages', {
|
|
106
107
|
method: 'POST',
|
|
107
108
|
params: { receive_id_type: 'chat_id' },
|
|
108
109
|
body: {
|
|
109
|
-
receive_id:
|
|
110
|
+
receive_id: conversation.id,
|
|
110
111
|
msg_type: content.msg_type,
|
|
111
112
|
content: content.content,
|
|
112
113
|
},
|
|
@@ -115,10 +116,7 @@ export class LarkEndpoint {
|
|
|
115
116
|
throw new Error(`Failed to send message: ${data.msg}`);
|
|
116
117
|
}
|
|
117
118
|
const messageId = data.data?.message_id || `${Date.now()}`;
|
|
118
|
-
logger.debug(formatCompact({
|
|
119
|
-
op: 'send',
|
|
120
|
-
endpoint: this.#options.config.name,
|
|
121
|
-
to: target,
|
|
119
|
+
this.#logger.debug(formatCompact({ op: 'send', to: conversation.id,
|
|
122
120
|
id: messageId,
|
|
123
121
|
}));
|
|
124
122
|
return messageId;
|
|
@@ -177,7 +175,7 @@ export class LarkEndpoint {
|
|
|
177
175
|
throw new Error(`Image upload failed: ${data.msg}`);
|
|
178
176
|
}
|
|
179
177
|
catch (error) {
|
|
180
|
-
logger.error('Failed to upload image:', error);
|
|
178
|
+
this.#logger.error('Failed to upload image:', error);
|
|
181
179
|
return null;
|
|
182
180
|
}
|
|
183
181
|
}
|
|
@@ -185,22 +183,21 @@ export class LarkEndpoint {
|
|
|
185
183
|
admit(msg) {
|
|
186
184
|
if (!this.#open)
|
|
187
185
|
return;
|
|
188
|
-
const
|
|
186
|
+
const conversation = larkInboundConversation(String(this.#options.id), msg);
|
|
189
187
|
void this.#options.gateway.receive({
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
conversation,
|
|
189
|
+
message: { conversation, id: generateMessageId(msg) },
|
|
192
190
|
content: formatInboundContent(msg),
|
|
193
|
-
sender: resolveSender(msg),
|
|
194
|
-
|
|
191
|
+
sender: { id: resolveSender(msg) },
|
|
192
|
+
endpointId: this.#options.config.id,
|
|
195
193
|
metadata: Object.freeze({
|
|
196
194
|
messageType: msg.message_type,
|
|
197
195
|
chatType: resolveChatType(msg.chat_id, msg.chat_type),
|
|
198
|
-
endpoint: this.#options.config.name,
|
|
199
196
|
}),
|
|
200
197
|
}).catch((err) => {
|
|
201
|
-
logger.warn(formatCompact({
|
|
198
|
+
this.#logger.warn(formatCompact({
|
|
202
199
|
op: 'lark_gateway_receive_failed',
|
|
203
|
-
|
|
200
|
+
conversation: conversation.id,
|
|
204
201
|
error: err instanceof Error ? err.message : String(err),
|
|
205
202
|
}));
|
|
206
203
|
});
|
|
@@ -214,7 +211,7 @@ export class LarkEndpoint {
|
|
|
214
211
|
return data.data?.user ?? null;
|
|
215
212
|
}
|
|
216
213
|
catch (error) {
|
|
217
|
-
logger.error('Failed to get user info:', error);
|
|
214
|
+
this.#logger.error('Failed to get user info:', error);
|
|
218
215
|
return null;
|
|
219
216
|
}
|
|
220
217
|
}
|
|
@@ -224,7 +221,7 @@ export class LarkEndpoint {
|
|
|
224
221
|
return data.data ?? null;
|
|
225
222
|
}
|
|
226
223
|
catch (error) {
|
|
227
|
-
logger.error('Failed to get chat info:', error);
|
|
224
|
+
this.#logger.error('Failed to get chat info:', error);
|
|
228
225
|
return null;
|
|
229
226
|
}
|
|
230
227
|
}
|
|
@@ -248,7 +245,7 @@ export class LarkEndpoint {
|
|
|
248
245
|
throw new Error(`Upload failed: ${data.msg}`);
|
|
249
246
|
}
|
|
250
247
|
catch (error) {
|
|
251
|
-
logger.error('Failed to upload file:', error);
|
|
248
|
+
this.#logger.error('Failed to upload file:', error);
|
|
252
249
|
return null;
|
|
253
250
|
}
|
|
254
251
|
}
|
|
@@ -267,7 +264,7 @@ export class LarkEndpoint {
|
|
|
267
264
|
throw new Error(`Failed to create chat: ${data.msg}`);
|
|
268
265
|
}
|
|
269
266
|
catch (error) {
|
|
270
|
-
logger.error('Failed to create chat:', error);
|
|
267
|
+
this.#logger.error('Failed to create chat:', error);
|
|
271
268
|
return null;
|
|
272
269
|
}
|
|
273
270
|
}
|
|
@@ -282,7 +279,7 @@ export class LarkEndpoint {
|
|
|
282
279
|
throw new Error(`Failed to update chat: ${data.msg}`);
|
|
283
280
|
}
|
|
284
281
|
catch (error) {
|
|
285
|
-
logger.error('Failed to update chat:', error);
|
|
282
|
+
this.#logger.error('Failed to update chat:', error);
|
|
286
283
|
return false;
|
|
287
284
|
}
|
|
288
285
|
}
|
|
@@ -297,7 +294,7 @@ export class LarkEndpoint {
|
|
|
297
294
|
throw new Error(`Failed to add members: ${data.msg}`);
|
|
298
295
|
}
|
|
299
296
|
catch (error) {
|
|
300
|
-
logger.error('Failed to add chat members:', error);
|
|
297
|
+
this.#logger.error('Failed to add chat members:', error);
|
|
301
298
|
return false;
|
|
302
299
|
}
|
|
303
300
|
}
|
|
@@ -312,7 +309,7 @@ export class LarkEndpoint {
|
|
|
312
309
|
throw new Error(`Failed to remove members: ${data.msg}`);
|
|
313
310
|
}
|
|
314
311
|
catch (error) {
|
|
315
|
-
logger.error('Failed to remove chat members:', error);
|
|
312
|
+
this.#logger.error('Failed to remove chat members:', error);
|
|
316
313
|
return false;
|
|
317
314
|
}
|
|
318
315
|
}
|
|
@@ -324,7 +321,7 @@ export class LarkEndpoint {
|
|
|
324
321
|
throw new Error(`Failed to get members: ${data.msg}`);
|
|
325
322
|
}
|
|
326
323
|
catch (error) {
|
|
327
|
-
logger.error('Failed to get chat members:', error);
|
|
324
|
+
this.#logger.error('Failed to get chat members:', error);
|
|
328
325
|
return [];
|
|
329
326
|
}
|
|
330
327
|
}
|
|
@@ -336,7 +333,7 @@ export class LarkEndpoint {
|
|
|
336
333
|
throw new Error(`Failed to dissolve chat: ${data.msg}`);
|
|
337
334
|
}
|
|
338
335
|
catch (error) {
|
|
339
|
-
logger.error('Failed to dissolve chat:', error);
|
|
336
|
+
this.#logger.error('Failed to dissolve chat:', error);
|
|
340
337
|
return false;
|
|
341
338
|
}
|
|
342
339
|
}
|
|
@@ -351,7 +348,7 @@ export class LarkEndpoint {
|
|
|
351
348
|
throw new Error(`Failed to set managers: ${data.msg}`);
|
|
352
349
|
}
|
|
353
350
|
catch (error) {
|
|
354
|
-
logger.error('Failed to set chat managers:', error);
|
|
351
|
+
this.#logger.error('Failed to set chat managers:', error);
|
|
355
352
|
return false;
|
|
356
353
|
}
|
|
357
354
|
}
|
|
@@ -366,7 +363,7 @@ export class LarkEndpoint {
|
|
|
366
363
|
throw new Error(`Failed to remove managers: ${data.msg}`);
|
|
367
364
|
}
|
|
368
365
|
catch (error) {
|
|
369
|
-
logger.error('Failed to remove chat managers:', error);
|
|
366
|
+
this.#logger.error('Failed to remove chat managers:', error);
|
|
370
367
|
return false;
|
|
371
368
|
}
|
|
372
369
|
}
|
|
@@ -451,7 +448,7 @@ export class LarkEndpoint {
|
|
|
451
448
|
expires_in: data.expire ?? 7200,
|
|
452
449
|
timestamp: Date.now(),
|
|
453
450
|
};
|
|
454
|
-
logger.debug('Access token refreshed successfully');
|
|
451
|
+
this.#logger.debug('Access token refreshed successfully');
|
|
455
452
|
return;
|
|
456
453
|
}
|
|
457
454
|
throw new Error(`Failed to get access token: ${data.msg} (${data.code})`);
|
package/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { formatInboundContent, formatOutboundBody, generateMessageId, headerValue, normalizeWebhookPath, readTextBody, resolveChatType, resolveLarkConfig, resolveSender,
|
|
1
|
+
export { formatInboundContent, formatOutboundBody, generateMessageId, headerValue, larkInboundConversation, normalizeWebhookPath, readTextBody, resolveChatType, resolveLarkConfig, resolveSender, verifySignature, type AccessToken, type LarkAdapterConfig, type LarkApiResponse, type LarkEventBody, type LarkMessage, type LarkSendBody, type LarkWireSegment, type ResolvedLarkConfig, } from './protocol.js';
|
|
2
2
|
export { getLarkAgentDeps, registerLarkAgentEndpoint, setLarkAgentDeps, type LarkAgentDeps, type LarkAgentEndpoint, } from './lark-agent-deps.js';
|
|
3
|
-
export { checkLarkPlatformPermit, larkGroupPermitResolver, normalizeLarkSenderForPermit, platformPermit,
|
|
3
|
+
export { checkLarkPlatformPermit, larkGroupPermitResolver, normalizeLarkSenderForPermit, platformPermit, } from './platform-permit.js';
|
|
4
4
|
export { LarkEndpoint, type LarkEndpointOptions, type LarkFetch, } from './endpoint.js';
|
|
5
5
|
export { buildImageUploadForm, readOutboundImageMedia, resolveMediaBinary, type MediaBinary, } from './media-upload.js';
|
|
6
6
|
export { registerLarkWebhookRoutes, handleLarkWebhookRequest, type LarkWebhookHandler, } from './webhook.js';
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { formatInboundContent, formatOutboundBody, generateMessageId, headerValue, normalizeWebhookPath, readTextBody, resolveChatType, resolveLarkConfig, resolveSender,
|
|
1
|
+
export { formatInboundContent, formatOutboundBody, generateMessageId, headerValue, larkInboundConversation, normalizeWebhookPath, readTextBody, resolveChatType, resolveLarkConfig, resolveSender, verifySignature, } from './protocol.js';
|
|
2
2
|
export { getLarkAgentDeps, registerLarkAgentEndpoint, setLarkAgentDeps, } from './lark-agent-deps.js';
|
|
3
|
-
export { checkLarkPlatformPermit, larkGroupPermitResolver, normalizeLarkSenderForPermit, platformPermit,
|
|
3
|
+
export { checkLarkPlatformPermit, larkGroupPermitResolver, normalizeLarkSenderForPermit, platformPermit, } from './platform-permit.js';
|
|
4
4
|
export { LarkEndpoint, } from './endpoint.js';
|
|
5
5
|
export { buildImageUploadForm, readOutboundImageMedia, resolveMediaBinary, } from './media-upload.js';
|
|
6
6
|
export { registerLarkWebhookRoutes, handleLarkWebhookRequest, } from './webhook.js';
|
package/lib/lark-agent-deps.d.ts
CHANGED
|
@@ -19,9 +19,9 @@ export interface LarkAgentEndpoint {
|
|
|
19
19
|
removeChatManagers(chatId: string, userIds: string[]): Promise<boolean>;
|
|
20
20
|
}
|
|
21
21
|
export interface LarkAgentDeps {
|
|
22
|
-
getEndpoint: (
|
|
22
|
+
getEndpoint: (endpointKey: string) => LarkAgentEndpoint;
|
|
23
23
|
}
|
|
24
|
-
export declare function registerLarkAgentEndpoint(
|
|
24
|
+
export declare function registerLarkAgentEndpoint(endpointKey: string, endpoint: LarkAgentEndpoint): () => void;
|
|
25
25
|
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
26
26
|
export declare function setLarkAgentDeps(deps: LarkAgentDeps | null): void;
|
|
27
27
|
export declare function getLarkAgentDeps(): LarkAgentDeps;
|
package/lib/lark-agent-deps.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const endpoints = new Map();
|
|
6
6
|
let override = null;
|
|
7
|
-
export function registerLarkAgentEndpoint(
|
|
8
|
-
endpoints.set(
|
|
7
|
+
export function registerLarkAgentEndpoint(endpointKey, endpoint) {
|
|
8
|
+
endpoints.set(endpointKey, endpoint);
|
|
9
9
|
return () => {
|
|
10
|
-
if (endpoints.get(
|
|
11
|
-
endpoints.delete(
|
|
10
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
11
|
+
endpoints.delete(endpointKey);
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
}
|
|
@@ -20,10 +20,10 @@ export function getLarkAgentDeps() {
|
|
|
20
20
|
if (override)
|
|
21
21
|
return override;
|
|
22
22
|
return {
|
|
23
|
-
getEndpoint(
|
|
24
|
-
const endpoint = endpoints.get(
|
|
23
|
+
getEndpoint(endpointKey) {
|
|
24
|
+
const endpoint = endpoints.get(endpointKey);
|
|
25
25
|
if (!endpoint)
|
|
26
|
-
throw new Error(`Endpoint ${
|
|
26
|
+
throw new Error(`Endpoint ${endpointKey} 不存在`);
|
|
27
27
|
return endpoint;
|
|
28
28
|
},
|
|
29
29
|
};
|
package/lib/platform-permit.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 飞书 Lark platform permit
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
export declare function platformPermit(perm: string): string;
|
|
6
6
|
export declare function larkGroupPermitResolver(logicalPerm: string): string;
|
|
7
7
|
export declare function normalizeLarkSenderForPermit(input: {
|
|
@@ -11,5 +11,4 @@ export declare function normalizeLarkSenderForPermit(input: {
|
|
|
11
11
|
role?: string;
|
|
12
12
|
permissions?: string[];
|
|
13
13
|
};
|
|
14
|
-
export declare function checkLarkPlatformPermit(perm: string,
|
|
15
|
-
export declare function registerLarkPlatformPermitChecker(): () => void;
|
|
14
|
+
export declare function checkLarkPlatformPermit(perm: string, subject: PermissionSubject): boolean;
|
package/lib/platform-permit.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 飞书 Lark platform permit
|
|
3
|
-
*/
|
|
4
|
-
import { registerPlatformPermitChecker } from '@zhin.js/core';
|
|
5
1
|
const ADAPTER = 'lark';
|
|
6
2
|
export function platformPermit(perm) {
|
|
7
3
|
return `platform(${ADAPTER},${perm})`;
|
|
@@ -22,10 +18,9 @@ export function normalizeLarkSenderForPermit(input) {
|
|
|
22
18
|
}
|
|
23
19
|
return { role: 'member', permissions: [] };
|
|
24
20
|
}
|
|
25
|
-
export function checkLarkPlatformPermit(perm,
|
|
26
|
-
const
|
|
27
|
-
const permissions = sender
|
|
28
|
-
const role = sender.role;
|
|
21
|
+
export function checkLarkPlatformPermit(perm, subject) {
|
|
22
|
+
const role = subject.sender?.role?.[0];
|
|
23
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
29
24
|
const has = (t) => permissions.includes(t);
|
|
30
25
|
switch (perm) {
|
|
31
26
|
case 'chat_owner':
|
|
@@ -38,6 +33,3 @@ export function checkLarkPlatformPermit(perm, message) {
|
|
|
38
33
|
return false;
|
|
39
34
|
}
|
|
40
35
|
}
|
|
41
|
-
export function registerLarkPlatformPermitChecker() {
|
|
42
|
-
return registerPlatformPermitChecker(ADAPTER, checkLarkPlatformPermit);
|
|
43
|
-
}
|
package/lib/protocol.d.ts
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
*/
|
|
5
5
|
import type { IncomingMessage } from 'node:http';
|
|
6
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
6
7
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
7
8
|
export interface LarkAdapterConfig {
|
|
8
|
-
readonly
|
|
9
|
+
readonly id?: string;
|
|
9
10
|
readonly appId?: string;
|
|
10
11
|
readonly appSecret?: string;
|
|
11
12
|
readonly encryptKey?: string;
|
|
@@ -20,7 +21,7 @@ export interface LarkAdapterConfig {
|
|
|
20
21
|
}
|
|
21
22
|
export interface ResolvedLarkConfig {
|
|
22
23
|
readonly context: 'lark';
|
|
23
|
-
readonly
|
|
24
|
+
readonly id: string;
|
|
24
25
|
readonly appId: string;
|
|
25
26
|
readonly appSecret: string;
|
|
26
27
|
readonly encryptKey?: string;
|
|
@@ -96,7 +97,12 @@ export interface LarkSendBody {
|
|
|
96
97
|
export declare function resolveLarkConfig(config?: LarkAdapterConfig): ResolvedLarkConfig;
|
|
97
98
|
export declare function normalizeWebhookPath(path: string): string;
|
|
98
99
|
export declare function resolveChatType(chatId?: string, chatType?: string): 'group' | 'private';
|
|
99
|
-
|
|
100
|
+
/**
|
|
101
|
+
* 入站归一化 → ConversationRef:`chat_type` 为 'group' 的会话映射为 group,
|
|
102
|
+
* p2p 单聊映射为 private;id 一律为平台 chat_id。Lark 无 guild/频道容器,
|
|
103
|
+
* 不产生 `parent`。
|
|
104
|
+
*/
|
|
105
|
+
export declare function larkInboundConversation(endpointKey: string, msg: LarkMessage): ConversationRef;
|
|
100
106
|
export declare function resolveSender(msg: LarkMessage): string;
|
|
101
107
|
export declare function generateMessageId(msg: LarkMessage): string;
|
|
102
108
|
/** Build inbound text for MessageGateway.receive. */
|
package/lib/protocol.js
CHANGED
|
@@ -14,8 +14,8 @@ export function resolveLarkConfig(config = {}) {
|
|
|
14
14
|
throw new TypeError('Lark adapter requires appId + appSecret (plugins.<key> or endpoints with context: lark)');
|
|
15
15
|
}
|
|
16
16
|
const isFeishu = config.isFeishu ?? entry?.isFeishu ?? true;
|
|
17
|
-
const
|
|
18
|
-
|| (typeof entry?.
|
|
17
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
18
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
19
19
|
|| process.env.LARK_BOT_NAME
|
|
20
20
|
|| 'lark-bot';
|
|
21
21
|
const webhookPath = normalizeWebhookPath(config.webhookPath ?? entry?.webhookPath ?? '/lark/webhook');
|
|
@@ -27,7 +27,7 @@ export function resolveLarkConfig(config = {}) {
|
|
|
27
27
|
const verificationToken = config.verificationToken ?? entry?.verificationToken;
|
|
28
28
|
return {
|
|
29
29
|
context: 'lark',
|
|
30
|
-
|
|
30
|
+
id,
|
|
31
31
|
appId,
|
|
32
32
|
appSecret,
|
|
33
33
|
...(encryptKey ? { encryptKey } : {}),
|
|
@@ -49,8 +49,17 @@ export function resolveChatType(chatId, chatType) {
|
|
|
49
49
|
return 'private';
|
|
50
50
|
return chatId?.startsWith('oc_') ? 'group' : 'private';
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
/**
|
|
53
|
+
* 入站归一化 → ConversationRef:`chat_type` 为 'group' 的会话映射为 group,
|
|
54
|
+
* p2p 单聊映射为 private;id 一律为平台 chat_id。Lark 无 guild/频道容器,
|
|
55
|
+
* 不产生 `parent`。
|
|
56
|
+
*/
|
|
57
|
+
export function larkInboundConversation(endpointKey, msg) {
|
|
58
|
+
return {
|
|
59
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
60
|
+
kind: resolveChatType(msg.chat_id, msg.chat_type),
|
|
61
|
+
id: msg.chat_id || 'unknown',
|
|
62
|
+
};
|
|
54
63
|
}
|
|
55
64
|
export function resolveSender(msg) {
|
|
56
65
|
return msg.sender?.sender_id?.open_id
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-lark",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.5",
|
|
4
4
|
"description": "Zhin.js Lark/Feishu (飞书) adapter for Plugin Runtime (HTTP webhook)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -34,21 +34,23 @@
|
|
|
34
34
|
"directory": "plugins/adapters/lark"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@zhin.js/adapter": "1.1.
|
|
38
|
-
"@zhin.js/command": "1.0.
|
|
39
|
-
"@zhin.js/core": "1.5.
|
|
40
|
-
"@zhin.js/host-http": "1.0.
|
|
41
|
-
"@zhin.js/
|
|
42
|
-
"@zhin.js/
|
|
37
|
+
"@zhin.js/adapter": "1.1.7",
|
|
38
|
+
"@zhin.js/command": "1.0.9",
|
|
39
|
+
"@zhin.js/core": "1.5.4",
|
|
40
|
+
"@zhin.js/host-http": "1.0.8",
|
|
41
|
+
"@zhin.js/im-contract": "1.0.3",
|
|
42
|
+
"@zhin.js/logger": "1.0.76",
|
|
43
|
+
"@zhin.js/plugin-runtime": "1.1.5"
|
|
43
44
|
},
|
|
44
45
|
"peerDependencies": {
|
|
45
46
|
"zod": "^4.0.0",
|
|
46
|
-
"@zhin.js/adapter": "1.1.
|
|
47
|
-
"@zhin.js/agent": "1.1.
|
|
48
|
-
"@zhin.js/core": "1.5.
|
|
49
|
-
"@zhin.js/host-http": "1.0.
|
|
50
|
-
"@zhin.js/
|
|
51
|
-
"zhin.js": "
|
|
47
|
+
"@zhin.js/adapter": "1.1.7",
|
|
48
|
+
"@zhin.js/agent": "1.1.5",
|
|
49
|
+
"@zhin.js/core": "1.5.4",
|
|
50
|
+
"@zhin.js/host-http": "1.0.8",
|
|
51
|
+
"@zhin.js/permission": "1.0.1",
|
|
52
|
+
"@zhin.js/plugin-runtime": "1.1.5",
|
|
53
|
+
"zhin.js": "6.0.4"
|
|
52
54
|
},
|
|
53
55
|
"peerDependenciesMeta": {
|
|
54
56
|
"zhin.js": {
|
|
@@ -66,8 +68,8 @@
|
|
|
66
68
|
"typescript": "^6.0.3",
|
|
67
69
|
"vitest": "^4.1.10",
|
|
68
70
|
"zod": "^4.4.3",
|
|
69
|
-
"@zhin.js/agent": "1.1.
|
|
70
|
-
"zhin.js": "6.0.
|
|
71
|
+
"@zhin.js/agent": "1.1.5",
|
|
72
|
+
"zhin.js": "6.0.4"
|
|
71
73
|
},
|
|
72
74
|
"files": [
|
|
73
75
|
"adapters",
|
package/plugin.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { createEndpointRuntimeState } from '@zhin.js/adapter';
|
|
3
3
|
import { definePlugin } from '@zhin.js/plugin-runtime';
|
|
4
|
-
import {
|
|
4
|
+
import { permissionHostToken } from '@zhin.js/permission';
|
|
5
|
+
import { checkLarkPlatformPermit } from "./lib/platform-permit.js";
|
|
5
6
|
import { larkRuntimeStateToken } from "./lib/lark-runtime-state.js";
|
|
6
7
|
export default definePlugin({
|
|
7
8
|
name: 'lark',
|
|
@@ -9,8 +10,10 @@ export default definePlugin({
|
|
|
9
10
|
displayName: 'Lark/Feishu (飞书) Adapter',
|
|
10
11
|
},
|
|
11
12
|
setup(context) {
|
|
12
|
-
// 运行中 endpoint 注册表(lark.endpoint list 的"运行中"数据源)
|
|
13
13
|
context.resources.provide(larkRuntimeStateToken, createEndpointRuntimeState());
|
|
14
|
-
|
|
14
|
+
if (context.resources.has(permissionHostToken)) {
|
|
15
|
+
const host = context.resources.use(permissionHostToken);
|
|
16
|
+
return host.registerPlatform('lark', checkLarkPlatformPermit);
|
|
17
|
+
}
|
|
15
18
|
},
|
|
16
19
|
});
|
package/schema.json
CHANGED
|
@@ -14,16 +14,48 @@
|
|
|
14
14
|
"type": "boolean",
|
|
15
15
|
"default": true
|
|
16
16
|
},
|
|
17
|
+
"master": {
|
|
18
|
+
"type": [
|
|
19
|
+
"string",
|
|
20
|
+
"number"
|
|
21
|
+
],
|
|
22
|
+
"description": "框架 master(Lark/Feishu open_id or user_id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
|
|
23
|
+
},
|
|
24
|
+
"trusted": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"items": {
|
|
27
|
+
"type": [
|
|
28
|
+
"string",
|
|
29
|
+
"number"
|
|
30
|
+
],
|
|
31
|
+
"description": "Trusted Lark/Feishu open_id or user_id"
|
|
32
|
+
},
|
|
33
|
+
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
|
|
34
|
+
},
|
|
17
35
|
"endpoints": {
|
|
18
36
|
"type": "array",
|
|
19
|
-
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(
|
|
37
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
20
38
|
"items": {
|
|
21
39
|
"type": "object",
|
|
22
40
|
"additionalProperties": true,
|
|
23
41
|
"properties": {
|
|
24
|
-
"
|
|
25
|
-
"type":
|
|
26
|
-
|
|
42
|
+
"master": {
|
|
43
|
+
"type": [
|
|
44
|
+
"string",
|
|
45
|
+
"number"
|
|
46
|
+
],
|
|
47
|
+
"description": "本 endpoint 的框架 master(Lark/Feishu open_id or user_id);覆盖顶层 master"
|
|
48
|
+
},
|
|
49
|
+
"trusted": {
|
|
50
|
+
"type": "array",
|
|
51
|
+
"items": {
|
|
52
|
+
"type": [
|
|
53
|
+
"string",
|
|
54
|
+
"number"
|
|
55
|
+
],
|
|
56
|
+
"description": "Trusted Lark/Feishu open_id or user_id"
|
|
57
|
+
},
|
|
58
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
27
59
|
},
|
|
28
60
|
"appId": {
|
|
29
61
|
"type": "string",
|
|
@@ -40,10 +72,14 @@
|
|
|
40
72
|
"verificationToken": {
|
|
41
73
|
"type": "string",
|
|
42
74
|
"description": "Lark verification token"
|
|
75
|
+
},
|
|
76
|
+
"id": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"description": "Lark bot name"
|
|
43
79
|
}
|
|
44
80
|
},
|
|
45
81
|
"required": [
|
|
46
|
-
"
|
|
82
|
+
"id",
|
|
47
83
|
"appId",
|
|
48
84
|
"appSecret"
|
|
49
85
|
]
|
package/src/endpoint.ts
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { readFile } from 'node:fs/promises';
|
|
5
5
|
import { basename } from 'node:path';
|
|
6
|
-
import type { EndpointGroup, EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
6
|
+
import type { EndpointGroup, EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
|
|
7
7
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
8
8
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
9
|
-
import { formatCompact,
|
|
9
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
10
10
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
11
11
|
import { registerLarkAgentEndpoint } from './lark-agent-deps.js';
|
|
12
12
|
import {
|
|
@@ -18,9 +18,9 @@ import {
|
|
|
18
18
|
formatInboundContent,
|
|
19
19
|
formatOutboundBody,
|
|
20
20
|
generateMessageId,
|
|
21
|
+
larkInboundConversation,
|
|
21
22
|
resolveChatType,
|
|
22
23
|
resolveSender,
|
|
23
|
-
resolveTarget,
|
|
24
24
|
type AccessToken,
|
|
25
25
|
type LarkApiResponse,
|
|
26
26
|
type LarkMessage,
|
|
@@ -28,8 +28,6 @@ import {
|
|
|
28
28
|
} from './protocol.js';
|
|
29
29
|
import { registerLarkWebhookRoutes } from './webhook.js';
|
|
30
30
|
|
|
31
|
-
const logger = getLogger('lark');
|
|
32
|
-
|
|
33
31
|
/** 出站 HTTP 调用统一 30s 超时。 */
|
|
34
32
|
const OUTBOUND_TIMEOUT_MS = 30_000;
|
|
35
33
|
|
|
@@ -57,6 +55,8 @@ export interface LarkEndpointOptions {
|
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
export class LarkEndpoint implements EndpointInstance {
|
|
58
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
59
|
+
|
|
60
60
|
readonly #options: LarkEndpointOptions;
|
|
61
61
|
readonly #fetch: LarkFetch;
|
|
62
62
|
/**
|
|
@@ -93,6 +93,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
93
93
|
#unregisterAgent?: () => void;
|
|
94
94
|
|
|
95
95
|
constructor(options: LarkEndpointOptions) {
|
|
96
|
+
this.#logger = getAdapterLogger('lark', options.config.id);
|
|
96
97
|
this.#options = options;
|
|
97
98
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
98
99
|
}
|
|
@@ -112,24 +113,24 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
112
113
|
try {
|
|
113
114
|
if (!this.#options.config.encryptKey && !this.#options.config.verificationToken) {
|
|
114
115
|
// encryptKey / verificationToken 都未配置时 webhook 完全无鉴权,任何人可伪造事件
|
|
115
|
-
logger.warn(formatCompact({
|
|
116
|
-
endpoint: this.#options.config.
|
|
116
|
+
this.#logger.warn(formatCompact({
|
|
117
|
+
endpoint: this.#options.config.id,
|
|
117
118
|
op: 'webhook',
|
|
118
119
|
ok: false,
|
|
119
120
|
error: 'neither encryptKey nor verificationToken configured; webhook is unauthenticated',
|
|
120
121
|
}));
|
|
121
122
|
}
|
|
122
123
|
await this.#refreshAccessToken();
|
|
123
|
-
this.#unregisterAgent = registerLarkAgentEndpoint(this.#options.config.
|
|
124
|
+
this.#unregisterAgent = registerLarkAgentEndpoint(this.#options.config.id, this);
|
|
124
125
|
this.#routeReleases.push(...registerLarkWebhookRoutes(this.#options.http, this));
|
|
125
|
-
logger.debug(formatCompact({
|
|
126
|
-
endpoint: this.#options.config.
|
|
126
|
+
this.#logger.debug(formatCompact({
|
|
127
|
+
endpoint: this.#options.config.id,
|
|
127
128
|
op: 'webhook',
|
|
128
129
|
path: this.#options.config.webhookPath,
|
|
129
130
|
}));
|
|
130
131
|
} catch (error) {
|
|
131
132
|
await this.stop();
|
|
132
|
-
logger.error('Failed to connect Lark endpoint:', error);
|
|
133
|
+
this.#logger.error('Failed to connect Lark endpoint:', error);
|
|
133
134
|
throw error;
|
|
134
135
|
}
|
|
135
136
|
}
|
|
@@ -148,17 +149,17 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
148
149
|
this.#unregisterAgent?.();
|
|
149
150
|
this.#unregisterAgent = undefined;
|
|
150
151
|
this.#started = false;
|
|
151
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
152
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
async send({
|
|
155
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
155
156
|
const materialized = await this.#materializeOutboundMedia(payload);
|
|
156
157
|
const content = formatOutboundBody(materialized);
|
|
157
158
|
const data = await this.#request('/im/v1/messages', {
|
|
158
159
|
method: 'POST',
|
|
159
160
|
params: { receive_id_type: 'chat_id' },
|
|
160
161
|
body: {
|
|
161
|
-
receive_id:
|
|
162
|
+
receive_id: conversation.id,
|
|
162
163
|
msg_type: content.msg_type,
|
|
163
164
|
content: content.content,
|
|
164
165
|
},
|
|
@@ -167,10 +168,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
167
168
|
throw new Error(`Failed to send message: ${data.msg}`);
|
|
168
169
|
}
|
|
169
170
|
const messageId = (data.data?.message_id as string) || `${Date.now()}`;
|
|
170
|
-
logger.debug(formatCompact({
|
|
171
|
-
op: 'send',
|
|
172
|
-
endpoint: this.#options.config.name,
|
|
173
|
-
to: target,
|
|
171
|
+
this.#logger.debug(formatCompact({ op: 'send', to: conversation.id,
|
|
174
172
|
id: messageId,
|
|
175
173
|
}));
|
|
176
174
|
return messageId;
|
|
@@ -226,7 +224,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
226
224
|
}
|
|
227
225
|
throw new Error(`Image upload failed: ${data.msg}`);
|
|
228
226
|
} catch (error) {
|
|
229
|
-
logger.error('Failed to upload image:', error);
|
|
227
|
+
this.#logger.error('Failed to upload image:', error);
|
|
230
228
|
return null;
|
|
231
229
|
}
|
|
232
230
|
}
|
|
@@ -234,22 +232,21 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
234
232
|
/** Test / internal: admit a parsed message when open (non-webhook path). */
|
|
235
233
|
admit(msg: LarkMessage): void {
|
|
236
234
|
if (!this.#open) return;
|
|
237
|
-
const
|
|
235
|
+
const conversation = larkInboundConversation(String(this.#options.id), msg);
|
|
238
236
|
void this.#options.gateway.receive({
|
|
239
|
-
|
|
240
|
-
|
|
237
|
+
conversation,
|
|
238
|
+
message: { conversation, id: generateMessageId(msg) },
|
|
241
239
|
content: formatInboundContent(msg),
|
|
242
|
-
sender: resolveSender(msg),
|
|
243
|
-
|
|
240
|
+
sender: { id: resolveSender(msg) },
|
|
241
|
+
endpointId: this.#options.config.id,
|
|
244
242
|
metadata: Object.freeze({
|
|
245
243
|
messageType: msg.message_type,
|
|
246
244
|
chatType: resolveChatType(msg.chat_id, msg.chat_type),
|
|
247
|
-
endpoint: this.#options.config.name,
|
|
248
245
|
}),
|
|
249
246
|
}).catch((err) => {
|
|
250
|
-
logger.warn(formatCompact({
|
|
247
|
+
this.#logger.warn(formatCompact({
|
|
251
248
|
op: 'lark_gateway_receive_failed',
|
|
252
|
-
|
|
249
|
+
conversation: conversation.id,
|
|
253
250
|
error: err instanceof Error ? err.message : String(err),
|
|
254
251
|
}));
|
|
255
252
|
});
|
|
@@ -266,7 +263,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
266
263
|
});
|
|
267
264
|
return data.data?.user ?? null;
|
|
268
265
|
} catch (error) {
|
|
269
|
-
logger.error('Failed to get user info:', error);
|
|
266
|
+
this.#logger.error('Failed to get user info:', error);
|
|
270
267
|
return null;
|
|
271
268
|
}
|
|
272
269
|
}
|
|
@@ -276,7 +273,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
276
273
|
const data = await this.#request(`/im/v1/chats/${chatId}`, { method: 'GET' });
|
|
277
274
|
return data.data ?? null;
|
|
278
275
|
} catch (error) {
|
|
279
|
-
logger.error('Failed to get chat info:', error);
|
|
276
|
+
this.#logger.error('Failed to get chat info:', error);
|
|
280
277
|
return null;
|
|
281
278
|
}
|
|
282
279
|
}
|
|
@@ -303,7 +300,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
303
300
|
}
|
|
304
301
|
throw new Error(`Upload failed: ${data.msg}`);
|
|
305
302
|
} catch (error) {
|
|
306
|
-
logger.error('Failed to upload file:', error);
|
|
303
|
+
this.#logger.error('Failed to upload file:', error);
|
|
307
304
|
return null;
|
|
308
305
|
}
|
|
309
306
|
}
|
|
@@ -325,7 +322,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
325
322
|
if (data.code === 0) return (data.data?.chat_id as string) || null;
|
|
326
323
|
throw new Error(`Failed to create chat: ${data.msg}`);
|
|
327
324
|
} catch (error) {
|
|
328
|
-
logger.error('Failed to create chat:', error);
|
|
325
|
+
this.#logger.error('Failed to create chat:', error);
|
|
329
326
|
return null;
|
|
330
327
|
}
|
|
331
328
|
}
|
|
@@ -342,7 +339,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
342
339
|
if (data.code === 0) return true;
|
|
343
340
|
throw new Error(`Failed to update chat: ${data.msg}`);
|
|
344
341
|
} catch (error) {
|
|
345
|
-
logger.error('Failed to update chat:', error);
|
|
342
|
+
this.#logger.error('Failed to update chat:', error);
|
|
346
343
|
return false;
|
|
347
344
|
}
|
|
348
345
|
}
|
|
@@ -356,7 +353,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
356
353
|
if (data.code === 0) return true;
|
|
357
354
|
throw new Error(`Failed to add members: ${data.msg}`);
|
|
358
355
|
} catch (error) {
|
|
359
|
-
logger.error('Failed to add chat members:', error);
|
|
356
|
+
this.#logger.error('Failed to add chat members:', error);
|
|
360
357
|
return false;
|
|
361
358
|
}
|
|
362
359
|
}
|
|
@@ -370,7 +367,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
370
367
|
if (data.code === 0) return true;
|
|
371
368
|
throw new Error(`Failed to remove members: ${data.msg}`);
|
|
372
369
|
} catch (error) {
|
|
373
|
-
logger.error('Failed to remove chat members:', error);
|
|
370
|
+
this.#logger.error('Failed to remove chat members:', error);
|
|
374
371
|
return false;
|
|
375
372
|
}
|
|
376
373
|
}
|
|
@@ -381,7 +378,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
381
378
|
if (data.code === 0) return (data.data?.items as unknown[]) || [];
|
|
382
379
|
throw new Error(`Failed to get members: ${data.msg}`);
|
|
383
380
|
} catch (error) {
|
|
384
|
-
logger.error('Failed to get chat members:', error);
|
|
381
|
+
this.#logger.error('Failed to get chat members:', error);
|
|
385
382
|
return [];
|
|
386
383
|
}
|
|
387
384
|
}
|
|
@@ -392,7 +389,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
392
389
|
if (data.code === 0) return true;
|
|
393
390
|
throw new Error(`Failed to dissolve chat: ${data.msg}`);
|
|
394
391
|
} catch (error) {
|
|
395
|
-
logger.error('Failed to dissolve chat:', error);
|
|
392
|
+
this.#logger.error('Failed to dissolve chat:', error);
|
|
396
393
|
return false;
|
|
397
394
|
}
|
|
398
395
|
}
|
|
@@ -406,7 +403,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
406
403
|
if (data.code === 0) return true;
|
|
407
404
|
throw new Error(`Failed to set managers: ${data.msg}`);
|
|
408
405
|
} catch (error) {
|
|
409
|
-
logger.error('Failed to set chat managers:', error);
|
|
406
|
+
this.#logger.error('Failed to set chat managers:', error);
|
|
410
407
|
return false;
|
|
411
408
|
}
|
|
412
409
|
}
|
|
@@ -420,7 +417,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
420
417
|
if (data.code === 0) return true;
|
|
421
418
|
throw new Error(`Failed to remove managers: ${data.msg}`);
|
|
422
419
|
} catch (error) {
|
|
423
|
-
logger.error('Failed to remove chat managers:', error);
|
|
420
|
+
this.#logger.error('Failed to remove chat managers:', error);
|
|
424
421
|
return false;
|
|
425
422
|
}
|
|
426
423
|
}
|
|
@@ -525,7 +522,7 @@ export class LarkEndpoint implements EndpointInstance {
|
|
|
525
522
|
expires_in: data.expire ?? 7200,
|
|
526
523
|
timestamp: Date.now(),
|
|
527
524
|
};
|
|
528
|
-
logger.debug('Access token refreshed successfully');
|
|
525
|
+
this.#logger.debug('Access token refreshed successfully');
|
|
529
526
|
return;
|
|
530
527
|
}
|
|
531
528
|
throw new Error(`Failed to get access token: ${data.msg} (${data.code})`);
|
package/src/index.ts
CHANGED
|
@@ -3,12 +3,12 @@ export {
|
|
|
3
3
|
formatOutboundBody,
|
|
4
4
|
generateMessageId,
|
|
5
5
|
headerValue,
|
|
6
|
+
larkInboundConversation,
|
|
6
7
|
normalizeWebhookPath,
|
|
7
8
|
readTextBody,
|
|
8
9
|
resolveChatType,
|
|
9
10
|
resolveLarkConfig,
|
|
10
11
|
resolveSender,
|
|
11
|
-
resolveTarget,
|
|
12
12
|
verifySignature,
|
|
13
13
|
type AccessToken,
|
|
14
14
|
type LarkAdapterConfig,
|
|
@@ -33,7 +33,6 @@ export {
|
|
|
33
33
|
larkGroupPermitResolver,
|
|
34
34
|
normalizeLarkSenderForPermit,
|
|
35
35
|
platformPermit,
|
|
36
|
-
registerLarkPlatformPermitChecker,
|
|
37
36
|
} from './platform-permit.js';
|
|
38
37
|
|
|
39
38
|
export {
|
package/src/lark-agent-deps.ts
CHANGED
|
@@ -27,20 +27,20 @@ export interface LarkAgentEndpoint {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface LarkAgentDeps {
|
|
30
|
-
getEndpoint: (
|
|
30
|
+
getEndpoint: (endpointKey: string) => LarkAgentEndpoint;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
const endpoints = new Map<string, LarkAgentEndpoint>();
|
|
34
34
|
let override: LarkAgentDeps | null = null;
|
|
35
35
|
|
|
36
36
|
export function registerLarkAgentEndpoint(
|
|
37
|
-
|
|
37
|
+
endpointKey: string,
|
|
38
38
|
endpoint: LarkAgentEndpoint,
|
|
39
39
|
): () => void {
|
|
40
|
-
endpoints.set(
|
|
40
|
+
endpoints.set(endpointKey, endpoint);
|
|
41
41
|
return () => {
|
|
42
|
-
if (endpoints.get(
|
|
43
|
-
endpoints.delete(
|
|
42
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
43
|
+
endpoints.delete(endpointKey);
|
|
44
44
|
}
|
|
45
45
|
};
|
|
46
46
|
}
|
|
@@ -53,9 +53,9 @@ export function setLarkAgentDeps(deps: LarkAgentDeps | null): void {
|
|
|
53
53
|
export function getLarkAgentDeps(): LarkAgentDeps {
|
|
54
54
|
if (override) return override;
|
|
55
55
|
return {
|
|
56
|
-
getEndpoint(
|
|
57
|
-
const endpoint = endpoints.get(
|
|
58
|
-
if (!endpoint) throw new Error(`Endpoint ${
|
|
56
|
+
getEndpoint(endpointKey) {
|
|
57
|
+
const endpoint = endpoints.get(endpointKey);
|
|
58
|
+
if (!endpoint) throw new Error(`Endpoint ${endpointKey} 不存在`);
|
|
59
59
|
return endpoint;
|
|
60
60
|
},
|
|
61
61
|
};
|
package/src/platform-permit.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 飞书 Lark platform permit
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
|
|
6
6
|
const ADAPTER = 'lark';
|
|
7
7
|
|
|
@@ -31,10 +31,9 @@ export function normalizeLarkSenderForPermit(input: {
|
|
|
31
31
|
return { role: 'member', permissions: [] };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function checkLarkPlatformPermit(perm: string,
|
|
35
|
-
const
|
|
36
|
-
const permissions = sender
|
|
37
|
-
const role = sender.role;
|
|
34
|
+
export function checkLarkPlatformPermit(perm: string, subject: PermissionSubject): boolean {
|
|
35
|
+
const role = subject.sender?.role?.[0];
|
|
36
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
38
37
|
const has = (t: string) => permissions.includes(t);
|
|
39
38
|
|
|
40
39
|
switch (perm) {
|
|
@@ -49,6 +48,3 @@ export function checkLarkPlatformPermit(perm: string, message: Message<any>): bo
|
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
export function registerLarkPlatformPermitChecker(): () => void {
|
|
53
|
-
return registerPlatformPermitChecker(ADAPTER, checkLarkPlatformPermit);
|
|
54
|
-
}
|
package/src/protocol.ts
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
7
7
|
import type { IncomingMessage } from 'node:http';
|
|
8
8
|
import { isMediaRef } from '@zhin.js/core';
|
|
9
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
9
10
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
10
11
|
|
|
11
12
|
const logger = getLogger('lark');
|
|
12
13
|
|
|
13
14
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
14
15
|
export interface LarkAdapterConfig {
|
|
15
|
-
readonly
|
|
16
|
+
readonly id?: string;
|
|
16
17
|
readonly appId?: string;
|
|
17
18
|
readonly appSecret?: string;
|
|
18
19
|
readonly encryptKey?: string;
|
|
@@ -28,7 +29,7 @@ export interface LarkAdapterConfig {
|
|
|
28
29
|
|
|
29
30
|
export interface ResolvedLarkConfig {
|
|
30
31
|
readonly context: 'lark';
|
|
31
|
-
readonly
|
|
32
|
+
readonly id: string;
|
|
32
33
|
readonly appId: string;
|
|
33
34
|
readonly appSecret: string;
|
|
34
35
|
readonly encryptKey?: string;
|
|
@@ -118,8 +119,8 @@ export function resolveLarkConfig(config: LarkAdapterConfig = {}): ResolvedLarkC
|
|
|
118
119
|
);
|
|
119
120
|
}
|
|
120
121
|
const isFeishu = config.isFeishu ?? entry?.isFeishu ?? true;
|
|
121
|
-
const
|
|
122
|
-
|| (typeof entry?.
|
|
122
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
123
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
123
124
|
|| process.env.LARK_BOT_NAME
|
|
124
125
|
|| 'lark-bot';
|
|
125
126
|
const webhookPath = normalizeWebhookPath(
|
|
@@ -135,7 +136,7 @@ export function resolveLarkConfig(config: LarkAdapterConfig = {}): ResolvedLarkC
|
|
|
135
136
|
const verificationToken = config.verificationToken ?? entry?.verificationToken;
|
|
136
137
|
return {
|
|
137
138
|
context: 'lark',
|
|
138
|
-
|
|
139
|
+
id,
|
|
139
140
|
appId,
|
|
140
141
|
appSecret,
|
|
141
142
|
...(encryptKey ? { encryptKey } : {}),
|
|
@@ -158,8 +159,17 @@ export function resolveChatType(chatId?: string, chatType?: string): 'group' | '
|
|
|
158
159
|
return chatId?.startsWith('oc_') ? 'group' : 'private';
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
/**
|
|
163
|
+
* 入站归一化 → ConversationRef:`chat_type` 为 'group' 的会话映射为 group,
|
|
164
|
+
* p2p 单聊映射为 private;id 一律为平台 chat_id。Lark 无 guild/频道容器,
|
|
165
|
+
* 不产生 `parent`。
|
|
166
|
+
*/
|
|
167
|
+
export function larkInboundConversation(endpointKey: string, msg: LarkMessage): ConversationRef {
|
|
168
|
+
return {
|
|
169
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
170
|
+
kind: resolveChatType(msg.chat_id, msg.chat_type),
|
|
171
|
+
id: msg.chat_id || 'unknown',
|
|
172
|
+
};
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
export function resolveSender(msg: LarkMessage): string {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|