@huo15/dingtalk-connector-pro 1.0.0 → 1.0.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/dist/hooks/init.js +16 -0
- package/hooks/init.js +18 -0
- package/package.json +25 -11
- package/CHANGELOG.md +0 -485
- package/docs/AGENT_ROUTING.md +0 -335
- package/docs/DEAP_AGENT_GUIDE.en.md +0 -115
- package/docs/DEAP_AGENT_GUIDE.md +0 -115
- package/docs/images/dingtalk.svg +0 -1
- package/docs/images/image-1.png +0 -0
- package/docs/images/image-2.png +0 -0
- package/docs/images/image-3.png +0 -0
- package/docs/images/image-4.png +0 -0
- package/docs/images/image-5.png +0 -0
- package/docs/images/image-6.png +0 -0
- package/docs/images/image-7.png +0 -0
- package/install-beta.sh +0 -438
- package/install-npm.sh +0 -167
- package/openclaw.plugin.json +0 -498
- package/src/channel.ts +0 -463
- package/src/config/accounts.ts +0 -242
- package/src/config/schema.ts +0 -148
- package/src/core/connection.ts +0 -722
- package/src/core/message-handler.ts +0 -1700
- package/src/core/provider.ts +0 -111
- package/src/core/state.ts +0 -54
- package/src/directory.ts +0 -95
- package/src/docs.ts +0 -293
- package/src/gateway-methods.ts +0 -404
- package/src/onboarding.ts +0 -413
- package/src/policy.ts +0 -32
- package/src/probe.ts +0 -212
- package/src/reply-dispatcher.ts +0 -630
- package/src/runtime.ts +0 -32
- package/src/sdk/helpers.ts +0 -322
- package/src/sdk/types.ts +0 -513
- package/src/secret-input.ts +0 -19
- package/src/services/media/audio.ts +0 -54
- package/src/services/media/chunk-upload.ts +0 -296
- package/src/services/media/common.ts +0 -155
- package/src/services/media/file.ts +0 -70
- package/src/services/media/image.ts +0 -81
- package/src/services/media/index.ts +0 -10
- package/src/services/media/video.ts +0 -162
- package/src/services/media.ts +0 -1136
- package/src/services/messaging/card.ts +0 -342
- package/src/services/messaging/index.ts +0 -17
- package/src/services/messaging/send.ts +0 -141
- package/src/services/messaging.ts +0 -1013
- package/src/targets.ts +0 -45
- package/src/types/index.ts +0 -59
- package/src/utils/agent.ts +0 -63
- package/src/utils/async.ts +0 -51
- package/src/utils/constants.ts +0 -27
- package/src/utils/http-client.ts +0 -37
- package/src/utils/index.ts +0 -8
- package/src/utils/logger.ts +0 -78
- package/src/utils/session.ts +0 -147
- package/src/utils/token.ts +0 -93
- package/src/utils/utils-legacy.ts +0 -454
- package/tsconfig.json +0 -20
package/src/core/provider.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 钉钉消息流 Provider 入口
|
|
3
|
-
*
|
|
4
|
-
* 职责:
|
|
5
|
-
* - 提供 monitorDingtalkProvider 函数作为钉钉消息流的统一入口
|
|
6
|
-
* - 协调单账号和多账号监控场景
|
|
7
|
-
* - 并行导入连接层和消息处理模块,避免循环依赖
|
|
8
|
-
*
|
|
9
|
-
* 主要功能:
|
|
10
|
-
* - 根据 accountId 参数决定启动单账号或所有账号
|
|
11
|
-
* - 验证账号配置状态
|
|
12
|
-
* - 并行启动多个账号的消息流连接
|
|
13
|
-
*/
|
|
14
|
-
import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
|
15
|
-
import * as monitorState from "./state";
|
|
16
|
-
import { createLogger } from "../utils/logger";
|
|
17
|
-
|
|
18
|
-
// 只解构 monitorState 的导出
|
|
19
|
-
const {
|
|
20
|
-
clearDingtalkWebhookRateLimitStateForTest,
|
|
21
|
-
getDingtalkWebhookRateLimitStateSizeForTest,
|
|
22
|
-
isWebhookRateLimitedForTest,
|
|
23
|
-
stopDingtalkMonitorState,
|
|
24
|
-
} = monitorState;
|
|
25
|
-
|
|
26
|
-
export type MonitorDingtalkOpts = {
|
|
27
|
-
config?: ClawdbotConfig;
|
|
28
|
-
runtime?: RuntimeEnv;
|
|
29
|
-
abortSignal?: AbortSignal;
|
|
30
|
-
accountId?: string;
|
|
31
|
-
/** 可选:连接状态变更时回调,用于更新 UI 显示的 Connected / Last inbound 字段 */
|
|
32
|
-
onStatusChange?: (patch: Record<string, unknown>) => void;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export {
|
|
36
|
-
clearDingtalkWebhookRateLimitStateForTest,
|
|
37
|
-
getDingtalkWebhookRateLimitStateSizeForTest,
|
|
38
|
-
isWebhookRateLimitedForTest,
|
|
39
|
-
} from "./state";
|
|
40
|
-
|
|
41
|
-
// 只导出类型,不 re-export 函数(避免循环依赖)
|
|
42
|
-
export type { DingtalkReactionCreatedEvent } from "./connection";
|
|
43
|
-
|
|
44
|
-
export async function monitorDingtalkProvider(opts: MonitorDingtalkOpts = {}): Promise<void> {
|
|
45
|
-
const cfg = opts.config;
|
|
46
|
-
if (!cfg) {
|
|
47
|
-
throw new Error("Config is required for DingTalk monitor");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const log = createLogger(cfg.channels?.["dingtalk-connector"]?.debug ?? false);
|
|
51
|
-
|
|
52
|
-
// 并行导入所有模块(无循环依赖,可以并行)
|
|
53
|
-
const [accountsModule, monitorAccountModule, monitorSingleModule] = await Promise.all([
|
|
54
|
-
import("../config/accounts"),
|
|
55
|
-
import("./message-handler"),
|
|
56
|
-
import("./connection"),
|
|
57
|
-
]);
|
|
58
|
-
|
|
59
|
-
const { resolveDingtalkAccount, listEnabledDingtalkAccounts } = accountsModule;
|
|
60
|
-
const { handleDingTalkMessage } = monitorAccountModule;
|
|
61
|
-
const { monitorSingleAccount, resolveReactionSyntheticEvent } = monitorSingleModule;
|
|
62
|
-
|
|
63
|
-
if (opts.accountId) {
|
|
64
|
-
const account = resolveDingtalkAccount({ cfg, accountId: opts.accountId });
|
|
65
|
-
if (!account.enabled || !account.configured) {
|
|
66
|
-
throw new Error(`DingTalk account "${opts.accountId}" not configured or disabled`);
|
|
67
|
-
}
|
|
68
|
-
return monitorSingleAccount({
|
|
69
|
-
cfg,
|
|
70
|
-
account,
|
|
71
|
-
runtime: opts.runtime,
|
|
72
|
-
abortSignal: opts.abortSignal,
|
|
73
|
-
messageHandler: handleDingTalkMessage,
|
|
74
|
-
onStatusChange: opts.onStatusChange,
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const accounts = listEnabledDingtalkAccounts(cfg);
|
|
79
|
-
if (accounts.length === 0) {
|
|
80
|
-
throw new Error("No enabled DingTalk accounts configured");
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
log?.info?.(
|
|
84
|
-
`dingtalk-connector: starting ${accounts.length} account(s): ${accounts.map((a) => a.accountId).join(", ")}`,
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
const monitorPromises: Promise<void>[] = [];
|
|
88
|
-
for (const account of accounts) {
|
|
89
|
-
if (opts.abortSignal?.aborted) {
|
|
90
|
-
log?.info?.("dingtalk-connector: abort signal received during startup preflight; stopping startup");
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
monitorPromises.push(
|
|
95
|
-
monitorSingleAccount({
|
|
96
|
-
cfg,
|
|
97
|
-
account,
|
|
98
|
-
runtime: opts.runtime,
|
|
99
|
-
abortSignal: opts.abortSignal,
|
|
100
|
-
messageHandler: handleDingTalkMessage,
|
|
101
|
-
onStatusChange: opts.onStatusChange,
|
|
102
|
-
}),
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
await Promise.all(monitorPromises);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function stopDingtalkMonitor(accountId?: string): void {
|
|
110
|
-
stopDingtalkMonitorState(accountId);
|
|
111
|
-
}
|
package/src/core/state.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 钉钉消息流状态管理
|
|
3
|
-
*
|
|
4
|
-
* 职责:
|
|
5
|
-
* - 管理每个钉钉账号的运行状态
|
|
6
|
-
* - 存储 AbortController 用于优雅停止消息流
|
|
7
|
-
* - 提供测试工具函数
|
|
8
|
-
*
|
|
9
|
-
* 核心功能:
|
|
10
|
-
* - setDingtalkMonitorState: 设置账号运行状态
|
|
11
|
-
* - getDingtalkMonitorState: 获取账号运行状态
|
|
12
|
-
* - stopDingtalkMonitorState: 停止单个或多个账号的消息流
|
|
13
|
-
* - 测试工具:clearDingtalkWebhookRateLimitStateForTest 等
|
|
14
|
-
*/
|
|
15
|
-
const monitorState = new Map<string, { running: boolean; abortController?: AbortController }>();
|
|
16
|
-
|
|
17
|
-
export function setDingtalkMonitorState(accountId: string, state: { running: boolean; abortController?: AbortController }): void {
|
|
18
|
-
monitorState.set(accountId, state);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function getDingtalkMonitorState(accountId: string): { running: boolean; abortController?: AbortController } | undefined {
|
|
22
|
-
return monitorState.get(accountId);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function stopDingtalkMonitorState(accountId?: string): void {
|
|
26
|
-
if (accountId) {
|
|
27
|
-
const state = monitorState.get(accountId);
|
|
28
|
-
if (state?.abortController) {
|
|
29
|
-
state.abortController.abort();
|
|
30
|
-
}
|
|
31
|
-
monitorState.delete(accountId);
|
|
32
|
-
} else {
|
|
33
|
-
// Stop all monitors
|
|
34
|
-
for (const [id, state] of monitorState.entries()) {
|
|
35
|
-
if (state.abortController) {
|
|
36
|
-
state.abortController.abort();
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
monitorState.clear();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Test utilities
|
|
44
|
-
export function clearDingtalkWebhookRateLimitStateForTest(): void {
|
|
45
|
-
// DingTalk doesn't use webhook rate limiting
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function getDingtalkWebhookRateLimitStateSizeForTest(): number {
|
|
49
|
-
return 0;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function isWebhookRateLimitedForTest(): boolean {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
package/src/directory.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
-
import { resolveDingtalkAccount } from "./config/accounts.ts";
|
|
3
|
-
import { normalizeDingtalkTarget } from "./targets.ts";
|
|
4
|
-
|
|
5
|
-
export type DingtalkDirectoryPeer = {
|
|
6
|
-
kind: "user";
|
|
7
|
-
id: string;
|
|
8
|
-
name?: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type DingtalkDirectoryGroup = {
|
|
12
|
-
kind: "group";
|
|
13
|
-
id: string;
|
|
14
|
-
name?: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export async function listDingtalkDirectoryPeers(params: {
|
|
18
|
-
cfg: ClawdbotConfig;
|
|
19
|
-
query?: string;
|
|
20
|
-
limit?: number;
|
|
21
|
-
accountId?: string;
|
|
22
|
-
}): Promise<DingtalkDirectoryPeer[]> {
|
|
23
|
-
const account = resolveDingtalkAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
24
|
-
const dingtalkCfg = account.config;
|
|
25
|
-
const q = params.query?.trim().toLowerCase() || "";
|
|
26
|
-
const ids = new Set<string>();
|
|
27
|
-
|
|
28
|
-
for (const entry of dingtalkCfg?.allowFrom ?? []) {
|
|
29
|
-
const trimmed = String(entry).trim();
|
|
30
|
-
if (trimmed && trimmed !== "*") {
|
|
31
|
-
ids.add(trimmed);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return Array.from(ids)
|
|
36
|
-
.map((raw) => raw.trim())
|
|
37
|
-
.filter(Boolean)
|
|
38
|
-
.map((raw) => normalizeDingtalkTarget(raw) ?? raw)
|
|
39
|
-
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
|
|
40
|
-
.slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
|
|
41
|
-
.map((id) => ({ kind: "user" as const, id }));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function listDingtalkDirectoryGroups(params: {
|
|
45
|
-
cfg: ClawdbotConfig;
|
|
46
|
-
query?: string;
|
|
47
|
-
limit?: number;
|
|
48
|
-
accountId?: string;
|
|
49
|
-
}): Promise<DingtalkDirectoryGroup[]> {
|
|
50
|
-
const account = resolveDingtalkAccount({ cfg: params.cfg, accountId: params.accountId });
|
|
51
|
-
const dingtalkCfg = account.config;
|
|
52
|
-
const q = params.query?.trim().toLowerCase() || "";
|
|
53
|
-
const ids = new Set<string>();
|
|
54
|
-
|
|
55
|
-
for (const groupId of Object.keys(dingtalkCfg?.groups ?? {})) {
|
|
56
|
-
const trimmed = groupId.trim();
|
|
57
|
-
if (trimmed && trimmed !== "*") {
|
|
58
|
-
ids.add(trimmed);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
for (const entry of dingtalkCfg?.groupAllowFrom ?? []) {
|
|
63
|
-
const trimmed = String(entry).trim();
|
|
64
|
-
if (trimmed && trimmed !== "*") {
|
|
65
|
-
ids.add(trimmed);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return Array.from(ids)
|
|
70
|
-
.map((raw) => raw.trim())
|
|
71
|
-
.filter(Boolean)
|
|
72
|
-
.filter((id) => (q ? id.toLowerCase().includes(q) : true))
|
|
73
|
-
.slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
|
|
74
|
-
.map((id) => ({ kind: "group" as const, id }));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export async function listDingtalkDirectoryPeersLive(params: {
|
|
78
|
-
cfg: ClawdbotConfig;
|
|
79
|
-
query?: string;
|
|
80
|
-
limit?: number;
|
|
81
|
-
accountId?: string;
|
|
82
|
-
}): Promise<DingtalkDirectoryPeer[]> {
|
|
83
|
-
// DingTalk doesn't have a public API to list users, so we fall back to static list
|
|
84
|
-
return listDingtalkDirectoryPeers(params);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export async function listDingtalkDirectoryGroupsLive(params: {
|
|
88
|
-
cfg: ClawdbotConfig;
|
|
89
|
-
query?: string;
|
|
90
|
-
limit?: number;
|
|
91
|
-
accountId?: string;
|
|
92
|
-
}): Promise<DingtalkDirectoryGroup[]> {
|
|
93
|
-
// DingTalk doesn't have a public API to list groups, so we fall back to static list
|
|
94
|
-
return listDingtalkDirectoryGroups(params);
|
|
95
|
-
}
|
package/src/docs.ts
DELETED
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 钉钉文档 API 客户端
|
|
3
|
-
* 支持读写钉钉在线文档(文档、表格等)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { DingtalkConfig } from './types/index.ts';
|
|
7
|
-
import { getAccessToken, DINGTALK_API } from './utils/index.ts';
|
|
8
|
-
import { dingtalkHttp } from './utils/http-client.ts';
|
|
9
|
-
|
|
10
|
-
// ============ 类型定义 ============
|
|
11
|
-
|
|
12
|
-
/** 文档信息接口 */
|
|
13
|
-
export interface DocInfo {
|
|
14
|
-
docId: string;
|
|
15
|
-
title: string;
|
|
16
|
-
docType: string;
|
|
17
|
-
creatorId?: string;
|
|
18
|
-
updatedAt?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/** 文档内容块 */
|
|
22
|
-
interface DocBlock {
|
|
23
|
-
blockId: string;
|
|
24
|
-
blockType: string;
|
|
25
|
-
text?: string;
|
|
26
|
-
children?: DocBlock[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ============ 钉钉文档客户端类 ============
|
|
30
|
-
|
|
31
|
-
export class DingtalkDocsClient {
|
|
32
|
-
private config: DingtalkConfig;
|
|
33
|
-
private log?: any;
|
|
34
|
-
|
|
35
|
-
constructor(config: DingtalkConfig, log?: any) {
|
|
36
|
-
this.config = config;
|
|
37
|
-
this.log = log;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** 获取带鉴权的请求头 */
|
|
41
|
-
private async getHeaders(): Promise<Record<string, string>> {
|
|
42
|
-
const token = await getAccessToken(this.config);
|
|
43
|
-
return {
|
|
44
|
-
'x-acs-dingtalk-access-token': token,
|
|
45
|
-
'Content-Type': 'application/json',
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* 获取文档元信息
|
|
51
|
-
*/
|
|
52
|
-
async getDocInfo(spaceId: string, docId: string): Promise<DocInfo | null> {
|
|
53
|
-
try {
|
|
54
|
-
const headers = await this.getHeaders();
|
|
55
|
-
this.log?.info?.(`[DingTalk][Docs] 获取文档信息: spaceId=${spaceId}, docId=${docId}`);
|
|
56
|
-
|
|
57
|
-
const resp = await dingtalkHttp.get(
|
|
58
|
-
`${DINGTALK_API}/v1.0/doc/spaces/${spaceId}/docs/${docId}`,
|
|
59
|
-
{ headers, timeout: 10_000 },
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
const data = resp.data;
|
|
63
|
-
this.log?.info?.(`[DingTalk][Docs] 文档信息获取成功: title=${data?.title}`);
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
docId: data.docId || docId,
|
|
67
|
-
title: data.title || '',
|
|
68
|
-
docType: data.docType || 'unknown',
|
|
69
|
-
creatorId: data.creatorId,
|
|
70
|
-
updatedAt: data.updatedAt,
|
|
71
|
-
};
|
|
72
|
-
} catch (err: any) {
|
|
73
|
-
this.log?.error?.(`[DingTalk][Docs] 获取文档信息失败: ${err.message}`);
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* 读取文档内容(通过 v2.0/wiki 节点 API)
|
|
80
|
-
*/
|
|
81
|
-
async readDoc(nodeId: string, operatorId?: string): Promise<string | null> {
|
|
82
|
-
try {
|
|
83
|
-
const headers = await this.getHeaders();
|
|
84
|
-
this.log?.info?.(`[DingTalk][Docs] 读取知识库节点: nodeId=${nodeId}, operatorId=${operatorId}`);
|
|
85
|
-
|
|
86
|
-
if (!operatorId) {
|
|
87
|
-
this.log?.error?.('[DingTalk][Docs] readDoc 需要 operatorId(unionId)');
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const resp = await dingtalkHttp.get(
|
|
92
|
-
`${DINGTALK_API}/v2.0/wiki/nodes/${nodeId}/content`,
|
|
93
|
-
{ headers, params: { operatorId }, timeout: 30_000 },
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
const node = resp.data?.node || resp.data;
|
|
97
|
-
const name = node.name || '未知文档';
|
|
98
|
-
const category = node.category || 'unknown';
|
|
99
|
-
const url = node.url || '';
|
|
100
|
-
const workspaceId = node.workspaceId || '';
|
|
101
|
-
|
|
102
|
-
const content = [
|
|
103
|
-
`文档名: ${name}`,
|
|
104
|
-
`类型: ${category}`,
|
|
105
|
-
`URL: ${url}`,
|
|
106
|
-
`工作区: ${workspaceId}`,
|
|
107
|
-
].join('\n');
|
|
108
|
-
|
|
109
|
-
this.log?.info?.(`[DingTalk][Docs] 节点信息获取成功: name=${name}, category=${category}`);
|
|
110
|
-
return content;
|
|
111
|
-
} catch (err: any) {
|
|
112
|
-
this.log?.error?.(`[DingTalk][Docs] 读取节点失败: ${err.message}`);
|
|
113
|
-
if (err.response) {
|
|
114
|
-
this.log?.error?.(`[DingTalk][Docs] 错误详情: status=${err.response.status} data=${JSON.stringify(err.response.data)}`);
|
|
115
|
-
}
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* 从 block 树中递归提取纯文本内容
|
|
122
|
-
*/
|
|
123
|
-
private extractTextFromBlocks(blocks: DocBlock[]): string[] {
|
|
124
|
-
const result: string[] = [];
|
|
125
|
-
for (const block of blocks) {
|
|
126
|
-
if (block.text) {
|
|
127
|
-
result.push(block.text);
|
|
128
|
-
}
|
|
129
|
-
if (block.children && block.children.length > 0) {
|
|
130
|
-
result.push(...this.extractTextFromBlocks(block.children));
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return result;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* 向文档追加内容
|
|
138
|
-
*/
|
|
139
|
-
async appendToDoc(
|
|
140
|
-
docId: string,
|
|
141
|
-
content: string,
|
|
142
|
-
index: number = -1,
|
|
143
|
-
): Promise<boolean> {
|
|
144
|
-
try {
|
|
145
|
-
const headers = await this.getHeaders();
|
|
146
|
-
this.log?.info?.(`[DingTalk][Docs] 向文档追加内容: docId=${docId}, contentLen=${content.length}`);
|
|
147
|
-
|
|
148
|
-
const body = {
|
|
149
|
-
blockType: 'PARAGRAPH',
|
|
150
|
-
body: {
|
|
151
|
-
text: content,
|
|
152
|
-
},
|
|
153
|
-
index,
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
await dingtalkHttp.post(
|
|
157
|
-
`${DINGTALK_API}/v1.0/doc/documents/${docId}/blocks/root/children`,
|
|
158
|
-
body,
|
|
159
|
-
{ headers, timeout: 10_000 },
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
this.log?.info?.(`[DingTalk][Docs] 内容追加成功`);
|
|
163
|
-
return true;
|
|
164
|
-
} catch (err: any) {
|
|
165
|
-
this.log?.error?.(`[DingTalk][Docs] 追加内容失败: ${err.message}`);
|
|
166
|
-
if (err.response) {
|
|
167
|
-
this.log?.error?.(`[DingTalk][Docs] 错误详情: status=${err.response.status} data=${JSON.stringify(err.response.data)}`);
|
|
168
|
-
}
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* 创建新文档
|
|
175
|
-
*/
|
|
176
|
-
async createDoc(
|
|
177
|
-
spaceId: string,
|
|
178
|
-
title: string,
|
|
179
|
-
content?: string,
|
|
180
|
-
): Promise<DocInfo | null> {
|
|
181
|
-
try {
|
|
182
|
-
const headers = await this.getHeaders();
|
|
183
|
-
this.log?.info?.(`[DingTalk][Docs] 创建文档: spaceId=${spaceId}, title=${title}`);
|
|
184
|
-
|
|
185
|
-
const body: any = {
|
|
186
|
-
spaceId,
|
|
187
|
-
parentDentryId: '',
|
|
188
|
-
name: title,
|
|
189
|
-
docType: 'alidoc',
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
const resp = await dingtalkHttp.post(
|
|
193
|
-
`${DINGTALK_API}/v1.0/doc/spaces/${spaceId}/docs`,
|
|
194
|
-
body,
|
|
195
|
-
{ headers, timeout: 10_000 },
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
const data = resp.data;
|
|
199
|
-
this.log?.info?.(`[DingTalk][Docs] 文档创建成功: docId=${data?.docId}`);
|
|
200
|
-
|
|
201
|
-
const docInfo: DocInfo = {
|
|
202
|
-
docId: data.docId || data.dentryUuid || '',
|
|
203
|
-
title: title,
|
|
204
|
-
docType: data.docType || 'alidoc',
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
if (content && docInfo.docId) {
|
|
208
|
-
await this.appendToDoc(docInfo.docId, content);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return docInfo;
|
|
212
|
-
} catch (err: any) {
|
|
213
|
-
this.log?.error?.(`[DingTalk][Docs] 创建文档失败: ${err.message}`);
|
|
214
|
-
if (err.response) {
|
|
215
|
-
this.log?.error?.(`[DingTalk][Docs] 错误详情: status=${err.response.status} data=${JSON.stringify(err.response.data)}`);
|
|
216
|
-
}
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* 搜索文档
|
|
223
|
-
*/
|
|
224
|
-
async searchDocs(
|
|
225
|
-
keyword: string,
|
|
226
|
-
spaceId?: string,
|
|
227
|
-
): Promise<DocInfo[]> {
|
|
228
|
-
try {
|
|
229
|
-
const headers = await this.getHeaders();
|
|
230
|
-
this.log?.info?.(`[DingTalk][Docs] 搜索文档: keyword=${keyword}, spaceId=${spaceId || '全部'}`);
|
|
231
|
-
|
|
232
|
-
const body: any = { keyword, maxResults: 20 };
|
|
233
|
-
if (spaceId) body.spaceId = spaceId;
|
|
234
|
-
|
|
235
|
-
const resp = await dingtalkHttp.post(
|
|
236
|
-
`${DINGTALK_API}/v1.0/doc/docs/search`,
|
|
237
|
-
body,
|
|
238
|
-
{ headers, timeout: 10_000 },
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
const items = resp.data?.items || [];
|
|
242
|
-
const docs: DocInfo[] = items.map((item: any) => ({
|
|
243
|
-
docId: item.docId || item.dentryUuid || '',
|
|
244
|
-
title: item.name || item.title || '',
|
|
245
|
-
docType: item.docType || 'unknown',
|
|
246
|
-
creatorId: item.creatorId,
|
|
247
|
-
updatedAt: item.updatedAt,
|
|
248
|
-
}));
|
|
249
|
-
|
|
250
|
-
this.log?.info?.(`[DingTalk][Docs] 搜索到 ${docs.length} 个文档`);
|
|
251
|
-
return docs;
|
|
252
|
-
} catch (err: any) {
|
|
253
|
-
this.log?.error?.(`[DingTalk][Docs] 搜索文档失败: ${err.message}`);
|
|
254
|
-
return [];
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* 列出空间下的文档
|
|
260
|
-
*/
|
|
261
|
-
async listDocs(
|
|
262
|
-
spaceId: string,
|
|
263
|
-
parentId?: string,
|
|
264
|
-
): Promise<DocInfo[]> {
|
|
265
|
-
try {
|
|
266
|
-
const headers = await this.getHeaders();
|
|
267
|
-
this.log?.info?.(`[DingTalk][Docs] 列出文档: spaceId=${spaceId}, parentId=${parentId || '根目录'}`);
|
|
268
|
-
|
|
269
|
-
const params: any = { maxResults: 50 };
|
|
270
|
-
if (parentId) params.parentDentryId = parentId;
|
|
271
|
-
|
|
272
|
-
const resp = await dingtalkHttp.get(
|
|
273
|
-
`${DINGTALK_API}/v1.0/doc/spaces/${spaceId}/dentries`,
|
|
274
|
-
{ headers, params, timeout: 10_000 },
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
const items = resp.data?.items || [];
|
|
278
|
-
const docs: DocInfo[] = items.map((item: any) => ({
|
|
279
|
-
docId: item.dentryUuid || item.docId || '',
|
|
280
|
-
title: item.name || '',
|
|
281
|
-
docType: item.docType || item.dentryType || 'unknown',
|
|
282
|
-
creatorId: item.creatorId,
|
|
283
|
-
updatedAt: item.updatedAt,
|
|
284
|
-
}));
|
|
285
|
-
|
|
286
|
-
this.log?.info?.(`[DingTalk][Docs] 列出 ${docs.length} 个文档/目录`);
|
|
287
|
-
return docs;
|
|
288
|
-
} catch (err: any) {
|
|
289
|
-
this.log?.error?.(`[DingTalk][Docs] 列出文档失败: ${err.message}`);
|
|
290
|
-
return [];
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|