@zhin.js/adapter-dingtalk 4.0.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +59 -345
- package/adapters/dingtalk.ts +26 -0
- package/agent/tools/add_chat_members.ts +2 -2
- package/agent/tools/create_chat.ts +2 -2
- package/agent/tools/dept_info.ts +2 -2
- package/agent/tools/get_dept_users.ts +2 -2
- package/agent/tools/get_user.ts +2 -2
- package/agent/tools/list_departments.ts +2 -2
- package/agent/tools/send_work_notice.ts +2 -2
- package/agent/tools/update_chat.ts +2 -2
- package/lib/dingtalk-agent-deps.d.ts +26 -0
- package/lib/dingtalk-agent-deps.js +30 -0
- package/lib/endpoint.d.ts +55 -0
- package/lib/endpoint.js +312 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +5 -0
- package/lib/platform-permit.d.ts +15 -0
- package/lib/{src/platform-permit.js → platform-permit.js} +1 -2
- package/lib/protocol.d.ts +121 -0
- package/lib/protocol.js +221 -0
- package/lib/webhook.d.ts +13 -0
- package/lib/webhook.js +48 -0
- package/package.json +45 -23
- package/plugin.ts +12 -0
- package/schema.json +56 -0
- package/src/dingtalk-agent-deps.ts +49 -9
- package/src/endpoint.ts +263 -479
- package/src/index.ts +45 -59
- package/src/platform-permit.ts +1 -1
- package/src/protocol.ts +338 -0
- package/src/webhook.ts +76 -0
- package/lib/agent/tools/add_chat_members.js +0 -21
- package/lib/agent/tools/add_chat_members.js.map +0 -1
- package/lib/agent/tools/create_chat.js +0 -22
- package/lib/agent/tools/create_chat.js.map +0 -1
- package/lib/agent/tools/dept_info.js +0 -17
- package/lib/agent/tools/dept_info.js.map +0 -1
- package/lib/agent/tools/get_dept_users.js +0 -18
- package/lib/agent/tools/get_dept_users.js.map +0 -1
- package/lib/agent/tools/get_user.js +0 -17
- package/lib/agent/tools/get_user.js.map +0 -1
- package/lib/agent/tools/list_departments.js +0 -18
- package/lib/agent/tools/list_departments.js.map +0 -1
- package/lib/agent/tools/send_work_notice.js +0 -20
- package/lib/agent/tools/send_work_notice.js.map +0 -1
- package/lib/agent/tools/update_chat.js +0 -31
- package/lib/agent/tools/update_chat.js.map +0 -1
- package/lib/src/adapter.js +0 -40
- package/lib/src/adapter.js.map +0 -1
- package/lib/src/dingtalk-agent-deps.js +0 -10
- package/lib/src/dingtalk-agent-deps.js.map +0 -1
- package/lib/src/endpoint.js +0 -547
- package/lib/src/endpoint.js.map +0 -1
- package/lib/src/index.js +0 -43
- package/lib/src/index.js.map +0 -1
- package/lib/src/platform-permit.js.map +0 -1
- package/lib/src/segment-mapper.js +0 -2
- package/lib/src/segment-mapper.js.map +0 -1
- package/lib/src/types.js +0 -5
- package/lib/src/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -46
- package/src/segment-mapper.ts +0 -1
- package/src/types.ts +0 -56
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"list_departments.js","sourceRoot":"","sources":["../../../agent/tools/list_departments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,eAAe,UAAU,CAA4C;IACnE,WAAW,EAAE,UAAU;IACvB,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC;QAC/C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC;KAC5D,CAAC;IACF,SAAS,EAAE,CAAC,UAAU,CAAC;IACvB,IAAI,EAAE,CAAC,UAAU,CAAC;IAClB,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,EAAgD;QAClF,MAAM,QAAQ,GAAG,oBAAoB,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QACjE,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,iBAAiB,CAAC,MAAM,CAAC,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC;QAC7E,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC;IACpD,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { defineTool } from '@zhin.js/agent/tools';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getDingtalkAgentDeps } from '../../src/dingtalk-agent-deps.js';
|
|
4
|
-
export default defineTool({
|
|
5
|
-
description: '向指定用户发送钉钉工作通知',
|
|
6
|
-
inputSchema: z.object({
|
|
7
|
-
endpoint_id: z.string().describe('Endpoint 名称'),
|
|
8
|
-
user_ids: z.string().describe('用户 ID 列表,逗号分隔'),
|
|
9
|
-
content: z.string().describe('通知内容'),
|
|
10
|
-
}),
|
|
11
|
-
platforms: ['dingtalk'],
|
|
12
|
-
tags: ['dingtalk'],
|
|
13
|
-
async execute({ endpoint_id, user_ids, content }) {
|
|
14
|
-
const endpoint = getDingtalkAgentDeps().getEndpoint(endpoint_id);
|
|
15
|
-
const msgContent = { msgtype: 'text', text: { content } };
|
|
16
|
-
const success = await endpoint.sendWorkNotice(user_ids.split(','), msgContent);
|
|
17
|
-
return { success, message: success ? '工作通知已发送' : '发送失败' };
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
//# sourceMappingURL=send_work_notice.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"send_work_notice.js","sourceRoot":"","sources":["../../../agent/tools/send_work_notice.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,eAAe,UAAU,CAA6D;IACpF,WAAW,EAAE,eAAe;IAC5B,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC;QAC/C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC;QAC9C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC;KACrC,CAAC;IACF,SAAS,EAAE,CAAC,UAAU,CAAC;IACvB,IAAI,EAAE,CAAC,UAAU,CAAC;IAClB,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAiE;QAC7G,MAAM,QAAQ,GAAG,oBAAoB,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QACjE,MAAM,UAAU,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC;QAC1D,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC,CAAC;QAC/E,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC5D,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { defineTool } from '@zhin.js/agent/tools';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getDingtalkAgentDeps } from '../../src/dingtalk-agent-deps.js';
|
|
4
|
-
export default defineTool({
|
|
5
|
-
description: '更新钉钉群聊设置(改名、换群主、增减成员)',
|
|
6
|
-
inputSchema: z.object({
|
|
7
|
-
endpoint_id: z.string().describe('Endpoint 名称'),
|
|
8
|
-
chat_id: z.string().describe('群聊 ID'),
|
|
9
|
-
name: z.string().optional().describe('新群名(可选)'),
|
|
10
|
-
owner: z.string().optional().describe('新群主 userId(可选)'),
|
|
11
|
-
add_members: z.string().optional().describe('要添加的成员 userId,逗号分隔(可选)'),
|
|
12
|
-
remove_members: z.string().optional().describe('要移除的成员 userId,逗号分隔(可选)'),
|
|
13
|
-
}),
|
|
14
|
-
platforms: ['dingtalk'],
|
|
15
|
-
tags: ['dingtalk'],
|
|
16
|
-
async execute({ endpoint_id, chat_id, name, owner, add_members, remove_members }) {
|
|
17
|
-
const endpoint = getDingtalkAgentDeps().getEndpoint(endpoint_id);
|
|
18
|
-
const options = {};
|
|
19
|
-
if (name)
|
|
20
|
-
options.name = name;
|
|
21
|
-
if (owner)
|
|
22
|
-
options.owner = owner;
|
|
23
|
-
if (add_members)
|
|
24
|
-
options.add_useridlist = add_members.split(',').map((s) => s.trim());
|
|
25
|
-
if (remove_members)
|
|
26
|
-
options.del_useridlist = remove_members.split(',').map((s) => s.trim());
|
|
27
|
-
await endpoint.updateChat(chat_id, options);
|
|
28
|
-
return { success: true, message: '群聊设置已更新' };
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
//# sourceMappingURL=update_chat.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"update_chat.js","sourceRoot":"","sources":["../../../agent/tools/update_chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,eAAe,UAAU,CAAyH;IAChJ,WAAW,EAAE,uBAAuB;IACpC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC;QAC/C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC;QACrC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC;QAC/C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QACvD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;QACrE,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;KACzE,CAAC;IACF,SAAS,EAAE,CAAC,UAAU,CAAC;IACvB,IAAI,EAAE,CAAC,UAAU,CAAC;IAClB,KAAK,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,cAAc,EAA6H;QACzM,MAAM,QAAQ,GAAG,oBAAoB,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QACjE,MAAM,OAAO,GAA4B,EAAE,CAAC;QAC5C,IAAI,IAAI;YAAE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;QAC9B,IAAI,KAAK;YAAE,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;QACjC,IAAI,WAAW;YAAE,OAAO,CAAC,cAAc,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC9F,IAAI,cAAc;YAAE,OAAO,CAAC,cAAc,GAAG,cAAc,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACpG,MAAM,QAAQ,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IAC/C,CAAC;CACF,CAAC,CAAC"}
|
package/lib/src/adapter.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 钉钉适配器
|
|
3
|
-
*/
|
|
4
|
-
import { Adapter, OUTBOUND_RICH_SEGMENT_POLICY_IM_FULL } from 'zhin.js';
|
|
5
|
-
import { DingTalkEndpoint } from "./endpoint.js";
|
|
6
|
-
export class DingTalkAdapter extends Adapter {
|
|
7
|
-
static capabilities = ['inbound', 'outbound'];
|
|
8
|
-
static outboundRichSegmentPolicy = OUTBOUND_RICH_SEGMENT_POLICY_IM_FULL;
|
|
9
|
-
static interactivePolicy = 'text';
|
|
10
|
-
#router;
|
|
11
|
-
constructor(plugin, router) {
|
|
12
|
-
super(plugin, "dingtalk", []);
|
|
13
|
-
this.#router = router;
|
|
14
|
-
}
|
|
15
|
-
createEndpoint(config) {
|
|
16
|
-
return new DingTalkEndpoint(this, this.#router, config);
|
|
17
|
-
}
|
|
18
|
-
async removeMember(endpointId, sceneId, userId) {
|
|
19
|
-
const endpoint = this.endpoints.get(endpointId);
|
|
20
|
-
if (!endpoint)
|
|
21
|
-
throw new Error(`Endpoint ${endpointId} 不存在`);
|
|
22
|
-
return endpoint.updateChat(sceneId, { del_useridlist: [userId] });
|
|
23
|
-
}
|
|
24
|
-
async renameScene(endpointId, sceneId, name) {
|
|
25
|
-
const endpoint = this.endpoints.get(endpointId);
|
|
26
|
-
if (!endpoint)
|
|
27
|
-
throw new Error(`Endpoint ${endpointId} 不存在`);
|
|
28
|
-
return endpoint.updateChat(sceneId, { name });
|
|
29
|
-
}
|
|
30
|
-
async getSceneInfo(endpointId, sceneId) {
|
|
31
|
-
const endpoint = this.endpoints.get(endpointId);
|
|
32
|
-
if (!endpoint)
|
|
33
|
-
throw new Error(`Endpoint ${endpointId} 不存在`);
|
|
34
|
-
return endpoint.getChatInfo(sceneId);
|
|
35
|
-
}
|
|
36
|
-
async start() {
|
|
37
|
-
await super.start();
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
//# sourceMappingURL=adapter.js.map
|
package/lib/src/adapter.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/adapter.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,OAAO,EACN,oCAAoC,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD,MAAM,OAAO,eAAgB,SAAQ,OAAyB;IAC5D,MAAM,CAAmB,YAAY,GAAG,CAAC,SAAS,EAAE,UAAU,CAAU,CAAC;IACzE,MAAM,CAAU,yBAAyB,GAAG,oCAAoC,CAAC;IACjF,MAAM,CAAU,iBAAiB,GAAG,MAAe,CAAC;IAEpD,OAAO,CAAM;IAEb,YAAY,MAAc,EAAE,MAAW;QACrC,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED,cAAc,CAAC,MAA8B;QAC3C,OAAO,IAAI,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1D,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,UAAkB,EAAE,OAAe,EAAE,MAAc;QACpE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,UAAU,MAAM,CAAC,CAAC;QAC7D,OAAO,QAAQ,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,cAAc,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,UAAkB,EAAE,OAAe,EAAE,IAAY;QACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,UAAU,MAAM,CAAC,CAAC;QAC7D,OAAO,QAAQ,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,UAAkB,EAAE,OAAe;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,UAAU,MAAM,CAAC,CAAC;QAC7D,OAAO,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
let _deps = null;
|
|
2
|
-
export function setDingtalkAgentDeps(deps) {
|
|
3
|
-
_deps = deps;
|
|
4
|
-
}
|
|
5
|
-
export function getDingtalkAgentDeps() {
|
|
6
|
-
if (!_deps)
|
|
7
|
-
throw new Error('dingtalk agent deps not initialized');
|
|
8
|
-
return _deps;
|
|
9
|
-
}
|
|
10
|
-
//# sourceMappingURL=dingtalk-agent-deps.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dingtalk-agent-deps.js","sourceRoot":"","sources":["../../src/dingtalk-agent-deps.ts"],"names":[],"mappings":"AAQA,IAAI,KAAK,GAA6B,IAAI,CAAC;AAE3C,MAAM,UAAU,oBAAoB,CAAC,IAAuB;IAC1D,KAAK,GAAG,IAAI,CAAC;AACf,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACnE,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/lib/src/endpoint.js
DELETED
|
@@ -1,547 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 钉钉 Endpoint 实现
|
|
3
|
-
*/
|
|
4
|
-
import { formatCompact, Message, segment, expandInteractiveSegmentsInContent, } from 'zhin.js';
|
|
5
|
-
import { registerFetchRoute } from "@zhin.js/host-router/router";
|
|
6
|
-
import { createHmac } from "crypto";
|
|
7
|
-
import { normalizeDingtalkSenderForPermit } from "./platform-permit.js";
|
|
8
|
-
import { fromCanonicalSegments, toCanonicalSegments } from './segment-mapper.js';
|
|
9
|
-
export class DingTalkEndpoint {
|
|
10
|
-
adapter;
|
|
11
|
-
$config;
|
|
12
|
-
$connected;
|
|
13
|
-
router;
|
|
14
|
-
accessToken;
|
|
15
|
-
baseURL;
|
|
16
|
-
sessionWebhooks = new Map();
|
|
17
|
-
get $id() {
|
|
18
|
-
return this.$config.name;
|
|
19
|
-
}
|
|
20
|
-
get logger() {
|
|
21
|
-
return this.adapter.plugin.logger;
|
|
22
|
-
}
|
|
23
|
-
constructor(adapter, router, $config) {
|
|
24
|
-
this.adapter = adapter;
|
|
25
|
-
this.$config = $config;
|
|
26
|
-
this.router = router;
|
|
27
|
-
this.$connected = false;
|
|
28
|
-
this.accessToken = { token: "", expires_in: 0, timestamp: 0 };
|
|
29
|
-
this.baseURL = $config.apiBaseUrl || "https://oapi.dingtalk.com";
|
|
30
|
-
this.setupWebhookRoute();
|
|
31
|
-
}
|
|
32
|
-
async request(path, options = {}) {
|
|
33
|
-
await this.ensureAccessToken();
|
|
34
|
-
const { method = "GET", params = {}, body } = options;
|
|
35
|
-
const urlParams = new URLSearchParams({
|
|
36
|
-
...params,
|
|
37
|
-
access_token: this.accessToken.token,
|
|
38
|
-
});
|
|
39
|
-
const url = `${this.baseURL}${path}?${urlParams.toString()}`;
|
|
40
|
-
const fetchOptions = {
|
|
41
|
-
method,
|
|
42
|
-
headers: {
|
|
43
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
if (body && method === "POST") {
|
|
47
|
-
fetchOptions.body = JSON.stringify(body);
|
|
48
|
-
}
|
|
49
|
-
const response = await fetch(url, fetchOptions);
|
|
50
|
-
return await response.json();
|
|
51
|
-
}
|
|
52
|
-
setupWebhookRoute() {
|
|
53
|
-
registerFetchRoute(this.router, "POST", this.$config.webhookPath, (ctx) => {
|
|
54
|
-
void this.handleWebhook(ctx);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
async handleWebhook(ctx) {
|
|
58
|
-
try {
|
|
59
|
-
const body = ctx.request.body;
|
|
60
|
-
const timestamp = ctx.get("timestamp");
|
|
61
|
-
const sign = ctx.get("sign");
|
|
62
|
-
if (timestamp && sign) {
|
|
63
|
-
if (!this.verifySignature(timestamp, sign)) {
|
|
64
|
-
this.logger.warn(formatCompact({ op: "webhook", ok: false, error: "invalid signature" }));
|
|
65
|
-
ctx.status = 403;
|
|
66
|
-
ctx.body = { code: -1, msg: "Forbidden" };
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
const event = body;
|
|
71
|
-
if (event.msgtype) {
|
|
72
|
-
await this.handleEvent(event);
|
|
73
|
-
}
|
|
74
|
-
ctx.status = 200;
|
|
75
|
-
ctx.body = { code: 0, msg: "success" };
|
|
76
|
-
}
|
|
77
|
-
catch (error) {
|
|
78
|
-
this.logger.error("Webhook error:", error);
|
|
79
|
-
ctx.status = 500;
|
|
80
|
-
ctx.body = { code: -1, msg: "Internal Server Error" };
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
verifySignature(timestamp, sign) {
|
|
84
|
-
try {
|
|
85
|
-
const stringToSign = `${timestamp}\n${this.$config.appSecret}`;
|
|
86
|
-
const hmac = createHmac("sha256", this.$config.appSecret);
|
|
87
|
-
hmac.update(stringToSign);
|
|
88
|
-
const calculatedSign = hmac.digest("base64");
|
|
89
|
-
return calculatedSign === sign;
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
this.logger.error("Signature verification error:", error);
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async handleEvent(event) {
|
|
97
|
-
if (event.sessionWebhook && event.conversationId) {
|
|
98
|
-
this.sessionWebhooks.set(event.conversationId, event.sessionWebhook);
|
|
99
|
-
}
|
|
100
|
-
const message = this.$formatMessage(event);
|
|
101
|
-
this.adapter.emit("message.receive", message);
|
|
102
|
-
this.logger.debug(formatCompact({
|
|
103
|
-
op: "recv",
|
|
104
|
-
endpoint: this.$config.name,
|
|
105
|
-
channel: message.$channel.type,
|
|
106
|
-
id: message.$channel.id,
|
|
107
|
-
len: segment.raw(message.$content).length,
|
|
108
|
-
}));
|
|
109
|
-
}
|
|
110
|
-
async ensureAccessToken() {
|
|
111
|
-
const now = Date.now();
|
|
112
|
-
if (this.accessToken.token &&
|
|
113
|
-
now <
|
|
114
|
-
this.accessToken.timestamp +
|
|
115
|
-
(this.accessToken.expires_in - 300) * 1000) {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
await this.refreshAccessToken();
|
|
119
|
-
}
|
|
120
|
-
async refreshAccessToken() {
|
|
121
|
-
try {
|
|
122
|
-
const baseURL = this.$config.apiBaseUrl || "https://oapi.dingtalk.com";
|
|
123
|
-
const params = new URLSearchParams({
|
|
124
|
-
appkey: this.$config.appKey,
|
|
125
|
-
appsecret: this.$config.appSecret,
|
|
126
|
-
});
|
|
127
|
-
const url = `${baseURL}/gettoken?${params.toString()}`;
|
|
128
|
-
const response = await fetch(url);
|
|
129
|
-
const data = await response.json();
|
|
130
|
-
if (data.errcode === 0) {
|
|
131
|
-
this.accessToken = {
|
|
132
|
-
token: data.access_token,
|
|
133
|
-
expires_in: data.expires_in,
|
|
134
|
-
timestamp: Date.now(),
|
|
135
|
-
};
|
|
136
|
-
this.logger.debug("Access token refreshed successfully");
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
throw new Error(`Failed to get access token: ${data.errmsg}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
this.logger.error("Failed to refresh access token:", error);
|
|
144
|
-
throw error;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
$formatMessage(msg) {
|
|
148
|
-
const wire = this.parseMessageContent(msg);
|
|
149
|
-
const content = toCanonicalSegments(wire);
|
|
150
|
-
const chatType = msg.conversationType === "2" ? "group" : "private";
|
|
151
|
-
const permit = normalizeDingtalkSenderForPermit({ isAdmin: msg.isAdmin === true });
|
|
152
|
-
return Message.from(msg, {
|
|
153
|
-
$id: msg.msgId || Date.now().toString(),
|
|
154
|
-
$adapter: "dingtalk",
|
|
155
|
-
$endpoint: this.$config.name,
|
|
156
|
-
$sender: {
|
|
157
|
-
id: msg.senderId || msg.senderStaffId || "unknown",
|
|
158
|
-
name: msg.senderNick || msg.senderId || "Unknown User",
|
|
159
|
-
role: permit.role,
|
|
160
|
-
permissions: permit.permissions,
|
|
161
|
-
},
|
|
162
|
-
$channel: {
|
|
163
|
-
id: msg.conversationId || "unknown",
|
|
164
|
-
type: chatType,
|
|
165
|
-
},
|
|
166
|
-
$content: content,
|
|
167
|
-
$raw: JSON.stringify(msg),
|
|
168
|
-
$timestamp: msg.createAt || Date.now(),
|
|
169
|
-
$recall: async () => {
|
|
170
|
-
await this.$recallMessage(msg.msgId || "");
|
|
171
|
-
},
|
|
172
|
-
$reply: async (content) => {
|
|
173
|
-
return await this.adapter.sendMessage({
|
|
174
|
-
context: "dingtalk",
|
|
175
|
-
endpoint: this.$config.name,
|
|
176
|
-
id: msg.conversationId || msg.senderId || "unknown",
|
|
177
|
-
type: chatType,
|
|
178
|
-
content: content,
|
|
179
|
-
});
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
parseMessageContent(msg) {
|
|
184
|
-
const content = [];
|
|
185
|
-
if (!msg.msgtype)
|
|
186
|
-
return content;
|
|
187
|
-
try {
|
|
188
|
-
switch (msg.msgtype) {
|
|
189
|
-
case "text":
|
|
190
|
-
if (msg.text?.content) {
|
|
191
|
-
content.push(segment("text", { content: msg.text.content }));
|
|
192
|
-
if (msg.atUsers && msg.atUsers.length > 0) {
|
|
193
|
-
for (const atUser of msg.atUsers) {
|
|
194
|
-
content.push(segment("at", {
|
|
195
|
-
id: atUser.dingtalkId || atUser.staffId,
|
|
196
|
-
name: atUser.dingtalkId || atUser.staffId,
|
|
197
|
-
}));
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
break;
|
|
202
|
-
case "picture":
|
|
203
|
-
if (msg.content) {
|
|
204
|
-
content.push(segment("image", {
|
|
205
|
-
url: msg.content.downloadCode ||
|
|
206
|
-
msg.content.pictureDownloadCode,
|
|
207
|
-
file: msg.content.downloadCode ||
|
|
208
|
-
msg.content.pictureDownloadCode,
|
|
209
|
-
}));
|
|
210
|
-
}
|
|
211
|
-
break;
|
|
212
|
-
case "file":
|
|
213
|
-
if (msg.content) {
|
|
214
|
-
content.push(segment("file", {
|
|
215
|
-
file: msg.content.downloadCode,
|
|
216
|
-
name: msg.content.fileName,
|
|
217
|
-
size: msg.content.fileSize,
|
|
218
|
-
}));
|
|
219
|
-
}
|
|
220
|
-
break;
|
|
221
|
-
case "audio":
|
|
222
|
-
if (msg.content) {
|
|
223
|
-
content.push(segment("audio", {
|
|
224
|
-
file: msg.content.downloadCode,
|
|
225
|
-
duration: msg.content.duration,
|
|
226
|
-
}));
|
|
227
|
-
}
|
|
228
|
-
break;
|
|
229
|
-
case "video":
|
|
230
|
-
if (msg.content) {
|
|
231
|
-
content.push(segment("video", {
|
|
232
|
-
file: msg.content.downloadCode,
|
|
233
|
-
duration: msg.content.duration,
|
|
234
|
-
size: msg.content.videoSize,
|
|
235
|
-
}));
|
|
236
|
-
}
|
|
237
|
-
break;
|
|
238
|
-
case "richText":
|
|
239
|
-
if (msg.content?.richText) {
|
|
240
|
-
for (const item of msg.content.richText) {
|
|
241
|
-
if (item.text) {
|
|
242
|
-
content.push(segment("text", { content: item.text }));
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
break;
|
|
247
|
-
case "markdown":
|
|
248
|
-
if (msg.content?.text) {
|
|
249
|
-
content.push(segment("markdown", {
|
|
250
|
-
content: msg.content.text,
|
|
251
|
-
title: msg.content.title,
|
|
252
|
-
}));
|
|
253
|
-
}
|
|
254
|
-
break;
|
|
255
|
-
default:
|
|
256
|
-
content.push(segment("text", {
|
|
257
|
-
content: `[不支持的消息类型: ${msg.msgtype}]`,
|
|
258
|
-
}));
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
catch (error) {
|
|
263
|
-
this.logger.error("Failed to parse message content:", error);
|
|
264
|
-
content.push(segment("text", { content: "[消息解析失败]" }));
|
|
265
|
-
}
|
|
266
|
-
return content;
|
|
267
|
-
}
|
|
268
|
-
async $sendMessage(options) {
|
|
269
|
-
const conversationId = options.id;
|
|
270
|
-
const canonical = expandInteractiveSegmentsInContent(options.content);
|
|
271
|
-
const wire = fromCanonicalSegments(canonical);
|
|
272
|
-
const content = this.formatSendContent(wire);
|
|
273
|
-
try {
|
|
274
|
-
const sessionWebhook = this.sessionWebhooks.get(conversationId);
|
|
275
|
-
if (sessionWebhook) {
|
|
276
|
-
const response = await fetch(sessionWebhook, {
|
|
277
|
-
method: "POST",
|
|
278
|
-
headers: {
|
|
279
|
-
"Content-Type": "application/json; charset=utf-8",
|
|
280
|
-
},
|
|
281
|
-
body: JSON.stringify(content),
|
|
282
|
-
});
|
|
283
|
-
const data = await response.json();
|
|
284
|
-
if (data.errcode !== 0) {
|
|
285
|
-
throw new Error(`Failed to send message via session webhook: ${data.errmsg}`);
|
|
286
|
-
}
|
|
287
|
-
this.logger.debug("Message sent via session webhook");
|
|
288
|
-
return data.msgId || Date.now().toString();
|
|
289
|
-
}
|
|
290
|
-
const data = await this.request("/robot/send", {
|
|
291
|
-
method: "POST",
|
|
292
|
-
body: {
|
|
293
|
-
...content,
|
|
294
|
-
robotCode: this.$config.robotCode,
|
|
295
|
-
},
|
|
296
|
-
});
|
|
297
|
-
if (data.errcode !== 0) {
|
|
298
|
-
throw new Error(`Failed to send message: ${data.errmsg}`);
|
|
299
|
-
}
|
|
300
|
-
this.logger.debug("Message sent successfully");
|
|
301
|
-
return data.msgId || Date.now().toString();
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
this.logger.error("Failed to send message:", error);
|
|
305
|
-
throw error;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
async $recallMessage(id) {
|
|
309
|
-
this.logger.warn(formatCompact({ op: "recall", ok: false, error: "not supported" }));
|
|
310
|
-
}
|
|
311
|
-
formatSendContent(content) {
|
|
312
|
-
if (typeof content === "string") {
|
|
313
|
-
return { msgtype: "text", text: { content } };
|
|
314
|
-
}
|
|
315
|
-
if (Array.isArray(content)) {
|
|
316
|
-
const textParts = [];
|
|
317
|
-
const atUserIds = [];
|
|
318
|
-
let hasMedia = false;
|
|
319
|
-
let mediaContent = null;
|
|
320
|
-
for (const item of content) {
|
|
321
|
-
if (typeof item === "string") {
|
|
322
|
-
textParts.push(item);
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
const seg = item;
|
|
326
|
-
switch (seg.type) {
|
|
327
|
-
case "text":
|
|
328
|
-
textParts.push(seg.data.content || seg.data.text || "");
|
|
329
|
-
break;
|
|
330
|
-
case "at":
|
|
331
|
-
const userId = seg.data.id || seg.data.userId;
|
|
332
|
-
if (userId) {
|
|
333
|
-
atUserIds.push(userId);
|
|
334
|
-
textParts.push(`@${seg.data.name || userId} `);
|
|
335
|
-
}
|
|
336
|
-
break;
|
|
337
|
-
case "image":
|
|
338
|
-
if (!hasMedia) {
|
|
339
|
-
hasMedia = true;
|
|
340
|
-
mediaContent = {
|
|
341
|
-
msgtype: "picture",
|
|
342
|
-
picture: {
|
|
343
|
-
picURL: seg.data.url || seg.data.file,
|
|
344
|
-
},
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
break;
|
|
348
|
-
case "markdown":
|
|
349
|
-
if (!hasMedia) {
|
|
350
|
-
hasMedia = true;
|
|
351
|
-
mediaContent = {
|
|
352
|
-
msgtype: "markdown",
|
|
353
|
-
markdown: {
|
|
354
|
-
title: seg.data.title || "消息",
|
|
355
|
-
text: seg.data.content || seg.data.text,
|
|
356
|
-
},
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
break;
|
|
360
|
-
case "link":
|
|
361
|
-
if (!hasMedia) {
|
|
362
|
-
hasMedia = true;
|
|
363
|
-
mediaContent = {
|
|
364
|
-
msgtype: "link",
|
|
365
|
-
link: {
|
|
366
|
-
title: seg.data.title || "链接",
|
|
367
|
-
text: seg.data.text || seg.data.content || "",
|
|
368
|
-
messageUrl: seg.data.url,
|
|
369
|
-
picUrl: seg.data.picUrl,
|
|
370
|
-
},
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
break;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (hasMedia && mediaContent)
|
|
378
|
-
return mediaContent;
|
|
379
|
-
const result = {
|
|
380
|
-
msgtype: "text",
|
|
381
|
-
text: { content: textParts.join("") },
|
|
382
|
-
};
|
|
383
|
-
if (atUserIds.length > 0) {
|
|
384
|
-
result.at = { atUserIds, isAtAll: false };
|
|
385
|
-
}
|
|
386
|
-
return result;
|
|
387
|
-
}
|
|
388
|
-
return { msgtype: "text", text: { content: String(content) } };
|
|
389
|
-
}
|
|
390
|
-
async $connect() {
|
|
391
|
-
try {
|
|
392
|
-
await this.refreshAccessToken();
|
|
393
|
-
this.$connected = true;
|
|
394
|
-
this.logger.debug(formatCompact({ endpoint: this.$config.name }));
|
|
395
|
-
this.logger.debug(formatCompact({ op: "webhook", path: this.$config.webhookPath }));
|
|
396
|
-
}
|
|
397
|
-
catch (error) {
|
|
398
|
-
this.logger.error("Failed to connect DingTalk bot:", error);
|
|
399
|
-
throw error;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
async $disconnect() {
|
|
403
|
-
try {
|
|
404
|
-
this.sessionWebhooks.clear();
|
|
405
|
-
this.$connected = false;
|
|
406
|
-
this.logger.debug(formatCompact({ op: "disconnect", endpoint: this.$config.name }));
|
|
407
|
-
}
|
|
408
|
-
catch (error) {
|
|
409
|
-
this.logger.error("Error disconnecting DingTalk bot:", error);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
async getUserInfo(userId) {
|
|
413
|
-
try {
|
|
414
|
-
const data = await this.request("/topapi/v2/user/get", {
|
|
415
|
-
method: "POST",
|
|
416
|
-
body: { userid: userId },
|
|
417
|
-
});
|
|
418
|
-
if (data.errcode === 0)
|
|
419
|
-
return data.result;
|
|
420
|
-
throw new Error(`Failed to get user info: ${data.errmsg}`);
|
|
421
|
-
}
|
|
422
|
-
catch (error) {
|
|
423
|
-
this.logger.error("Failed to get user info:", error);
|
|
424
|
-
return null;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
async getDepartmentUsers(deptId) {
|
|
428
|
-
try {
|
|
429
|
-
const data = await this.request("/topapi/user/listid", {
|
|
430
|
-
method: "POST",
|
|
431
|
-
body: { dept_id: deptId },
|
|
432
|
-
});
|
|
433
|
-
if (data.errcode === 0)
|
|
434
|
-
return data.result.userid_list || [];
|
|
435
|
-
throw new Error(`Failed to get department users: ${data.errmsg}`);
|
|
436
|
-
}
|
|
437
|
-
catch (error) {
|
|
438
|
-
this.logger.error("Failed to get department users:", error);
|
|
439
|
-
return [];
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
async sendWorkNotice(userIdList, content) {
|
|
443
|
-
try {
|
|
444
|
-
const data = await this.request("/topapi/message/corpconversation/asyncsend_v2", {
|
|
445
|
-
method: "POST",
|
|
446
|
-
body: {
|
|
447
|
-
agent_id: this.$config.robotCode,
|
|
448
|
-
userid_list: userIdList.join(","),
|
|
449
|
-
msg: content,
|
|
450
|
-
},
|
|
451
|
-
});
|
|
452
|
-
if (data.errcode === 0) {
|
|
453
|
-
this.logger.debug("Work notice sent successfully");
|
|
454
|
-
return true;
|
|
455
|
-
}
|
|
456
|
-
throw new Error(`Failed to send work notice: ${data.errmsg}`);
|
|
457
|
-
}
|
|
458
|
-
catch (error) {
|
|
459
|
-
this.logger.error("Failed to send work notice:", error);
|
|
460
|
-
return false;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
async getDepartmentList(deptId = 1) {
|
|
464
|
-
try {
|
|
465
|
-
const data = await this.request("/topapi/v2/department/listsub", {
|
|
466
|
-
method: "POST",
|
|
467
|
-
body: { dept_id: deptId },
|
|
468
|
-
});
|
|
469
|
-
if (data.errcode === 0)
|
|
470
|
-
return data.result || [];
|
|
471
|
-
throw new Error(`Failed to get department list: ${data.errmsg}`);
|
|
472
|
-
}
|
|
473
|
-
catch (error) {
|
|
474
|
-
this.logger.error("Failed to get department list:", error);
|
|
475
|
-
return [];
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
async getDepartmentInfo(deptId) {
|
|
479
|
-
try {
|
|
480
|
-
const data = await this.request("/topapi/v2/department/get", {
|
|
481
|
-
method: "POST",
|
|
482
|
-
body: { dept_id: deptId },
|
|
483
|
-
});
|
|
484
|
-
if (data.errcode === 0)
|
|
485
|
-
return data.result;
|
|
486
|
-
throw new Error(`Failed to get department info: ${data.errmsg}`);
|
|
487
|
-
}
|
|
488
|
-
catch (error) {
|
|
489
|
-
this.logger.error("Failed to get department info:", error);
|
|
490
|
-
return null;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
async createChat(name, ownerUserId, userIdList) {
|
|
494
|
-
try {
|
|
495
|
-
const data = await this.request("/topapi/chat/create", {
|
|
496
|
-
method: "POST",
|
|
497
|
-
body: {
|
|
498
|
-
name,
|
|
499
|
-
owner: ownerUserId,
|
|
500
|
-
useridlist: userIdList,
|
|
501
|
-
},
|
|
502
|
-
});
|
|
503
|
-
if (data.errcode === 0) {
|
|
504
|
-
this.logger.debug(formatCompact({ op: "create_chat", chat: data.chatid }));
|
|
505
|
-
return data.chatid;
|
|
506
|
-
}
|
|
507
|
-
throw new Error(`Failed to create chat: ${data.errmsg}`);
|
|
508
|
-
}
|
|
509
|
-
catch (error) {
|
|
510
|
-
this.logger.error("Failed to create chat:", error);
|
|
511
|
-
return null;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
async getChatInfo(chatId) {
|
|
515
|
-
try {
|
|
516
|
-
const data = await this.request("/topapi/chat/get", {
|
|
517
|
-
method: "POST",
|
|
518
|
-
body: { chatid: chatId },
|
|
519
|
-
});
|
|
520
|
-
if (data.errcode === 0)
|
|
521
|
-
return data.chat_info;
|
|
522
|
-
throw new Error(`Failed to get chat info: ${data.errmsg}`);
|
|
523
|
-
}
|
|
524
|
-
catch (error) {
|
|
525
|
-
this.logger.error("Failed to get chat info:", error);
|
|
526
|
-
return null;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
async updateChat(chatId, options) {
|
|
530
|
-
try {
|
|
531
|
-
const data = await this.request("/topapi/chat/update", {
|
|
532
|
-
method: "POST",
|
|
533
|
-
body: { chatid: chatId, ...options },
|
|
534
|
-
});
|
|
535
|
-
if (data.errcode === 0) {
|
|
536
|
-
this.logger.debug(formatCompact({ op: "update_chat", chat: chatId }));
|
|
537
|
-
return true;
|
|
538
|
-
}
|
|
539
|
-
throw new Error(`Failed to update chat: ${data.errmsg}`);
|
|
540
|
-
}
|
|
541
|
-
catch (error) {
|
|
542
|
-
this.logger.error("Failed to update chat:", error);
|
|
543
|
-
return false;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
//# sourceMappingURL=endpoint.js.map
|