@zhin.js/adapter-dingtalk 1.0.80 → 1.1.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 +621 -0
- package/README.md +67 -333
- package/adapters/dingtalk.js +33 -0
- package/adapters/dingtalk.ts +38 -0
- package/agent/PERMITS.md +19 -0
- package/agent/tools/add_chat_members.ts +20 -0
- package/agent/tools/create_chat.ts +21 -0
- package/agent/tools/dept_info.ts +15 -0
- package/agent/tools/get_dept_users.ts +16 -0
- package/agent/tools/get_user.ts +15 -0
- package/agent/tools/list_departments.ts +16 -0
- package/agent/tools/send_work_notice.ts +18 -0
- package/agent/tools/update_chat.ts +25 -0
- package/commands/endpoint/add/[id].js +3 -0
- package/commands/endpoint/add/[id].ts +3 -0
- package/commands/endpoint/list.js +3 -0
- package/commands/endpoint/list.ts +3 -0
- package/commands/endpoint/remove/[id].js +3 -0
- package/commands/endpoint/remove/[id].ts +3 -0
- package/lib/client.d.ts +12 -0
- package/lib/client.js +2 -0
- package/lib/dingtalk-endpoint-commands.d.ts +1 -0
- package/lib/dingtalk-endpoint-commands.js +18 -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 +134 -0
- package/lib/protocol.js +265 -0
- package/lib/webhook.d.ts +13 -0
- package/lib/webhook.js +47 -0
- package/package.json +64 -18
- package/plugin.js +19 -0
- package/schema.json +92 -0
- package/src/client.ts +16 -0
- package/src/dingtalk-endpoint-commands.ts +19 -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 +385 -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/src/adapter.ts +0 -44
- package/src/bot.ts +0 -590
- package/src/types.ts +0 -56
- /package/{skills/dingtalk/SKILL.md → agent/skills/dingtalk.md} +0 -0
package/lib/client.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DingTalkClient } from './endpoint.js';
|
|
2
|
+
import type { DingTalkEvent } from './protocol.js';
|
|
3
|
+
export type DingtalkClientEventMap = Record<string, DingTalkEvent>;
|
|
4
|
+
declare module '@zhin.js/feature-kit' {
|
|
5
|
+
interface AdapterClientRegistry {
|
|
6
|
+
readonly dingtalk: {
|
|
7
|
+
readonly client: DingTalkClient;
|
|
8
|
+
readonly events: DingtalkClientEventMap;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export declare const dingtalkClient: import("@zhin.js/adapter").EndpointClientToken<DingTalkClient, DingtalkClientEventMap>;
|
package/lib/client.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const dingtalkEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage, string | undefined>>>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dingtalk.endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
|
|
3
|
+
* commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
|
|
4
|
+
*/
|
|
5
|
+
import { createEndpointCommands } from 'zhin.js/adapter';
|
|
6
|
+
import { defineCommand } from 'zhin.js/command';
|
|
7
|
+
import { dingtalkRuntimeStateToken } from './dingtalk-runtime-state.js';
|
|
8
|
+
export const dingtalkEndpointCommands = createEndpointCommands({
|
|
9
|
+
adapterKey: 'dingtalk',
|
|
10
|
+
adapterDisplayName: 'DingTalk',
|
|
11
|
+
fields: [
|
|
12
|
+
{ key: 'appKey', required: true, env: true, description: '钉钉 AppKey' },
|
|
13
|
+
{ key: 'appSecret', required: true, env: true, description: '钉钉 AppSecret' },
|
|
14
|
+
{ key: 'robotCode', required: true, env: true, description: '机器人 Code(出站 senderStaffId)' },
|
|
15
|
+
],
|
|
16
|
+
running: (use) => use(dingtalkRuntimeStateToken).endpoints.values(),
|
|
17
|
+
describeEntry: (entry) => `appKey: ${String(entry.appKey)}`,
|
|
18
|
+
}, defineCommand);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const dingtalkRuntimeStateToken: import("zhin.js").Token<import("@zhin.js/adapter").EndpointRuntimeState>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `dingtalk.endpoint` 命令共享(同一 owner generation)。
|
|
4
|
+
*/
|
|
5
|
+
import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
|
|
6
|
+
export const dingtalkRuntimeStateToken = defineEndpointRuntimeStateToken('dingtalk');
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Endpoint } from 'zhin.js/adapter';
|
|
2
|
+
/**
|
|
3
|
+
* DingTalkEndpoint — lifecycle, outbound, admit, OpenAPI helpers for agent tools.
|
|
4
|
+
*/
|
|
5
|
+
import { type EndpointSendRequest } from 'zhin.js/adapter';
|
|
6
|
+
import type { HttpHost } from '@zhin.js/host-http';
|
|
7
|
+
import type { CapabilityId } from 'zhin.js';
|
|
8
|
+
import { type DingTalkEvent, type DingTalkMessage, type ResolvedDingTalkConfig } from './protocol.js';
|
|
9
|
+
export type DingTalkFetch = (url: string, init?: {
|
|
10
|
+
readonly method?: string;
|
|
11
|
+
readonly headers?: Record<string, string>;
|
|
12
|
+
readonly body?: string;
|
|
13
|
+
}) => Promise<{
|
|
14
|
+
readonly ok: boolean;
|
|
15
|
+
readonly status: number;
|
|
16
|
+
text(): Promise<string>;
|
|
17
|
+
json(): Promise<unknown>;
|
|
18
|
+
}>;
|
|
19
|
+
export interface DingTalkEndpointOptions {
|
|
20
|
+
readonly id: CapabilityId;
|
|
21
|
+
readonly http: HttpHost;
|
|
22
|
+
readonly config: ResolvedDingTalkConfig;
|
|
23
|
+
readonly fetch?: DingTalkFetch;
|
|
24
|
+
}
|
|
25
|
+
export interface DingTalkClientApi {
|
|
26
|
+
getUserInfo(userId: string): Promise<unknown>;
|
|
27
|
+
getDepartmentUsers(deptId: number): Promise<unknown[]>;
|
|
28
|
+
sendWorkNotice(userIdList: string[], content: unknown): Promise<boolean>;
|
|
29
|
+
getDepartmentList(deptId?: number): Promise<unknown[]>;
|
|
30
|
+
getDepartmentInfo(deptId: number): Promise<unknown>;
|
|
31
|
+
createChat(name: string, ownerUserId: string, userIdList: string[]): Promise<string | null>;
|
|
32
|
+
getChatInfo(chatId: string): Promise<unknown>;
|
|
33
|
+
updateChat(chatId: string, options: {
|
|
34
|
+
name?: string;
|
|
35
|
+
owner?: string;
|
|
36
|
+
add_useridlist?: string[];
|
|
37
|
+
del_useridlist?: string[];
|
|
38
|
+
}): Promise<boolean>;
|
|
39
|
+
}
|
|
40
|
+
/** SDK-like DingTalk OpenAPI surface available on every Endpoint event. */
|
|
41
|
+
export declare class DingTalkClient implements DingTalkClientApi {
|
|
42
|
+
readonly api: DingTalkClientApi;
|
|
43
|
+
constructor(api: DingTalkClientApi);
|
|
44
|
+
getUserInfo: (userId: string) => Promise<unknown>;
|
|
45
|
+
getDepartmentUsers: (deptId: number) => Promise<unknown[]>;
|
|
46
|
+
sendWorkNotice: (userIds: string[], content: unknown) => Promise<boolean>;
|
|
47
|
+
getDepartmentList: (deptId?: number) => Promise<unknown[]>;
|
|
48
|
+
getDepartmentInfo: (deptId: number) => Promise<unknown>;
|
|
49
|
+
createChat: (name: string, owner: string, users: string[]) => Promise<string | null>;
|
|
50
|
+
getChatInfo: (chatId: string) => Promise<unknown>;
|
|
51
|
+
updateChat: (chatId: string, options: Parameters<DingTalkClientApi["updateChat"]>[1]) => Promise<boolean>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 钉钉机器人(webhook/stream 模式)无常规群列表 API——机器人不持有
|
|
55
|
+
* 「我所在的群」枚举面,仅能收发消息;
|
|
56
|
+
* 因此本 endpoint 不暴露 EndpointManagement(Console 社交面 RPC 对该平台保持未接线)。
|
|
57
|
+
*/
|
|
58
|
+
export declare class DingTalkEndpoint extends Endpoint<DingTalkClient> {
|
|
59
|
+
#private;
|
|
60
|
+
readonly client: DingTalkClient;
|
|
61
|
+
constructor(options: DingTalkEndpointOptions);
|
|
62
|
+
/** Used by webhook handler. */
|
|
63
|
+
get isOpen(): boolean;
|
|
64
|
+
get config(): ResolvedDingTalkConfig;
|
|
65
|
+
start(): Promise<void>;
|
|
66
|
+
open(): void;
|
|
67
|
+
close(): void;
|
|
68
|
+
stop(): Promise<void>;
|
|
69
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
70
|
+
/** Test / internal: admit a parsed event when open (non-webhook path). */
|
|
71
|
+
admit(event: DingTalkEvent | DingTalkMessage): void;
|
|
72
|
+
}
|
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 DingTalkAdapterConfig, 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';
|