@zhin.js/adapter-dingtalk 1.0.81 → 1.1.2
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 +709 -3
- package/PERMITS.md +19 -0
- package/README.md +67 -333
- package/adapters/dingtalk/index.js +33 -0
- package/adapters/dingtalk/index.ts +38 -0
- package/agents/dingtalk/agent.json +20 -0
- package/agents/dingtalk/boundaries.md +3 -0
- package/agents/dingtalk/conventions.md +3 -0
- package/agents/dingtalk/skills/dingtalk/SKILL.md +26 -0
- package/agents/dingtalk/skills/dingtalk/tools/add_chat_members/index.js +19 -0
- package/agents/dingtalk/skills/dingtalk/tools/add_chat_members/index.ts +20 -0
- package/agents/dingtalk/skills/dingtalk/tools/create_chat/index.js +20 -0
- package/agents/dingtalk/skills/dingtalk/tools/create_chat/index.ts +21 -0
- package/agents/dingtalk/skills/dingtalk/tools/dept_info/index.js +15 -0
- package/agents/dingtalk/skills/dingtalk/tools/dept_info/index.ts +15 -0
- package/agents/dingtalk/skills/dingtalk/tools/get_dept_users/index.js +16 -0
- package/agents/dingtalk/skills/dingtalk/tools/get_dept_users/index.ts +16 -0
- package/agents/dingtalk/skills/dingtalk/tools/get_user/index.js +15 -0
- package/agents/dingtalk/skills/dingtalk/tools/get_user/index.ts +15 -0
- package/agents/dingtalk/skills/dingtalk/tools/list_departments/index.js +16 -0
- package/agents/dingtalk/skills/dingtalk/tools/list_departments/index.ts +16 -0
- package/agents/dingtalk/skills/dingtalk/tools/send_work_notice/index.js +18 -0
- package/agents/dingtalk/skills/dingtalk/tools/send_work_notice/index.ts +18 -0
- package/agents/dingtalk/skills/dingtalk/tools/update_chat/index.js +29 -0
- package/agents/dingtalk/skills/dingtalk/tools/update_chat/index.ts +25 -0
- package/agents/dingtalk/system.md +3 -0
- package/commands/dingtalk/endpoint/add/[id]/index.js +3 -0
- package/commands/dingtalk/endpoint/add/[id]/index.ts +3 -0
- package/commands/dingtalk/endpoint/definition.js +19 -0
- package/commands/dingtalk/endpoint/definition.ts +19 -0
- package/commands/dingtalk/endpoint/list/index.js +3 -0
- package/commands/dingtalk/endpoint/list/index.ts +3 -0
- package/commands/dingtalk/endpoint/remove/[id]/index.js +3 -0
- package/commands/dingtalk/endpoint/remove/[id]/index.ts +3 -0
- package/lib/client.d.ts +12 -0
- package/lib/client.js +2 -0
- package/lib/dingtalk-runtime-state.d.ts +1 -0
- package/lib/dingtalk-runtime-state.js +6 -0
- package/lib/endpoint.d.ts +72 -0
- package/lib/endpoint.js +353 -0
- package/lib/index.d.ts +5 -15
- package/lib/index.js +5 -214
- package/lib/platform-permit.d.ts +14 -0
- package/lib/platform-permit.js +33 -0
- package/lib/protocol.d.ts +130 -0
- package/lib/protocol.js +264 -0
- package/lib/webhook.d.ts +13 -0
- package/lib/webhook.js +47 -0
- package/package.json +76 -19
- package/plugin.js +19 -0
- package/schema.json +97 -0
- package/src/client.ts +16 -0
- package/src/dingtalk-runtime-state.ts +7 -0
- package/src/endpoint.ts +439 -0
- package/src/index.ts +44 -228
- package/src/platform-permit.ts +48 -0
- package/src/protocol.ts +377 -0
- package/src/webhook.ts +75 -0
- package/lib/adapter.d.ts +0 -16
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -37
- package/lib/adapter.js.map +0 -1
- package/lib/bot.d.ts +0 -46
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js +0 -539
- package/lib/bot.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/types.d.ts +0 -58
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -5
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/skills/dingtalk/SKILL.md +0 -91
- package/src/adapter.ts +0 -44
- package/src/bot.ts +0 -590
- package/src/types.ts +0 -56
package/lib/endpoint.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import { Endpoint } from 'zhin.js/adapter';
|
|
2
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
3
|
+
import { normalizeDingtalkSenderForPermit } from './platform-permit.js';
|
|
4
|
+
import { dingtalkInboundConversation, formatInboundContent, formatOutboundBody, generateMessageId, isDingtalkBotMentioned, resolveChatType, resolveSender, } from './protocol.js';
|
|
5
|
+
import { registerDingTalkWebhookRoutes } from './webhook.js';
|
|
6
|
+
/** SDK-like DingTalk OpenAPI surface available on every Endpoint event. */
|
|
7
|
+
export class DingTalkClient {
|
|
8
|
+
api;
|
|
9
|
+
constructor(api) {
|
|
10
|
+
this.api = api;
|
|
11
|
+
}
|
|
12
|
+
getUserInfo = (userId) => this.api.getUserInfo(userId);
|
|
13
|
+
getDepartmentUsers = (deptId) => this.api.getDepartmentUsers(deptId);
|
|
14
|
+
sendWorkNotice = (userIds, content) => this.api.sendWorkNotice(userIds, content);
|
|
15
|
+
getDepartmentList = (deptId) => this.api.getDepartmentList(deptId);
|
|
16
|
+
getDepartmentInfo = (deptId) => this.api.getDepartmentInfo(deptId);
|
|
17
|
+
createChat = (name, owner, users) => this.api.createChat(name, owner, users);
|
|
18
|
+
getChatInfo = (chatId) => this.api.getChatInfo(chatId);
|
|
19
|
+
updateChat = (chatId, options) => this.api.updateChat(chatId, options);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 钉钉机器人(webhook/stream 模式)无常规群列表 API——机器人不持有
|
|
23
|
+
* 「我所在的群」枚举面,仅能收发消息;
|
|
24
|
+
* 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
|
|
25
|
+
*/
|
|
26
|
+
export class DingTalkEndpoint extends Endpoint {
|
|
27
|
+
client = new DingTalkClient({
|
|
28
|
+
getUserInfo: (userId) => this.#getUserInfo(userId),
|
|
29
|
+
getDepartmentUsers: (deptId) => this.#getDepartmentUsers(deptId),
|
|
30
|
+
sendWorkNotice: (userIds, content) => this.#sendWorkNotice(userIds, content),
|
|
31
|
+
getDepartmentList: (deptId) => this.#getDepartmentList(deptId),
|
|
32
|
+
getDepartmentInfo: (deptId) => this.#getDepartmentInfo(deptId),
|
|
33
|
+
createChat: (name, owner, users) => this.#createChat(name, owner, users),
|
|
34
|
+
getChatInfo: (chatId) => this.#getChatInfo(chatId),
|
|
35
|
+
updateChat: (chatId, options) => this.#updateChat(chatId, options),
|
|
36
|
+
});
|
|
37
|
+
#logger;
|
|
38
|
+
#options;
|
|
39
|
+
#fetch;
|
|
40
|
+
#routeReleases = [];
|
|
41
|
+
#accessToken = { token: '', expires_in: 0, timestamp: 0 };
|
|
42
|
+
#refreshPromise = null;
|
|
43
|
+
#sessionWebhooks = new Map();
|
|
44
|
+
#open = false;
|
|
45
|
+
#started = false;
|
|
46
|
+
constructor(options) {
|
|
47
|
+
super();
|
|
48
|
+
this.#logger = getAdapterLogger('dingtalk', options.config.id);
|
|
49
|
+
this.#options = options;
|
|
50
|
+
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
51
|
+
}
|
|
52
|
+
/** Used by webhook handler. */
|
|
53
|
+
get isOpen() {
|
|
54
|
+
return this.#open;
|
|
55
|
+
}
|
|
56
|
+
get config() {
|
|
57
|
+
return this.#options.config;
|
|
58
|
+
}
|
|
59
|
+
async start() {
|
|
60
|
+
if (this.#started)
|
|
61
|
+
return;
|
|
62
|
+
this.#started = true;
|
|
63
|
+
try {
|
|
64
|
+
await this.#refreshAccessToken();
|
|
65
|
+
this.#routeReleases.push(...registerDingTalkWebhookRoutes(this.#options.http, this));
|
|
66
|
+
this.#logger.debug(formatCompact({
|
|
67
|
+
endpoint: this.#options.config.id,
|
|
68
|
+
op: 'webhook',
|
|
69
|
+
path: this.#options.config.webhookPath,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
await this.stop();
|
|
74
|
+
this.#logger.error('Failed to connect DingTalk endpoint:', error);
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
open() {
|
|
79
|
+
this.#open = true;
|
|
80
|
+
}
|
|
81
|
+
close() {
|
|
82
|
+
this.#open = false;
|
|
83
|
+
}
|
|
84
|
+
async stop() {
|
|
85
|
+
this.#open = false;
|
|
86
|
+
this.#sessionWebhooks.clear();
|
|
87
|
+
for (const release of this.#routeReleases.splice(0))
|
|
88
|
+
release();
|
|
89
|
+
this.#started = false;
|
|
90
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
91
|
+
}
|
|
92
|
+
async send({ conversation, payload }) {
|
|
93
|
+
const content = formatOutboundBody(payload);
|
|
94
|
+
const sessionWebhook = this.#sessionWebhooks.get(conversation.id);
|
|
95
|
+
if (sessionWebhook) {
|
|
96
|
+
const response = await this.#fetch(sessionWebhook, {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
99
|
+
body: JSON.stringify(content),
|
|
100
|
+
});
|
|
101
|
+
const data = await response.json();
|
|
102
|
+
if (data.errcode !== 0) {
|
|
103
|
+
throw new Error(`Failed to send message via session webhook: ${data.errmsg}`);
|
|
104
|
+
}
|
|
105
|
+
this.#logger.debug(formatCompact({
|
|
106
|
+
op: 'send',
|
|
107
|
+
endpoint: this.#options.config.id,
|
|
108
|
+
via: 'sessionWebhook',
|
|
109
|
+
to: conversation.id,
|
|
110
|
+
}));
|
|
111
|
+
return data.msgId || `${Date.now()}`;
|
|
112
|
+
}
|
|
113
|
+
const body = {
|
|
114
|
+
...content,
|
|
115
|
+
...(this.#options.config.robotCode
|
|
116
|
+
? { robotCode: this.#options.config.robotCode }
|
|
117
|
+
: {}),
|
|
118
|
+
};
|
|
119
|
+
const data = await this.#request('/robot/send', {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
body: body,
|
|
122
|
+
});
|
|
123
|
+
if (data.errcode !== 0) {
|
|
124
|
+
throw new Error(`Failed to send message: ${data.errmsg}`);
|
|
125
|
+
}
|
|
126
|
+
this.#logger.debug(formatCompact({ op: 'send', to: conversation.id }));
|
|
127
|
+
return data.msgId || `${Date.now()}`;
|
|
128
|
+
}
|
|
129
|
+
/** Test / internal: admit a parsed event when open (non-webhook path). */
|
|
130
|
+
admit(event) {
|
|
131
|
+
if (!this.#open)
|
|
132
|
+
return;
|
|
133
|
+
this.#emitPlatformEvent(event.msgtype || 'event', event);
|
|
134
|
+
if (event.sessionWebhook && event.conversationId) {
|
|
135
|
+
this.#sessionWebhooks.set(event.conversationId, event.sessionWebhook);
|
|
136
|
+
}
|
|
137
|
+
const conversation = dingtalkInboundConversation(String(this.#options.id), event);
|
|
138
|
+
const chatType = resolveChatType(event.conversationType);
|
|
139
|
+
const permit = normalizeDingtalkSenderForPermit({ isAdmin: event.isAdmin === true });
|
|
140
|
+
void this.emit('message.receive', {
|
|
141
|
+
conversation,
|
|
142
|
+
message: { conversation, id: generateMessageId(event) },
|
|
143
|
+
content: formatInboundContent(event),
|
|
144
|
+
sender: {
|
|
145
|
+
id: resolveSender(event),
|
|
146
|
+
name: event.senderNick || undefined,
|
|
147
|
+
...(permit.role ? { roles: [permit.role] } : {}),
|
|
148
|
+
},
|
|
149
|
+
endpointId: this.#options.config.id,
|
|
150
|
+
...(isDingtalkBotMentioned(event, this.#options.config.robotCode) ? { mentioned: true } : {}),
|
|
151
|
+
metadata: Object.freeze({
|
|
152
|
+
msgtype: event.msgtype,
|
|
153
|
+
chatType,
|
|
154
|
+
senderNick: event.senderNick,
|
|
155
|
+
role: permit.role,
|
|
156
|
+
permissions: permit.permissions,
|
|
157
|
+
conversationType: event.conversationType,
|
|
158
|
+
}),
|
|
159
|
+
}).catch((err) => {
|
|
160
|
+
this.#logger.warn(formatCompact({
|
|
161
|
+
op: 'dingtalk_gateway_receive_failed',
|
|
162
|
+
conversationId: conversation.id,
|
|
163
|
+
error: err instanceof Error ? err.message : String(err),
|
|
164
|
+
}));
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
#emitPlatformEvent(name, event) {
|
|
168
|
+
void this.emitPlatform(name, event).catch((error) => {
|
|
169
|
+
this.#logger.warn(formatCompact({
|
|
170
|
+
op: 'dingtalk_platform_event_failed',
|
|
171
|
+
event: name,
|
|
172
|
+
error: error instanceof Error ? error.message : String(error),
|
|
173
|
+
}));
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
async #getUserInfo(userId) {
|
|
177
|
+
try {
|
|
178
|
+
const data = await this.#request('/topapi/v2/user/get', {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
body: { userid: userId },
|
|
181
|
+
});
|
|
182
|
+
if (data.errcode === 0)
|
|
183
|
+
return data.result;
|
|
184
|
+
throw new Error(`Failed to get user info: ${data.errmsg}`);
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
this.#logger.error('Failed to get user info:', error);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async #getDepartmentUsers(deptId) {
|
|
192
|
+
try {
|
|
193
|
+
const data = await this.#request('/topapi/user/listid', {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
body: { dept_id: deptId },
|
|
196
|
+
});
|
|
197
|
+
if (data.errcode === 0) {
|
|
198
|
+
const result = data.result;
|
|
199
|
+
return result?.userid_list || [];
|
|
200
|
+
}
|
|
201
|
+
throw new Error(`Failed to get department users: ${data.errmsg}`);
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
this.#logger.error('Failed to get department users:', error);
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async #sendWorkNotice(userIdList, content) {
|
|
209
|
+
try {
|
|
210
|
+
const data = await this.#request('/topapi/message/corpconversation/asyncsend_v2', {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
body: {
|
|
213
|
+
agent_id: this.#options.config.robotCode,
|
|
214
|
+
userid_list: userIdList.join(','),
|
|
215
|
+
msg: content,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
if (data.errcode === 0)
|
|
219
|
+
return true;
|
|
220
|
+
throw new Error(`Failed to send work notice: ${data.errmsg}`);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
this.#logger.error('Failed to send work notice:', error);
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async #getDepartmentList(deptId = 1) {
|
|
228
|
+
try {
|
|
229
|
+
const data = await this.#request('/topapi/v2/department/listsub', {
|
|
230
|
+
method: 'POST',
|
|
231
|
+
body: { dept_id: deptId },
|
|
232
|
+
});
|
|
233
|
+
if (data.errcode === 0)
|
|
234
|
+
return data.result || [];
|
|
235
|
+
throw new Error(`Failed to get department list: ${data.errmsg}`);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
this.#logger.error('Failed to get department list:', error);
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async #getDepartmentInfo(deptId) {
|
|
243
|
+
try {
|
|
244
|
+
const data = await this.#request('/topapi/v2/department/get', {
|
|
245
|
+
method: 'POST',
|
|
246
|
+
body: { dept_id: deptId },
|
|
247
|
+
});
|
|
248
|
+
if (data.errcode === 0)
|
|
249
|
+
return data.result;
|
|
250
|
+
throw new Error(`Failed to get department info: ${data.errmsg}`);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
this.#logger.error('Failed to get department info:', error);
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async #createChat(name, ownerUserId, userIdList) {
|
|
258
|
+
try {
|
|
259
|
+
const data = await this.#request('/topapi/chat/create', {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
body: { name, owner: ownerUserId, useridlist: userIdList },
|
|
262
|
+
});
|
|
263
|
+
if (data.errcode === 0)
|
|
264
|
+
return data.chatid || null;
|
|
265
|
+
throw new Error(`Failed to create chat: ${data.errmsg}`);
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
this.#logger.error('Failed to create chat:', error);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async #getChatInfo(chatId) {
|
|
273
|
+
try {
|
|
274
|
+
const data = await this.#request('/topapi/chat/get', {
|
|
275
|
+
method: 'POST',
|
|
276
|
+
body: { chatid: chatId },
|
|
277
|
+
});
|
|
278
|
+
if (data.errcode === 0)
|
|
279
|
+
return data.chat_info;
|
|
280
|
+
throw new Error(`Failed to get chat info: ${data.errmsg}`);
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
this.#logger.error('Failed to get chat info:', error);
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async #updateChat(chatId, options) {
|
|
288
|
+
try {
|
|
289
|
+
const data = await this.#request('/topapi/chat/update', {
|
|
290
|
+
method: 'POST',
|
|
291
|
+
body: { chatid: chatId, ...options },
|
|
292
|
+
});
|
|
293
|
+
if (data.errcode === 0)
|
|
294
|
+
return true;
|
|
295
|
+
throw new Error(`Failed to update chat: ${data.errmsg}`);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
this.#logger.error('Failed to update chat:', error);
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async #request(path, options = {}) {
|
|
303
|
+
await this.#ensureAccessToken();
|
|
304
|
+
const { method = 'GET', params = {}, body } = options;
|
|
305
|
+
const urlParams = new URLSearchParams({
|
|
306
|
+
...Object.fromEntries(Object.entries(params).map(([key, value]) => [key, String(value)])),
|
|
307
|
+
access_token: this.#accessToken.token,
|
|
308
|
+
});
|
|
309
|
+
const url = `${this.#options.config.apiBaseUrl}${path}?${urlParams.toString()}`;
|
|
310
|
+
const response = await this.#fetch(url, {
|
|
311
|
+
method,
|
|
312
|
+
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
|
313
|
+
body: body && method === 'POST' ? JSON.stringify(body) : undefined,
|
|
314
|
+
});
|
|
315
|
+
if (!response.ok) {
|
|
316
|
+
const text = await response.text().catch(() => '');
|
|
317
|
+
throw new Error(`DingTalk API error ${response.status}: ${text}`);
|
|
318
|
+
}
|
|
319
|
+
return await response.json();
|
|
320
|
+
}
|
|
321
|
+
async #ensureAccessToken() {
|
|
322
|
+
const now = Date.now();
|
|
323
|
+
if (this.#accessToken.token
|
|
324
|
+
&& now < this.#accessToken.timestamp + (this.#accessToken.expires_in - 300) * 1000) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (this.#refreshPromise) {
|
|
328
|
+
await this.#refreshPromise;
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
this.#refreshPromise = this.#refreshAccessToken()
|
|
332
|
+
.then(() => this.#accessToken.token)
|
|
333
|
+
.finally(() => { this.#refreshPromise = null; });
|
|
334
|
+
await this.#refreshPromise;
|
|
335
|
+
}
|
|
336
|
+
async #refreshAccessToken() {
|
|
337
|
+
const { appKey, appSecret, apiBaseUrl } = this.#options.config;
|
|
338
|
+
const params = new URLSearchParams({ appkey: appKey, appsecret: appSecret });
|
|
339
|
+
const url = `${apiBaseUrl}/gettoken?${params.toString()}`;
|
|
340
|
+
const response = await this.#fetch(url);
|
|
341
|
+
const data = await response.json();
|
|
342
|
+
if (data.errcode === 0 && data.access_token) {
|
|
343
|
+
this.#accessToken = {
|
|
344
|
+
token: data.access_token,
|
|
345
|
+
expires_in: data.expires_in ?? 7200,
|
|
346
|
+
timestamp: Date.now(),
|
|
347
|
+
};
|
|
348
|
+
this.#logger.debug('Access token refreshed successfully');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
throw new Error(`Failed to get access token: ${data.errmsg} (${data.errcode})`);
|
|
352
|
+
}
|
|
353
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
interface Adapters {
|
|
9
|
-
dingtalk: DingTalkAdapter;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
export * from "./types.js";
|
|
13
|
-
export { DingTalkBot } from "./bot.js";
|
|
14
|
-
export { DingTalkAdapter } from "./adapter.js";
|
|
15
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1
|
+
export { dingtalkInboundConversation, formatInboundContent, formatOutboundBody, generateMessageId, headerValue, normalizeWebhookPath, readTextBody, resolveChatType, resolveDingTalkConfig, resolveSender, verifySignature, type AccessToken, type DingTalkEndpointConfig, type DingTalkApiResponse, type DingTalkEvent, type DingTalkMessage, type DingTalkSendBody, type DingTalkWireSegment, type ResolvedDingTalkConfig, } from './protocol.js';
|
|
2
|
+
export { DingTalkClient, DingTalkEndpoint, type DingTalkClientApi, type DingTalkEndpointOptions, type DingTalkFetch, } from './endpoint.js';
|
|
3
|
+
export { registerDingTalkWebhookRoutes, handleDingTalkWebhookRequest, type DingTalkWebhookHandler, } from './webhook.js';
|
|
4
|
+
export { dingtalkClient, type DingtalkClientEventMap } from './client.js';
|
|
5
|
+
export { checkDingtalkPlatformPermit, dingtalkGroupPermitResolver, normalizeDingtalkSenderForPermit, platformPermit, } from './platform-permit.js';
|
package/lib/index.js
CHANGED
|
@@ -1,214 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export * from "./types.js";
|
|
7
|
-
export { DingTalkBot } from "./bot.js";
|
|
8
|
-
export { DingTalkAdapter } from "./adapter.js";
|
|
9
|
-
const plugin = usePlugin();
|
|
10
|
-
const { provide, useContext } = plugin;
|
|
11
|
-
useContext("router", (router) => {
|
|
12
|
-
provide({
|
|
13
|
-
name: "dingtalk",
|
|
14
|
-
description: "DingTalk Bot Adapter",
|
|
15
|
-
mounted: async (p) => {
|
|
16
|
-
const adapter = new DingTalkAdapter(p, router);
|
|
17
|
-
await adapter.start();
|
|
18
|
-
return adapter;
|
|
19
|
-
},
|
|
20
|
-
dispose: async (adapter) => {
|
|
21
|
-
await adapter.stop();
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
useContext('tool', 'dingtalk', (toolService, dingtalk) => {
|
|
26
|
-
const groupTools = createGroupManagementTools(dingtalk, 'dingtalk');
|
|
27
|
-
const disposers = groupTools.map(t => toolService.addTool(t, plugin.name));
|
|
28
|
-
disposers.push(toolService.addTool({
|
|
29
|
-
name: 'dingtalk_get_user',
|
|
30
|
-
description: '获取钉钉用户信息',
|
|
31
|
-
parameters: {
|
|
32
|
-
type: 'object',
|
|
33
|
-
properties: {
|
|
34
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
35
|
-
user_id: { type: 'string', description: '用户 ID' },
|
|
36
|
-
},
|
|
37
|
-
required: ['bot', 'user_id'],
|
|
38
|
-
},
|
|
39
|
-
platforms: ['dingtalk'],
|
|
40
|
-
tags: ['dingtalk'],
|
|
41
|
-
execute: async (args) => {
|
|
42
|
-
const bot = dingtalk.bots.get(args.bot);
|
|
43
|
-
if (!bot)
|
|
44
|
-
throw new Error(`Bot ${args.bot} 不存在`);
|
|
45
|
-
return await bot.getUserInfo(args.user_id);
|
|
46
|
-
},
|
|
47
|
-
}, plugin.name));
|
|
48
|
-
disposers.push(toolService.addTool({
|
|
49
|
-
name: 'dingtalk_get_dept_users',
|
|
50
|
-
description: '获取钉钉部门用户列表',
|
|
51
|
-
parameters: {
|
|
52
|
-
type: 'object',
|
|
53
|
-
properties: {
|
|
54
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
55
|
-
dept_id: { type: 'string', description: '部门 ID' },
|
|
56
|
-
},
|
|
57
|
-
required: ['bot', 'dept_id'],
|
|
58
|
-
},
|
|
59
|
-
platforms: ['dingtalk'],
|
|
60
|
-
tags: ['dingtalk'],
|
|
61
|
-
execute: async (args) => {
|
|
62
|
-
const bot = dingtalk.bots.get(args.bot);
|
|
63
|
-
if (!bot)
|
|
64
|
-
throw new Error(`Bot ${args.bot} 不存在`);
|
|
65
|
-
const users = await bot.getDepartmentUsers(args.dept_id);
|
|
66
|
-
return { users, count: users.length };
|
|
67
|
-
},
|
|
68
|
-
}, plugin.name));
|
|
69
|
-
disposers.push(toolService.addTool({
|
|
70
|
-
name: 'dingtalk_list_departments',
|
|
71
|
-
description: '获取钉钉部门列表',
|
|
72
|
-
parameters: {
|
|
73
|
-
type: 'object',
|
|
74
|
-
properties: {
|
|
75
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
76
|
-
dept_id: { type: 'string', description: '父部门 ID,默认 1(根部门)' },
|
|
77
|
-
},
|
|
78
|
-
required: ['bot'],
|
|
79
|
-
},
|
|
80
|
-
platforms: ['dingtalk'],
|
|
81
|
-
tags: ['dingtalk'],
|
|
82
|
-
execute: async (args) => {
|
|
83
|
-
const bot = dingtalk.bots.get(args.bot);
|
|
84
|
-
if (!bot)
|
|
85
|
-
throw new Error(`Bot ${args.bot} 不存在`);
|
|
86
|
-
const departments = await bot.getDepartmentList(args.dept_id || '1');
|
|
87
|
-
return { departments, count: departments.length };
|
|
88
|
-
},
|
|
89
|
-
}, plugin.name));
|
|
90
|
-
disposers.push(toolService.addTool({
|
|
91
|
-
name: 'dingtalk_send_work_notice',
|
|
92
|
-
description: '向指定用户发送钉钉工作通知',
|
|
93
|
-
parameters: {
|
|
94
|
-
type: 'object',
|
|
95
|
-
properties: {
|
|
96
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
97
|
-
user_ids: { type: 'string', description: '用户 ID 列表,逗号分隔' },
|
|
98
|
-
content: { type: 'string', description: '通知内容' },
|
|
99
|
-
},
|
|
100
|
-
required: ['bot', 'user_ids', 'content'],
|
|
101
|
-
},
|
|
102
|
-
platforms: ['dingtalk'],
|
|
103
|
-
tags: ['dingtalk'],
|
|
104
|
-
execute: async (args) => {
|
|
105
|
-
const bot = dingtalk.bots.get(args.bot);
|
|
106
|
-
if (!bot)
|
|
107
|
-
throw new Error(`Bot ${args.bot} 不存在`);
|
|
108
|
-
const msgContent = { msgtype: 'text', text: { content: args.content } };
|
|
109
|
-
const success = await bot.sendWorkNotice(args.user_ids.split(','), msgContent);
|
|
110
|
-
return { success, message: success ? '工作通知已发送' : '发送失败' };
|
|
111
|
-
},
|
|
112
|
-
}, plugin.name));
|
|
113
|
-
disposers.push(toolService.addTool({
|
|
114
|
-
name: 'dingtalk_create_chat',
|
|
115
|
-
description: '创建钉钉群聊',
|
|
116
|
-
parameters: {
|
|
117
|
-
type: 'object',
|
|
118
|
-
properties: {
|
|
119
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
120
|
-
name: { type: 'string', description: '群名' },
|
|
121
|
-
owner: { type: 'string', description: '群主用户 ID' },
|
|
122
|
-
members: { type: 'string', description: '成员用户 ID 列表,逗号分隔' },
|
|
123
|
-
},
|
|
124
|
-
required: ['bot', 'name', 'owner', 'members'],
|
|
125
|
-
},
|
|
126
|
-
platforms: ['dingtalk'],
|
|
127
|
-
tags: ['dingtalk'],
|
|
128
|
-
execute: async (args) => {
|
|
129
|
-
const bot = dingtalk.bots.get(args.bot);
|
|
130
|
-
if (!bot)
|
|
131
|
-
throw new Error(`Bot ${args.bot} 不存在`);
|
|
132
|
-
const chatId = await bot.createChat(args.name, args.owner, args.members.split(','));
|
|
133
|
-
return { success: !!chatId, chat_id: chatId, message: chatId ? `群聊创建成功: ${chatId}` : '创建失败' };
|
|
134
|
-
},
|
|
135
|
-
}, plugin.name));
|
|
136
|
-
disposers.push(toolService.addTool({
|
|
137
|
-
name: 'dingtalk_add_chat_members',
|
|
138
|
-
description: '向钉钉群聊添加成员',
|
|
139
|
-
parameters: {
|
|
140
|
-
type: 'object',
|
|
141
|
-
properties: {
|
|
142
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
143
|
-
chat_id: { type: 'string', description: '群聊 ID' },
|
|
144
|
-
user_ids: { type: 'string', description: '要添加的用户 ID 列表,逗号分隔' },
|
|
145
|
-
},
|
|
146
|
-
required: ['bot', 'chat_id', 'user_ids'],
|
|
147
|
-
},
|
|
148
|
-
platforms: ['dingtalk'],
|
|
149
|
-
tags: ['dingtalk'],
|
|
150
|
-
execute: async (args) => {
|
|
151
|
-
const bot = dingtalk.bots.get(args.bot);
|
|
152
|
-
if (!bot)
|
|
153
|
-
throw new Error(`Bot ${args.bot} 不存在`);
|
|
154
|
-
const success = await bot.updateChat(args.chat_id, { add_useridlist: args.user_ids.split(',') });
|
|
155
|
-
return { success, message: success ? '成员添加成功' : '添加失败' };
|
|
156
|
-
},
|
|
157
|
-
}, plugin.name));
|
|
158
|
-
disposers.push(toolService.addTool({
|
|
159
|
-
name: 'dingtalk_dept_info',
|
|
160
|
-
description: '获取钉钉部门详细信息',
|
|
161
|
-
parameters: {
|
|
162
|
-
type: 'object',
|
|
163
|
-
properties: {
|
|
164
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
165
|
-
dept_id: { type: 'string', description: '部门 ID' },
|
|
166
|
-
},
|
|
167
|
-
required: ['bot', 'dept_id'],
|
|
168
|
-
},
|
|
169
|
-
platforms: ['dingtalk'],
|
|
170
|
-
tags: ['dingtalk'],
|
|
171
|
-
execute: async (args) => {
|
|
172
|
-
const bot = dingtalk.bots.get(args.bot);
|
|
173
|
-
if (!bot)
|
|
174
|
-
throw new Error(`Bot ${args.bot} 不存在`);
|
|
175
|
-
return await bot.getDepartmentInfo(Number(args.dept_id));
|
|
176
|
-
},
|
|
177
|
-
}, plugin.name));
|
|
178
|
-
disposers.push(toolService.addTool({
|
|
179
|
-
name: 'dingtalk_update_chat',
|
|
180
|
-
description: '更新钉钉群聊设置(改名、换群主、增减成员)',
|
|
181
|
-
parameters: {
|
|
182
|
-
type: 'object',
|
|
183
|
-
properties: {
|
|
184
|
-
bot: { type: 'string', description: 'Bot 名称' },
|
|
185
|
-
chat_id: { type: 'string', description: '群聊 ID' },
|
|
186
|
-
name: { type: 'string', description: '新群名(可选)' },
|
|
187
|
-
owner: { type: 'string', description: '新群主 userId(可选)' },
|
|
188
|
-
add_members: { type: 'string', description: '要添加的成员 userId,逗号分隔(可选)' },
|
|
189
|
-
remove_members: { type: 'string', description: '要移除的成员 userId,逗号分隔(可选)' },
|
|
190
|
-
},
|
|
191
|
-
required: ['bot', 'chat_id'],
|
|
192
|
-
},
|
|
193
|
-
platforms: ['dingtalk'],
|
|
194
|
-
tags: ['dingtalk'],
|
|
195
|
-
execute: async (args) => {
|
|
196
|
-
const bot = dingtalk.bots.get(args.bot);
|
|
197
|
-
if (!bot)
|
|
198
|
-
throw new Error(`Bot ${args.bot} 不存在`);
|
|
199
|
-
const options = {};
|
|
200
|
-
if (args.name)
|
|
201
|
-
options.name = args.name;
|
|
202
|
-
if (args.owner)
|
|
203
|
-
options.owner = args.owner;
|
|
204
|
-
if (args.add_members)
|
|
205
|
-
options.add_useridlist = args.add_members.split(',').map((s) => s.trim());
|
|
206
|
-
if (args.remove_members)
|
|
207
|
-
options.del_useridlist = args.remove_members.split(',').map((s) => s.trim());
|
|
208
|
-
await bot.updateChat(args.chat_id, options);
|
|
209
|
-
return { success: true, message: '群聊设置已更新' };
|
|
210
|
-
},
|
|
211
|
-
}, plugin.name));
|
|
212
|
-
return () => disposers.forEach(d => d());
|
|
213
|
-
});
|
|
214
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
export { dingtalkInboundConversation, formatInboundContent, formatOutboundBody, generateMessageId, headerValue, normalizeWebhookPath, readTextBody, resolveChatType, resolveDingTalkConfig, resolveSender, verifySignature, } from './protocol.js';
|
|
2
|
+
export { DingTalkClient, DingTalkEndpoint, } from './endpoint.js';
|
|
3
|
+
export { registerDingTalkWebhookRoutes, handleDingTalkWebhookRequest, } from './webhook.js';
|
|
4
|
+
export { dingtalkClient } from './client.js';
|
|
5
|
+
export { checkDingtalkPlatformPermit, dingtalkGroupPermitResolver, normalizeDingtalkSenderForPermit, platformPermit, } from './platform-permit.js';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 钉钉 DingTalk platform permit
|
|
3
|
+
*/
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
|
+
export declare function platformPermit(perm: string): string;
|
|
6
|
+
export declare function dingtalkGroupPermitResolver(logicalPerm: string): string;
|
|
7
|
+
export declare function normalizeDingtalkSenderForPermit(input: {
|
|
8
|
+
isOwner?: boolean;
|
|
9
|
+
isAdmin?: boolean;
|
|
10
|
+
}): {
|
|
11
|
+
role?: string;
|
|
12
|
+
permissions?: string[];
|
|
13
|
+
};
|
|
14
|
+
export declare function checkDingtalkPlatformPermit(perm: string, subject: PermissionSubject): boolean;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const ADAPTER = 'dingtalk';
|
|
2
|
+
export function platformPermit(perm) {
|
|
3
|
+
return `platform(${ADAPTER},${perm})`;
|
|
4
|
+
}
|
|
5
|
+
const FACTORY_PERM_MAP = {
|
|
6
|
+
scene_admin: 'chat_admin',
|
|
7
|
+
scene_owner: 'chat_owner',
|
|
8
|
+
};
|
|
9
|
+
export function dingtalkGroupPermitResolver(logicalPerm) {
|
|
10
|
+
return platformPermit(FACTORY_PERM_MAP[logicalPerm] ?? logicalPerm);
|
|
11
|
+
}
|
|
12
|
+
export function normalizeDingtalkSenderForPermit(input) {
|
|
13
|
+
if (input.isOwner) {
|
|
14
|
+
return { role: 'owner', permissions: ['chat_owner', 'chat_admin'] };
|
|
15
|
+
}
|
|
16
|
+
if (input.isAdmin) {
|
|
17
|
+
return { role: 'admin', permissions: ['chat_admin'] };
|
|
18
|
+
}
|
|
19
|
+
return { role: 'member', permissions: [] };
|
|
20
|
+
}
|
|
21
|
+
export function checkDingtalkPlatformPermit(perm, subject) {
|
|
22
|
+
const role = subject.sender?.role?.[0];
|
|
23
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
24
|
+
const has = (t) => permissions.includes(t);
|
|
25
|
+
switch (perm) {
|
|
26
|
+
case 'chat_owner':
|
|
27
|
+
return role === 'owner' || has('chat_owner');
|
|
28
|
+
case 'chat_admin':
|
|
29
|
+
return role === 'owner' || role === 'admin' || has('chat_admin') || has('chat_owner');
|
|
30
|
+
default:
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|