@zhin.js/adapter-dingtalk 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/dingtalk.js +2 -2
- package/adapters/dingtalk.ts +2 -2
- package/lib/dingtalk-agent-deps.d.ts +2 -2
- package/lib/dingtalk-agent-deps.js +7 -7
- package/lib/endpoint.d.ts +2 -5
- package/lib/endpoint.js +36 -32
- package/lib/index.d.ts +2 -2
- package/lib/index.js +2 -2
- 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/dingtalk-agent-deps.ts +8 -8
- package/src/endpoint.ts +38 -34
- package/src/index.ts +1 -2
- 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-dingtalk
|
|
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-dingtalk
|
|
|
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: ConversationRef, message: { conversation, id }, content: text, sender, metadata })`
|
|
28
|
+
出站:`send({ conversation, payload })` → sessionWebhook 或 `/robot/send`
|
|
29
29
|
|
|
30
30
|
## 前置条件
|
|
31
31
|
|
package/adapters/dingtalk.js
CHANGED
|
@@ -19,8 +19,8 @@ export default defineAdapter({
|
|
|
19
19
|
create(context) {
|
|
20
20
|
const config = resolveDingTalkConfig(context.config);
|
|
21
21
|
// 注册到插件运行时状态(dingtalk.endpoint list 的"运行中"数据源)
|
|
22
|
-
context.use(dingtalkRuntimeStateToken).endpoints.set(config.
|
|
23
|
-
|
|
22
|
+
context.use(dingtalkRuntimeStateToken).endpoints.set(config.id, {
|
|
23
|
+
id: config.id,
|
|
24
24
|
mode: 'webhook',
|
|
25
25
|
});
|
|
26
26
|
return new DingTalkEndpoint({
|
package/adapters/dingtalk.ts
CHANGED
|
@@ -24,8 +24,8 @@ export default defineAdapter<DingTalkAdapterConfig>({
|
|
|
24
24
|
create(context) {
|
|
25
25
|
const config = resolveDingTalkConfig(context.config);
|
|
26
26
|
// 注册到插件运行时状态(dingtalk.endpoint list 的"运行中"数据源)
|
|
27
|
-
context.use(dingtalkRuntimeStateToken).endpoints.set(config.
|
|
28
|
-
|
|
27
|
+
context.use(dingtalkRuntimeStateToken).endpoints.set(config.id, {
|
|
28
|
+
id: config.id,
|
|
29
29
|
mode: 'webhook',
|
|
30
30
|
});
|
|
31
31
|
return new DingTalkEndpoint({
|
|
@@ -18,9 +18,9 @@ export interface DingtalkAgentEndpoint {
|
|
|
18
18
|
}): Promise<boolean>;
|
|
19
19
|
}
|
|
20
20
|
export interface DingtalkAgentDeps {
|
|
21
|
-
getEndpoint: (
|
|
21
|
+
getEndpoint: (endpointKey: string) => DingtalkAgentEndpoint;
|
|
22
22
|
}
|
|
23
|
-
export declare function registerDingtalkAgentEndpoint(
|
|
23
|
+
export declare function registerDingtalkAgentEndpoint(endpointKey: string, endpoint: DingtalkAgentEndpoint): () => void;
|
|
24
24
|
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
25
25
|
export declare function setDingtalkAgentDeps(deps: DingtalkAgentDeps | null): void;
|
|
26
26
|
export declare function getDingtalkAgentDeps(): DingtalkAgentDeps;
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const endpoints = new Map();
|
|
6
6
|
let override = null;
|
|
7
|
-
export function registerDingtalkAgentEndpoint(
|
|
8
|
-
endpoints.set(
|
|
7
|
+
export function registerDingtalkAgentEndpoint(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 getDingtalkAgentDeps() {
|
|
|
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/endpoint.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DingTalkEndpoint — lifecycle, outbound, admit, OpenAPI helpers for agent tools.
|
|
3
3
|
*/
|
|
4
|
-
import type { EndpointInstance } from '@zhin.js/adapter';
|
|
4
|
+
import type { EndpointInstance, EndpointSendRequest } from '@zhin.js/adapter';
|
|
5
5
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
6
6
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
7
7
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
@@ -38,10 +38,7 @@ export declare class DingTalkEndpoint implements EndpointInstance {
|
|
|
38
38
|
open(): void;
|
|
39
39
|
close(): void;
|
|
40
40
|
stop(): Promise<void>;
|
|
41
|
-
send({
|
|
42
|
-
readonly target: string;
|
|
43
|
-
readonly payload: unknown;
|
|
44
|
-
}): Promise<string>;
|
|
41
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
45
42
|
/** Test / internal: admit a parsed event when open (non-webhook path). */
|
|
46
43
|
admit(event: DingTalkEvent | DingTalkMessage): void;
|
|
47
44
|
getUserInfo(userId: string): Promise<unknown>;
|
package/lib/endpoint.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { formatCompact,
|
|
1
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
2
2
|
import { registerDingtalkAgentEndpoint } from './dingtalk-agent-deps.js';
|
|
3
3
|
import { normalizeDingtalkSenderForPermit } from './platform-permit.js';
|
|
4
|
-
import { formatInboundContent, formatOutboundBody, generateMessageId, isDingtalkBotMentioned, resolveChatType, resolveSender,
|
|
4
|
+
import { dingtalkInboundConversation, formatInboundContent, formatOutboundBody, generateMessageId, isDingtalkBotMentioned, resolveChatType, resolveSender, } from './protocol.js';
|
|
5
5
|
import { registerDingTalkWebhookRoutes } from './webhook.js';
|
|
6
|
-
const logger = getLogger('dingtalk');
|
|
7
6
|
/**
|
|
8
7
|
* 钉钉机器人(webhook/stream 模式)无常规群列表 API——机器人不持有
|
|
9
8
|
* 「我所在的群」枚举面,仅能收发消息;
|
|
10
9
|
* 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
|
|
11
10
|
*/
|
|
12
11
|
export class DingTalkEndpoint {
|
|
12
|
+
#logger;
|
|
13
13
|
#options;
|
|
14
14
|
#fetch;
|
|
15
15
|
#routeReleases = [];
|
|
@@ -20,6 +20,7 @@ export class DingTalkEndpoint {
|
|
|
20
20
|
#started = false;
|
|
21
21
|
#unregisterAgent;
|
|
22
22
|
constructor(options) {
|
|
23
|
+
this.#logger = getAdapterLogger('dingtalk', options.config.id);
|
|
23
24
|
this.#options = options;
|
|
24
25
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
25
26
|
}
|
|
@@ -36,17 +37,17 @@ export class DingTalkEndpoint {
|
|
|
36
37
|
this.#started = true;
|
|
37
38
|
try {
|
|
38
39
|
await this.#refreshAccessToken();
|
|
39
|
-
this.#unregisterAgent = registerDingtalkAgentEndpoint(this.#options.config.
|
|
40
|
+
this.#unregisterAgent = registerDingtalkAgentEndpoint(this.#options.config.id, this);
|
|
40
41
|
this.#routeReleases.push(...registerDingTalkWebhookRoutes(this.#options.http, this));
|
|
41
|
-
logger.debug(formatCompact({
|
|
42
|
-
endpoint: this.#options.config.
|
|
42
|
+
this.#logger.debug(formatCompact({
|
|
43
|
+
endpoint: this.#options.config.id,
|
|
43
44
|
op: 'webhook',
|
|
44
45
|
path: this.#options.config.webhookPath,
|
|
45
46
|
}));
|
|
46
47
|
}
|
|
47
48
|
catch (error) {
|
|
48
49
|
await this.stop();
|
|
49
|
-
logger.error('Failed to connect DingTalk endpoint:', error);
|
|
50
|
+
this.#logger.error('Failed to connect DingTalk endpoint:', error);
|
|
50
51
|
throw error;
|
|
51
52
|
}
|
|
52
53
|
}
|
|
@@ -64,11 +65,11 @@ export class DingTalkEndpoint {
|
|
|
64
65
|
this.#unregisterAgent?.();
|
|
65
66
|
this.#unregisterAgent = undefined;
|
|
66
67
|
this.#started = false;
|
|
67
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
68
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
68
69
|
}
|
|
69
|
-
async send({
|
|
70
|
+
async send({ conversation, payload }) {
|
|
70
71
|
const content = formatOutboundBody(payload);
|
|
71
|
-
const sessionWebhook = this.#sessionWebhooks.get(
|
|
72
|
+
const sessionWebhook = this.#sessionWebhooks.get(conversation.id);
|
|
72
73
|
if (sessionWebhook) {
|
|
73
74
|
const response = await this.#fetch(sessionWebhook, {
|
|
74
75
|
method: 'POST',
|
|
@@ -79,11 +80,11 @@ export class DingTalkEndpoint {
|
|
|
79
80
|
if (data.errcode !== 0) {
|
|
80
81
|
throw new Error(`Failed to send message via session webhook: ${data.errmsg}`);
|
|
81
82
|
}
|
|
82
|
-
logger.debug(formatCompact({
|
|
83
|
+
this.#logger.debug(formatCompact({
|
|
83
84
|
op: 'send',
|
|
84
|
-
endpoint: this.#options.config.
|
|
85
|
+
endpoint: this.#options.config.id,
|
|
85
86
|
via: 'sessionWebhook',
|
|
86
|
-
to:
|
|
87
|
+
to: conversation.id,
|
|
87
88
|
}));
|
|
88
89
|
return data.msgId || `${Date.now()}`;
|
|
89
90
|
}
|
|
@@ -100,7 +101,7 @@ export class DingTalkEndpoint {
|
|
|
100
101
|
if (data.errcode !== 0) {
|
|
101
102
|
throw new Error(`Failed to send message: ${data.errmsg}`);
|
|
102
103
|
}
|
|
103
|
-
logger.debug(formatCompact({ op: 'send',
|
|
104
|
+
this.#logger.debug(formatCompact({ op: 'send', to: conversation.id }));
|
|
104
105
|
return data.msgId || `${Date.now()}`;
|
|
105
106
|
}
|
|
106
107
|
/** Test / internal: admit a parsed event when open (non-webhook path). */
|
|
@@ -110,29 +111,32 @@ export class DingTalkEndpoint {
|
|
|
110
111
|
if (event.sessionWebhook && event.conversationId) {
|
|
111
112
|
this.#sessionWebhooks.set(event.conversationId, event.sessionWebhook);
|
|
112
113
|
}
|
|
113
|
-
const
|
|
114
|
+
const conversation = dingtalkInboundConversation(String(this.#options.id), event);
|
|
114
115
|
const chatType = resolveChatType(event.conversationType);
|
|
115
116
|
const permit = normalizeDingtalkSenderForPermit({ isAdmin: event.isAdmin === true });
|
|
116
117
|
void this.#options.gateway.receive({
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
conversation,
|
|
119
|
+
message: { conversation, id: generateMessageId(event) },
|
|
119
120
|
content: formatInboundContent(event),
|
|
120
|
-
sender:
|
|
121
|
-
|
|
121
|
+
sender: {
|
|
122
|
+
id: resolveSender(event),
|
|
123
|
+
name: event.senderNick || undefined,
|
|
124
|
+
...(permit.role ? { roles: [permit.role] } : {}),
|
|
125
|
+
},
|
|
126
|
+
endpointId: this.#options.config.id,
|
|
127
|
+
...(isDingtalkBotMentioned(event, this.#options.config.robotCode) ? { mentioned: true } : {}),
|
|
122
128
|
metadata: Object.freeze({
|
|
123
129
|
msgtype: event.msgtype,
|
|
124
130
|
chatType,
|
|
125
|
-
endpoint: this.#options.config.name,
|
|
126
131
|
senderNick: event.senderNick,
|
|
127
132
|
role: permit.role,
|
|
128
133
|
permissions: permit.permissions,
|
|
129
134
|
conversationType: event.conversationType,
|
|
130
|
-
...(isDingtalkBotMentioned(event, this.#options.config.robotCode) ? { mentioned: true } : {}),
|
|
131
135
|
}),
|
|
132
136
|
}).catch((err) => {
|
|
133
|
-
logger.warn(formatCompact({
|
|
137
|
+
this.#logger.warn(formatCompact({
|
|
134
138
|
op: 'dingtalk_gateway_receive_failed',
|
|
135
|
-
|
|
139
|
+
conversationId: conversation.id,
|
|
136
140
|
error: err instanceof Error ? err.message : String(err),
|
|
137
141
|
}));
|
|
138
142
|
});
|
|
@@ -148,7 +152,7 @@ export class DingTalkEndpoint {
|
|
|
148
152
|
throw new Error(`Failed to get user info: ${data.errmsg}`);
|
|
149
153
|
}
|
|
150
154
|
catch (error) {
|
|
151
|
-
logger.error('Failed to get user info:', error);
|
|
155
|
+
this.#logger.error('Failed to get user info:', error);
|
|
152
156
|
return null;
|
|
153
157
|
}
|
|
154
158
|
}
|
|
@@ -165,7 +169,7 @@ export class DingTalkEndpoint {
|
|
|
165
169
|
throw new Error(`Failed to get department users: ${data.errmsg}`);
|
|
166
170
|
}
|
|
167
171
|
catch (error) {
|
|
168
|
-
logger.error('Failed to get department users:', error);
|
|
172
|
+
this.#logger.error('Failed to get department users:', error);
|
|
169
173
|
return [];
|
|
170
174
|
}
|
|
171
175
|
}
|
|
@@ -184,7 +188,7 @@ export class DingTalkEndpoint {
|
|
|
184
188
|
throw new Error(`Failed to send work notice: ${data.errmsg}`);
|
|
185
189
|
}
|
|
186
190
|
catch (error) {
|
|
187
|
-
logger.error('Failed to send work notice:', error);
|
|
191
|
+
this.#logger.error('Failed to send work notice:', error);
|
|
188
192
|
return false;
|
|
189
193
|
}
|
|
190
194
|
}
|
|
@@ -199,7 +203,7 @@ export class DingTalkEndpoint {
|
|
|
199
203
|
throw new Error(`Failed to get department list: ${data.errmsg}`);
|
|
200
204
|
}
|
|
201
205
|
catch (error) {
|
|
202
|
-
logger.error('Failed to get department list:', error);
|
|
206
|
+
this.#logger.error('Failed to get department list:', error);
|
|
203
207
|
return [];
|
|
204
208
|
}
|
|
205
209
|
}
|
|
@@ -214,7 +218,7 @@ export class DingTalkEndpoint {
|
|
|
214
218
|
throw new Error(`Failed to get department info: ${data.errmsg}`);
|
|
215
219
|
}
|
|
216
220
|
catch (error) {
|
|
217
|
-
logger.error('Failed to get department info:', error);
|
|
221
|
+
this.#logger.error('Failed to get department info:', error);
|
|
218
222
|
return null;
|
|
219
223
|
}
|
|
220
224
|
}
|
|
@@ -229,7 +233,7 @@ export class DingTalkEndpoint {
|
|
|
229
233
|
throw new Error(`Failed to create chat: ${data.errmsg}`);
|
|
230
234
|
}
|
|
231
235
|
catch (error) {
|
|
232
|
-
logger.error('Failed to create chat:', error);
|
|
236
|
+
this.#logger.error('Failed to create chat:', error);
|
|
233
237
|
return null;
|
|
234
238
|
}
|
|
235
239
|
}
|
|
@@ -244,7 +248,7 @@ export class DingTalkEndpoint {
|
|
|
244
248
|
throw new Error(`Failed to get chat info: ${data.errmsg}`);
|
|
245
249
|
}
|
|
246
250
|
catch (error) {
|
|
247
|
-
logger.error('Failed to get chat info:', error);
|
|
251
|
+
this.#logger.error('Failed to get chat info:', error);
|
|
248
252
|
return null;
|
|
249
253
|
}
|
|
250
254
|
}
|
|
@@ -259,7 +263,7 @@ export class DingTalkEndpoint {
|
|
|
259
263
|
throw new Error(`Failed to update chat: ${data.errmsg}`);
|
|
260
264
|
}
|
|
261
265
|
catch (error) {
|
|
262
|
-
logger.error('Failed to update chat:', error);
|
|
266
|
+
this.#logger.error('Failed to update chat:', error);
|
|
263
267
|
return false;
|
|
264
268
|
}
|
|
265
269
|
}
|
|
@@ -309,7 +313,7 @@ export class DingTalkEndpoint {
|
|
|
309
313
|
expires_in: data.expires_in ?? 7200,
|
|
310
314
|
timestamp: Date.now(),
|
|
311
315
|
};
|
|
312
|
-
logger.debug('Access token refreshed successfully');
|
|
316
|
+
this.#logger.debug('Access token refreshed successfully');
|
|
313
317
|
return;
|
|
314
318
|
}
|
|
315
319
|
throw new Error(`Failed to get access token: ${data.errmsg} (${data.errcode})`);
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { formatInboundContent, formatOutboundBody, generateMessageId, headerValue, normalizeWebhookPath, readTextBody, resolveChatType, resolveDingTalkConfig, resolveSender,
|
|
1
|
+
export { dingtalkInboundConversation, formatInboundContent, formatOutboundBody, generateMessageId, headerValue, normalizeWebhookPath, readTextBody, resolveChatType, resolveDingTalkConfig, resolveSender, verifySignature, type AccessToken, type DingTalkAdapterConfig, type DingTalkApiResponse, type DingTalkEvent, type DingTalkMessage, type DingTalkSendBody, type DingTalkWireSegment, type ResolvedDingTalkConfig, } from './protocol.js';
|
|
2
2
|
export { DingTalkEndpoint, type DingTalkEndpointOptions, type DingTalkFetch, } from './endpoint.js';
|
|
3
3
|
export { registerDingTalkWebhookRoutes, handleDingTalkWebhookRequest, type DingTalkWebhookHandler, } from './webhook.js';
|
|
4
4
|
export { getDingtalkAgentDeps, registerDingtalkAgentEndpoint, setDingtalkAgentDeps, type DingtalkAgentDeps, type DingtalkAgentEndpoint, } from './dingtalk-agent-deps.js';
|
|
5
|
-
export { checkDingtalkPlatformPermit, dingtalkGroupPermitResolver, normalizeDingtalkSenderForPermit, platformPermit,
|
|
5
|
+
export { checkDingtalkPlatformPermit, dingtalkGroupPermitResolver, normalizeDingtalkSenderForPermit, platformPermit, } from './platform-permit.js';
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { formatInboundContent, formatOutboundBody, generateMessageId, headerValue, normalizeWebhookPath, readTextBody, resolveChatType, resolveDingTalkConfig, resolveSender,
|
|
1
|
+
export { dingtalkInboundConversation, formatInboundContent, formatOutboundBody, generateMessageId, headerValue, normalizeWebhookPath, readTextBody, resolveChatType, resolveDingTalkConfig, resolveSender, verifySignature, } from './protocol.js';
|
|
2
2
|
export { DingTalkEndpoint, } from './endpoint.js';
|
|
3
3
|
export { registerDingTalkWebhookRoutes, handleDingTalkWebhookRequest, } from './webhook.js';
|
|
4
4
|
export { getDingtalkAgentDeps, registerDingtalkAgentEndpoint, setDingtalkAgentDeps, } from './dingtalk-agent-deps.js';
|
|
5
|
-
export { checkDingtalkPlatformPermit, dingtalkGroupPermitResolver, normalizeDingtalkSenderForPermit, platformPermit,
|
|
5
|
+
export { checkDingtalkPlatformPermit, dingtalkGroupPermitResolver, normalizeDingtalkSenderForPermit, platformPermit, } from './platform-permit.js';
|
package/lib/platform-permit.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 钉钉 DingTalk 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 dingtalkGroupPermitResolver(logicalPerm: string): string;
|
|
7
7
|
export declare function normalizeDingtalkSenderForPermit(input: {
|
|
@@ -11,5 +11,4 @@ export declare function normalizeDingtalkSenderForPermit(input: {
|
|
|
11
11
|
role?: string;
|
|
12
12
|
permissions?: string[];
|
|
13
13
|
};
|
|
14
|
-
export declare function checkDingtalkPlatformPermit(perm: string,
|
|
15
|
-
export declare function registerDingtalkPlatformPermitChecker(): () => void;
|
|
14
|
+
export declare function checkDingtalkPlatformPermit(perm: string, subject: PermissionSubject): boolean;
|
package/lib/platform-permit.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 钉钉 DingTalk platform permit
|
|
3
|
-
*/
|
|
4
|
-
import { registerPlatformPermitChecker } from '@zhin.js/core';
|
|
5
1
|
const ADAPTER = 'dingtalk';
|
|
6
2
|
export function platformPermit(perm) {
|
|
7
3
|
return `platform(${ADAPTER},${perm})`;
|
|
@@ -22,10 +18,9 @@ export function normalizeDingtalkSenderForPermit(input) {
|
|
|
22
18
|
}
|
|
23
19
|
return { role: 'member', permissions: [] };
|
|
24
20
|
}
|
|
25
|
-
export function checkDingtalkPlatformPermit(perm,
|
|
26
|
-
const
|
|
27
|
-
const permissions = sender
|
|
28
|
-
const role = sender.role;
|
|
21
|
+
export function checkDingtalkPlatformPermit(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':
|
|
@@ -36,6 +31,3 @@ export function checkDingtalkPlatformPermit(perm, message) {
|
|
|
36
31
|
return false;
|
|
37
32
|
}
|
|
38
33
|
}
|
|
39
|
-
export function registerDingtalkPlatformPermitChecker() {
|
|
40
|
-
return registerPlatformPermitChecker(ADAPTER, checkDingtalkPlatformPermit);
|
|
41
|
-
}
|
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 DingTalkAdapterConfig {
|
|
8
|
-
readonly
|
|
9
|
+
readonly id?: string;
|
|
9
10
|
readonly appKey?: string;
|
|
10
11
|
readonly appSecret?: string;
|
|
11
12
|
readonly webhookPath?: string;
|
|
@@ -18,7 +19,7 @@ export interface DingTalkAdapterConfig {
|
|
|
18
19
|
}
|
|
19
20
|
export interface ResolvedDingTalkConfig {
|
|
20
21
|
readonly context: 'dingtalk';
|
|
21
|
-
readonly
|
|
22
|
+
readonly id: string;
|
|
22
23
|
readonly appKey: string;
|
|
23
24
|
readonly appSecret: string;
|
|
24
25
|
readonly webhookPath: string;
|
|
@@ -104,7 +105,12 @@ export interface DingTalkSendBody {
|
|
|
104
105
|
export declare function resolveDingTalkConfig(config?: DingTalkAdapterConfig): ResolvedDingTalkConfig;
|
|
105
106
|
export declare function normalizeWebhookPath(path: string): string;
|
|
106
107
|
export declare function resolveChatType(conversationType?: string): 'group' | 'private';
|
|
107
|
-
|
|
108
|
+
/**
|
|
109
|
+
* 入站归一化 → ConversationRef:`conversationType: '2'` 为群会话(kind 'group'),
|
|
110
|
+
* 其余为单聊(kind 'private');会话原生 id 取 conversationId,缺省回退 senderId。
|
|
111
|
+
* 钉钉无 guild/频道容器概念,不产生 parent。
|
|
112
|
+
*/
|
|
113
|
+
export declare function dingtalkInboundConversation(endpointKey: string, msg: DingTalkMessage): ConversationRef;
|
|
108
114
|
export declare function resolveSender(msg: DingTalkMessage): string;
|
|
109
115
|
export declare function generateMessageId(msg: DingTalkMessage): string;
|
|
110
116
|
/** Build inbound text for MessageGateway.receive. */
|
package/lib/protocol.js
CHANGED
|
@@ -29,8 +29,8 @@ export function resolveDingTalkConfig(config = {}) {
|
|
|
29
29
|
if (!appKey || !appSecret) {
|
|
30
30
|
throw new TypeError('DingTalk adapter requires appKey + appSecret (plugins.<key> or endpoints with context: dingtalk)');
|
|
31
31
|
}
|
|
32
|
-
const
|
|
33
|
-
|| (typeof entry?.
|
|
32
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
33
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
34
34
|
|| process.env.DINGTALK_BOT_NAME
|
|
35
35
|
|| 'dingtalk-bot';
|
|
36
36
|
const webhookPath = normalizeWebhookPath(config.webhookPath ?? entry?.webhookPath ?? '/dingtalk/webhook');
|
|
@@ -38,7 +38,7 @@ export function resolveDingTalkConfig(config = {}) {
|
|
|
38
38
|
const robotCode = config.robotCode ?? entry?.robotCode;
|
|
39
39
|
return {
|
|
40
40
|
context: 'dingtalk',
|
|
41
|
-
|
|
41
|
+
id,
|
|
42
42
|
appKey,
|
|
43
43
|
appSecret,
|
|
44
44
|
webhookPath,
|
|
@@ -53,8 +53,17 @@ export function normalizeWebhookPath(path) {
|
|
|
53
53
|
export function resolveChatType(conversationType) {
|
|
54
54
|
return conversationType === '2' ? 'group' : 'private';
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
/**
|
|
57
|
+
* 入站归一化 → ConversationRef:`conversationType: '2'` 为群会话(kind 'group'),
|
|
58
|
+
* 其余为单聊(kind 'private');会话原生 id 取 conversationId,缺省回退 senderId。
|
|
59
|
+
* 钉钉无 guild/频道容器概念,不产生 parent。
|
|
60
|
+
*/
|
|
61
|
+
export function dingtalkInboundConversation(endpointKey, msg) {
|
|
62
|
+
return {
|
|
63
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
64
|
+
kind: resolveChatType(msg.conversationType),
|
|
65
|
+
id: msg.conversationId || msg.senderId || 'unknown',
|
|
66
|
+
};
|
|
58
67
|
}
|
|
59
68
|
export function resolveSender(msg) {
|
|
60
69
|
return msg.senderId || msg.senderStaffId || 'unknown';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-dingtalk",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.5",
|
|
4
4
|
"description": "Zhin.js DingTalk (钉钉) adapter for Plugin Runtime (HTTP webhook)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -33,21 +33,23 @@
|
|
|
33
33
|
"directory": "plugins/adapters/dingtalk"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@zhin.js/adapter": "1.1.
|
|
37
|
-
"@zhin.js/command": "1.0.
|
|
38
|
-
"@zhin.js/core": "1.5.
|
|
39
|
-
"@zhin.js/host-http": "1.0.
|
|
40
|
-
"@zhin.js/
|
|
41
|
-
"@zhin.js/
|
|
36
|
+
"@zhin.js/adapter": "1.1.7",
|
|
37
|
+
"@zhin.js/command": "1.0.9",
|
|
38
|
+
"@zhin.js/core": "1.5.4",
|
|
39
|
+
"@zhin.js/host-http": "1.0.8",
|
|
40
|
+
"@zhin.js/im-contract": "1.0.3",
|
|
41
|
+
"@zhin.js/logger": "1.0.76",
|
|
42
|
+
"@zhin.js/plugin-runtime": "1.1.5"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"zod": "^4.0.0",
|
|
45
|
-
"@zhin.js/adapter": "1.1.
|
|
46
|
-
"@zhin.js/agent": "1.1.
|
|
47
|
-
"@zhin.js/core": "1.5.
|
|
48
|
-
"@zhin.js/host-http": "1.0.
|
|
49
|
-
"@zhin.js/
|
|
50
|
-
"zhin.js": "
|
|
46
|
+
"@zhin.js/adapter": "1.1.7",
|
|
47
|
+
"@zhin.js/agent": "1.1.5",
|
|
48
|
+
"@zhin.js/core": "1.5.4",
|
|
49
|
+
"@zhin.js/host-http": "1.0.8",
|
|
50
|
+
"@zhin.js/permission": "1.0.1",
|
|
51
|
+
"@zhin.js/plugin-runtime": "1.1.5",
|
|
52
|
+
"zhin.js": "6.0.4"
|
|
51
53
|
},
|
|
52
54
|
"peerDependenciesMeta": {
|
|
53
55
|
"zhin.js": {
|
|
@@ -65,8 +67,8 @@
|
|
|
65
67
|
"typescript": "^6.0.3",
|
|
66
68
|
"vitest": "^4.1.10",
|
|
67
69
|
"zod": "^4.4.3",
|
|
68
|
-
"@zhin.js/agent": "1.1.
|
|
69
|
-
"zhin.js": "6.0.
|
|
70
|
+
"@zhin.js/agent": "1.1.5",
|
|
71
|
+
"zhin.js": "6.0.4"
|
|
70
72
|
},
|
|
71
73
|
"files": [
|
|
72
74
|
"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 { checkDingtalkPlatformPermit } from "./lib/platform-permit.js";
|
|
5
6
|
import { dingtalkRuntimeStateToken } from "./lib/dingtalk-runtime-state.js";
|
|
6
7
|
export default definePlugin({
|
|
7
8
|
name: 'dingtalk',
|
|
@@ -9,8 +10,10 @@ export default definePlugin({
|
|
|
9
10
|
displayName: 'DingTalk (钉钉) Adapter',
|
|
10
11
|
},
|
|
11
12
|
setup(context) {
|
|
12
|
-
// 运行中 endpoint 注册表(dingtalk.endpoint list 的"运行中"数据源)
|
|
13
13
|
context.resources.provide(dingtalkRuntimeStateToken, createEndpointRuntimeState());
|
|
14
|
-
|
|
14
|
+
if (context.resources.has(permissionHostToken)) {
|
|
15
|
+
const host = context.resources.use(permissionHostToken);
|
|
16
|
+
return host.registerPlatform('dingtalk', checkDingtalkPlatformPermit);
|
|
17
|
+
}
|
|
15
18
|
},
|
|
16
19
|
});
|
package/schema.json
CHANGED
|
@@ -7,16 +7,48 @@
|
|
|
7
7
|
"type": "string",
|
|
8
8
|
"default": "https://oapi.dingtalk.com"
|
|
9
9
|
},
|
|
10
|
+
"master": {
|
|
11
|
+
"type": [
|
|
12
|
+
"string",
|
|
13
|
+
"number"
|
|
14
|
+
],
|
|
15
|
+
"description": "框架 master(DingTalk userid;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
|
|
16
|
+
},
|
|
17
|
+
"trusted": {
|
|
18
|
+
"type": "array",
|
|
19
|
+
"items": {
|
|
20
|
+
"type": [
|
|
21
|
+
"string",
|
|
22
|
+
"number"
|
|
23
|
+
],
|
|
24
|
+
"description": "Trusted DingTalk userid"
|
|
25
|
+
},
|
|
26
|
+
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
|
|
27
|
+
},
|
|
10
28
|
"endpoints": {
|
|
11
29
|
"type": "array",
|
|
12
|
-
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(
|
|
30
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
13
31
|
"items": {
|
|
14
32
|
"type": "object",
|
|
15
33
|
"additionalProperties": true,
|
|
16
34
|
"properties": {
|
|
17
|
-
"
|
|
18
|
-
"type":
|
|
19
|
-
|
|
35
|
+
"master": {
|
|
36
|
+
"type": [
|
|
37
|
+
"string",
|
|
38
|
+
"number"
|
|
39
|
+
],
|
|
40
|
+
"description": "本 endpoint 的框架 master(DingTalk userid);覆盖顶层 master"
|
|
41
|
+
},
|
|
42
|
+
"trusted": {
|
|
43
|
+
"type": "array",
|
|
44
|
+
"items": {
|
|
45
|
+
"type": [
|
|
46
|
+
"string",
|
|
47
|
+
"number"
|
|
48
|
+
],
|
|
49
|
+
"description": "Trusted DingTalk userid"
|
|
50
|
+
},
|
|
51
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
20
52
|
},
|
|
21
53
|
"appKey": {
|
|
22
54
|
"type": "string",
|
|
@@ -33,10 +65,14 @@
|
|
|
33
65
|
"robotCode": {
|
|
34
66
|
"type": "string",
|
|
35
67
|
"description": "Dingtalk robot code"
|
|
68
|
+
},
|
|
69
|
+
"id": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"description": "Dingtalk bot name"
|
|
36
72
|
}
|
|
37
73
|
},
|
|
38
74
|
"required": [
|
|
39
|
-
"
|
|
75
|
+
"id",
|
|
40
76
|
"appKey",
|
|
41
77
|
"appSecret",
|
|
42
78
|
"webhookPath",
|
|
@@ -23,20 +23,20 @@ export interface DingtalkAgentEndpoint {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export interface DingtalkAgentDeps {
|
|
26
|
-
getEndpoint: (
|
|
26
|
+
getEndpoint: (endpointKey: string) => DingtalkAgentEndpoint;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const endpoints = new Map<string, DingtalkAgentEndpoint>();
|
|
30
30
|
let override: DingtalkAgentDeps | null = null;
|
|
31
31
|
|
|
32
32
|
export function registerDingtalkAgentEndpoint(
|
|
33
|
-
|
|
33
|
+
endpointKey: string,
|
|
34
34
|
endpoint: DingtalkAgentEndpoint,
|
|
35
35
|
): () => void {
|
|
36
|
-
endpoints.set(
|
|
36
|
+
endpoints.set(endpointKey, endpoint);
|
|
37
37
|
return () => {
|
|
38
|
-
if (endpoints.get(
|
|
39
|
-
endpoints.delete(
|
|
38
|
+
if (endpoints.get(endpointKey) === endpoint) {
|
|
39
|
+
endpoints.delete(endpointKey);
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
42
|
}
|
|
@@ -49,9 +49,9 @@ export function setDingtalkAgentDeps(deps: DingtalkAgentDeps | null): void {
|
|
|
49
49
|
export function getDingtalkAgentDeps(): DingtalkAgentDeps {
|
|
50
50
|
if (override) return override;
|
|
51
51
|
return {
|
|
52
|
-
getEndpoint(
|
|
53
|
-
const endpoint = endpoints.get(
|
|
54
|
-
if (!endpoint) throw new Error(`Endpoint ${
|
|
52
|
+
getEndpoint(endpointKey) {
|
|
53
|
+
const endpoint = endpoints.get(endpointKey);
|
|
54
|
+
if (!endpoint) throw new Error(`Endpoint ${endpointKey} 不存在`);
|
|
55
55
|
return endpoint;
|
|
56
56
|
},
|
|
57
57
|
};
|
package/src/endpoint.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DingTalkEndpoint — lifecycle, outbound, admit, OpenAPI helpers for agent tools.
|
|
3
3
|
*/
|
|
4
|
-
import type { EndpointInstance } from '@zhin.js/adapter';
|
|
4
|
+
import type { EndpointInstance, EndpointSendRequest } from '@zhin.js/adapter';
|
|
5
5
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
6
6
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
7
|
-
import { formatCompact,
|
|
7
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
8
8
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
9
9
|
import { registerDingtalkAgentEndpoint } from './dingtalk-agent-deps.js';
|
|
10
10
|
import { normalizeDingtalkSenderForPermit } from './platform-permit.js';
|
|
11
11
|
import {
|
|
12
|
+
dingtalkInboundConversation,
|
|
12
13
|
formatInboundContent,
|
|
13
14
|
formatOutboundBody,
|
|
14
15
|
generateMessageId,
|
|
15
16
|
isDingtalkBotMentioned,
|
|
16
17
|
resolveChatType,
|
|
17
18
|
resolveSender,
|
|
18
|
-
resolveTarget,
|
|
19
19
|
type AccessToken,
|
|
20
20
|
type DingTalkApiResponse,
|
|
21
21
|
type DingTalkEvent,
|
|
@@ -25,8 +25,6 @@ import {
|
|
|
25
25
|
} from './protocol.js';
|
|
26
26
|
import { registerDingTalkWebhookRoutes } from './webhook.js';
|
|
27
27
|
|
|
28
|
-
const logger = getLogger('dingtalk');
|
|
29
|
-
|
|
30
28
|
export type DingTalkFetch = (
|
|
31
29
|
url: string,
|
|
32
30
|
init?: {
|
|
@@ -55,6 +53,8 @@ export interface DingTalkEndpointOptions {
|
|
|
55
53
|
* 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
|
|
56
54
|
*/
|
|
57
55
|
export class DingTalkEndpoint implements EndpointInstance {
|
|
56
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
57
|
+
|
|
58
58
|
readonly #options: DingTalkEndpointOptions;
|
|
59
59
|
readonly #fetch: DingTalkFetch;
|
|
60
60
|
#routeReleases: HttpRouteRegistration[] = [];
|
|
@@ -66,6 +66,7 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
66
66
|
#unregisterAgent?: () => void;
|
|
67
67
|
|
|
68
68
|
constructor(options: DingTalkEndpointOptions) {
|
|
69
|
+
this.#logger = getAdapterLogger('dingtalk', options.config.id);
|
|
69
70
|
this.#options = options;
|
|
70
71
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
71
72
|
}
|
|
@@ -84,16 +85,16 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
84
85
|
this.#started = true;
|
|
85
86
|
try {
|
|
86
87
|
await this.#refreshAccessToken();
|
|
87
|
-
this.#unregisterAgent = registerDingtalkAgentEndpoint(this.#options.config.
|
|
88
|
+
this.#unregisterAgent = registerDingtalkAgentEndpoint(this.#options.config.id, this);
|
|
88
89
|
this.#routeReleases.push(...registerDingTalkWebhookRoutes(this.#options.http, this));
|
|
89
|
-
logger.debug(formatCompact({
|
|
90
|
-
endpoint: this.#options.config.
|
|
90
|
+
this.#logger.debug(formatCompact({
|
|
91
|
+
endpoint: this.#options.config.id,
|
|
91
92
|
op: 'webhook',
|
|
92
93
|
path: this.#options.config.webhookPath,
|
|
93
94
|
}));
|
|
94
95
|
} catch (error) {
|
|
95
96
|
await this.stop();
|
|
96
|
-
logger.error('Failed to connect DingTalk endpoint:', error);
|
|
97
|
+
this.#logger.error('Failed to connect DingTalk endpoint:', error);
|
|
97
98
|
throw error;
|
|
98
99
|
}
|
|
99
100
|
}
|
|
@@ -113,12 +114,12 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
113
114
|
this.#unregisterAgent?.();
|
|
114
115
|
this.#unregisterAgent = undefined;
|
|
115
116
|
this.#started = false;
|
|
116
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
117
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
async send({
|
|
120
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
120
121
|
const content = formatOutboundBody(payload);
|
|
121
|
-
const sessionWebhook = this.#sessionWebhooks.get(
|
|
122
|
+
const sessionWebhook = this.#sessionWebhooks.get(conversation.id);
|
|
122
123
|
if (sessionWebhook) {
|
|
123
124
|
const response = await this.#fetch(sessionWebhook, {
|
|
124
125
|
method: 'POST',
|
|
@@ -129,11 +130,11 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
129
130
|
if (data.errcode !== 0) {
|
|
130
131
|
throw new Error(`Failed to send message via session webhook: ${data.errmsg}`);
|
|
131
132
|
}
|
|
132
|
-
logger.debug(formatCompact({
|
|
133
|
+
this.#logger.debug(formatCompact({
|
|
133
134
|
op: 'send',
|
|
134
|
-
endpoint: this.#options.config.
|
|
135
|
+
endpoint: this.#options.config.id,
|
|
135
136
|
via: 'sessionWebhook',
|
|
136
|
-
to:
|
|
137
|
+
to: conversation.id,
|
|
137
138
|
}));
|
|
138
139
|
return (data.msgId as string) || `${Date.now()}`;
|
|
139
140
|
}
|
|
@@ -151,7 +152,7 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
151
152
|
if (data.errcode !== 0) {
|
|
152
153
|
throw new Error(`Failed to send message: ${data.errmsg}`);
|
|
153
154
|
}
|
|
154
|
-
logger.debug(formatCompact({ op: 'send',
|
|
155
|
+
this.#logger.debug(formatCompact({ op: 'send', to: conversation.id }));
|
|
155
156
|
return (data.msgId as string) || `${Date.now()}`;
|
|
156
157
|
}
|
|
157
158
|
|
|
@@ -161,29 +162,32 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
161
162
|
if (event.sessionWebhook && event.conversationId) {
|
|
162
163
|
this.#sessionWebhooks.set(event.conversationId, event.sessionWebhook);
|
|
163
164
|
}
|
|
164
|
-
const
|
|
165
|
+
const conversation = dingtalkInboundConversation(String(this.#options.id), event);
|
|
165
166
|
const chatType = resolveChatType(event.conversationType);
|
|
166
167
|
const permit = normalizeDingtalkSenderForPermit({ isAdmin: event.isAdmin === true });
|
|
167
168
|
void this.#options.gateway.receive({
|
|
168
|
-
|
|
169
|
-
|
|
169
|
+
conversation,
|
|
170
|
+
message: { conversation, id: generateMessageId(event) },
|
|
170
171
|
content: formatInboundContent(event),
|
|
171
|
-
sender:
|
|
172
|
-
|
|
172
|
+
sender: {
|
|
173
|
+
id: resolveSender(event),
|
|
174
|
+
name: event.senderNick || undefined,
|
|
175
|
+
...(permit.role ? { roles: [permit.role] } : {}),
|
|
176
|
+
},
|
|
177
|
+
endpointId: this.#options.config.id,
|
|
178
|
+
...(isDingtalkBotMentioned(event, this.#options.config.robotCode) ? { mentioned: true } : {}),
|
|
173
179
|
metadata: Object.freeze({
|
|
174
180
|
msgtype: event.msgtype,
|
|
175
181
|
chatType,
|
|
176
|
-
endpoint: this.#options.config.name,
|
|
177
182
|
senderNick: event.senderNick,
|
|
178
183
|
role: permit.role,
|
|
179
184
|
permissions: permit.permissions,
|
|
180
185
|
conversationType: event.conversationType,
|
|
181
|
-
...(isDingtalkBotMentioned(event, this.#options.config.robotCode) ? { mentioned: true } : {}),
|
|
182
186
|
}),
|
|
183
187
|
}).catch((err) => {
|
|
184
|
-
logger.warn(formatCompact({
|
|
188
|
+
this.#logger.warn(formatCompact({
|
|
185
189
|
op: 'dingtalk_gateway_receive_failed',
|
|
186
|
-
|
|
190
|
+
conversationId: conversation.id,
|
|
187
191
|
error: err instanceof Error ? err.message : String(err),
|
|
188
192
|
}));
|
|
189
193
|
});
|
|
@@ -198,7 +202,7 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
198
202
|
if (data.errcode === 0) return data.result;
|
|
199
203
|
throw new Error(`Failed to get user info: ${data.errmsg}`);
|
|
200
204
|
} catch (error) {
|
|
201
|
-
logger.error('Failed to get user info:', error);
|
|
205
|
+
this.#logger.error('Failed to get user info:', error);
|
|
202
206
|
return null;
|
|
203
207
|
}
|
|
204
208
|
}
|
|
@@ -215,7 +219,7 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
215
219
|
}
|
|
216
220
|
throw new Error(`Failed to get department users: ${data.errmsg}`);
|
|
217
221
|
} catch (error) {
|
|
218
|
-
logger.error('Failed to get department users:', error);
|
|
222
|
+
this.#logger.error('Failed to get department users:', error);
|
|
219
223
|
return [];
|
|
220
224
|
}
|
|
221
225
|
}
|
|
@@ -233,7 +237,7 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
233
237
|
if (data.errcode === 0) return true;
|
|
234
238
|
throw new Error(`Failed to send work notice: ${data.errmsg}`);
|
|
235
239
|
} catch (error) {
|
|
236
|
-
logger.error('Failed to send work notice:', error);
|
|
240
|
+
this.#logger.error('Failed to send work notice:', error);
|
|
237
241
|
return false;
|
|
238
242
|
}
|
|
239
243
|
}
|
|
@@ -247,7 +251,7 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
247
251
|
if (data.errcode === 0) return (data.result as unknown[]) || [];
|
|
248
252
|
throw new Error(`Failed to get department list: ${data.errmsg}`);
|
|
249
253
|
} catch (error) {
|
|
250
|
-
logger.error('Failed to get department list:', error);
|
|
254
|
+
this.#logger.error('Failed to get department list:', error);
|
|
251
255
|
return [];
|
|
252
256
|
}
|
|
253
257
|
}
|
|
@@ -261,7 +265,7 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
261
265
|
if (data.errcode === 0) return data.result;
|
|
262
266
|
throw new Error(`Failed to get department info: ${data.errmsg}`);
|
|
263
267
|
} catch (error) {
|
|
264
|
-
logger.error('Failed to get department info:', error);
|
|
268
|
+
this.#logger.error('Failed to get department info:', error);
|
|
265
269
|
return null;
|
|
266
270
|
}
|
|
267
271
|
}
|
|
@@ -279,7 +283,7 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
279
283
|
if (data.errcode === 0) return (data.chatid as string) || null;
|
|
280
284
|
throw new Error(`Failed to create chat: ${data.errmsg}`);
|
|
281
285
|
} catch (error) {
|
|
282
|
-
logger.error('Failed to create chat:', error);
|
|
286
|
+
this.#logger.error('Failed to create chat:', error);
|
|
283
287
|
return null;
|
|
284
288
|
}
|
|
285
289
|
}
|
|
@@ -293,7 +297,7 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
293
297
|
if (data.errcode === 0) return data.chat_info;
|
|
294
298
|
throw new Error(`Failed to get chat info: ${data.errmsg}`);
|
|
295
299
|
} catch (error) {
|
|
296
|
-
logger.error('Failed to get chat info:', error);
|
|
300
|
+
this.#logger.error('Failed to get chat info:', error);
|
|
297
301
|
return null;
|
|
298
302
|
}
|
|
299
303
|
}
|
|
@@ -315,7 +319,7 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
315
319
|
if (data.errcode === 0) return true;
|
|
316
320
|
throw new Error(`Failed to update chat: ${data.errmsg}`);
|
|
317
321
|
} catch (error) {
|
|
318
|
-
logger.error('Failed to update chat:', error);
|
|
322
|
+
this.#logger.error('Failed to update chat:', error);
|
|
319
323
|
return false;
|
|
320
324
|
}
|
|
321
325
|
}
|
|
@@ -379,7 +383,7 @@ export class DingTalkEndpoint implements EndpointInstance {
|
|
|
379
383
|
expires_in: data.expires_in ?? 7200,
|
|
380
384
|
timestamp: Date.now(),
|
|
381
385
|
};
|
|
382
|
-
logger.debug('Access token refreshed successfully');
|
|
386
|
+
this.#logger.debug('Access token refreshed successfully');
|
|
383
387
|
return;
|
|
384
388
|
}
|
|
385
389
|
throw new Error(`Failed to get access token: ${data.errmsg} (${data.errcode})`);
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export {
|
|
2
|
+
dingtalkInboundConversation,
|
|
2
3
|
formatInboundContent,
|
|
3
4
|
formatOutboundBody,
|
|
4
5
|
generateMessageId,
|
|
@@ -8,7 +9,6 @@ export {
|
|
|
8
9
|
resolveChatType,
|
|
9
10
|
resolveDingTalkConfig,
|
|
10
11
|
resolveSender,
|
|
11
|
-
resolveTarget,
|
|
12
12
|
verifySignature,
|
|
13
13
|
type AccessToken,
|
|
14
14
|
type DingTalkAdapterConfig,
|
|
@@ -45,5 +45,4 @@ export {
|
|
|
45
45
|
dingtalkGroupPermitResolver,
|
|
46
46
|
normalizeDingtalkSenderForPermit,
|
|
47
47
|
platformPermit,
|
|
48
|
-
registerDingtalkPlatformPermitChecker,
|
|
49
48
|
} from './platform-permit.js';
|
package/src/platform-permit.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 钉钉 DingTalk platform permit
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
|
|
6
6
|
const ADAPTER = 'dingtalk';
|
|
7
7
|
|
|
@@ -31,10 +31,9 @@ export function normalizeDingtalkSenderForPermit(input: {
|
|
|
31
31
|
return { role: 'member', permissions: [] };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
export function checkDingtalkPlatformPermit(perm: string,
|
|
35
|
-
const
|
|
36
|
-
const permissions = sender
|
|
37
|
-
const role = sender.role;
|
|
34
|
+
export function checkDingtalkPlatformPermit(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) {
|
|
@@ -47,6 +46,3 @@ export function checkDingtalkPlatformPermit(perm: string, message: Message<any>)
|
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
export function registerDingtalkPlatformPermitChecker(): () => void {
|
|
51
|
-
return registerPlatformPermitChecker(ADAPTER, checkDingtalkPlatformPermit);
|
|
52
|
-
}
|
package/src/protocol.ts
CHANGED
|
@@ -7,13 +7,14 @@ import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
|
7
7
|
import type { IncomingMessage } from 'node:http';
|
|
8
8
|
|
|
9
9
|
import { isMediaRef } from '@zhin.js/core';
|
|
10
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
10
11
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
11
12
|
|
|
12
13
|
const logger = getLogger('dingtalk');
|
|
13
14
|
|
|
14
15
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
15
16
|
export interface DingTalkAdapterConfig {
|
|
16
|
-
readonly
|
|
17
|
+
readonly id?: string;
|
|
17
18
|
readonly appKey?: string;
|
|
18
19
|
readonly appSecret?: string;
|
|
19
20
|
readonly webhookPath?: string;
|
|
@@ -27,7 +28,7 @@ export interface DingTalkAdapterConfig {
|
|
|
27
28
|
|
|
28
29
|
export interface ResolvedDingTalkConfig {
|
|
29
30
|
readonly context: 'dingtalk';
|
|
30
|
-
readonly
|
|
31
|
+
readonly id: string;
|
|
31
32
|
readonly appKey: string;
|
|
32
33
|
readonly appSecret: string;
|
|
33
34
|
readonly webhookPath: string;
|
|
@@ -123,8 +124,8 @@ export function resolveDingTalkConfig(config: DingTalkAdapterConfig = {}): Resol
|
|
|
123
124
|
'DingTalk adapter requires appKey + appSecret (plugins.<key> or endpoints with context: dingtalk)',
|
|
124
125
|
);
|
|
125
126
|
}
|
|
126
|
-
const
|
|
127
|
-
|| (typeof entry?.
|
|
127
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
128
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
128
129
|
|| process.env.DINGTALK_BOT_NAME
|
|
129
130
|
|| 'dingtalk-bot';
|
|
130
131
|
const webhookPath = normalizeWebhookPath(
|
|
@@ -136,7 +137,7 @@ export function resolveDingTalkConfig(config: DingTalkAdapterConfig = {}): Resol
|
|
|
136
137
|
const robotCode = config.robotCode ?? entry?.robotCode;
|
|
137
138
|
return {
|
|
138
139
|
context: 'dingtalk',
|
|
139
|
-
|
|
140
|
+
id,
|
|
140
141
|
appKey,
|
|
141
142
|
appSecret,
|
|
142
143
|
webhookPath,
|
|
@@ -154,8 +155,17 @@ export function resolveChatType(conversationType?: string): 'group' | 'private'
|
|
|
154
155
|
return conversationType === '2' ? 'group' : 'private';
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
|
|
158
|
+
/**
|
|
159
|
+
* 入站归一化 → ConversationRef:`conversationType: '2'` 为群会话(kind 'group'),
|
|
160
|
+
* 其余为单聊(kind 'private');会话原生 id 取 conversationId,缺省回退 senderId。
|
|
161
|
+
* 钉钉无 guild/频道容器概念,不产生 parent。
|
|
162
|
+
*/
|
|
163
|
+
export function dingtalkInboundConversation(endpointKey: string, msg: DingTalkMessage): ConversationRef {
|
|
164
|
+
return {
|
|
165
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
166
|
+
kind: resolveChatType(msg.conversationType),
|
|
167
|
+
id: msg.conversationId || msg.senderId || 'unknown',
|
|
168
|
+
};
|
|
159
169
|
}
|
|
160
170
|
|
|
161
171
|
export function resolveSender(msg: DingTalkMessage): string {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|