@soimy/dingtalk 3.0.0-beta.1 → 3.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/README.md +68 -3
- package/package.json +1 -1
- package/src/group-members-store.ts +45 -0
- package/src/inbound-handler.ts +1 -42
package/README.md
CHANGED
|
@@ -44,10 +44,70 @@ openclaw plugins install -l .
|
|
|
44
44
|
2. 确保包含 `index.ts`, `openclaw.plugin.json` 和 `package.json`。
|
|
45
45
|
3. 运行 `openclaw plugins list` 确认 `dingtalk` 已显示在列表中。
|
|
46
46
|
|
|
47
|
+
### 安装后必做:配置插件信任白名单(`plugins.allow`)
|
|
48
|
+
|
|
49
|
+
从 OpenClaw 新版本开始,如果发现了非内置插件且 `plugins.allow` 为空,会提示:
|
|
50
|
+
|
|
51
|
+
```text
|
|
52
|
+
[plugins] plugins.allow is empty; discovered non-bundled plugins may auto-load ...
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
这是一条安全告警(不是安装失败),建议显式写入你信任的插件 id。
|
|
56
|
+
|
|
57
|
+
#### 步骤 1:确认插件 id
|
|
58
|
+
|
|
59
|
+
本插件 id 固定为:`dingtalk`(定义于 `openclaw.plugin.json`)。
|
|
60
|
+
|
|
61
|
+
也可用下面命令查看已发现插件:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
openclaw plugins list
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### 步骤 2:在 `~/.openclaw/openclaw.json` 添加 `plugins.allow`
|
|
68
|
+
|
|
69
|
+
```json5
|
|
70
|
+
{
|
|
71
|
+
"plugins": {
|
|
72
|
+
"enabled": true,
|
|
73
|
+
"allow": ["dingtalk"]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
如果你还有其他已安装且需要启用的插件,请一并加入,例如:
|
|
79
|
+
|
|
80
|
+
```json5
|
|
81
|
+
{
|
|
82
|
+
"plugins": {
|
|
83
|
+
"allow": ["dingtalk", "telegram", "voice-call"]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
#### 步骤 3:重启 Gateway
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
openclaw gateway restart
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> 注意:如果你之前已经配置过 `plugins.allow`,但没有 `dingtalk`,那么插件不会被加载。请把 `dingtalk` 加入该列表。
|
|
95
|
+
|
|
47
96
|
## 更新
|
|
48
97
|
|
|
98
|
+
`openclaw plugins update` 使用插件 id(不是 npm 包名),并且仅适用于 npm 安装来源。
|
|
99
|
+
|
|
100
|
+
如果你是通过 npm 安装本插件:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
openclaw plugins update dingtalk
|
|
49
104
|
```
|
|
50
|
-
|
|
105
|
+
|
|
106
|
+
如果你是本地源码/链接安装(`openclaw plugins install -l .`),请在插件目录更新代码后重启 Gateway:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
git pull
|
|
110
|
+
openclaw gateway restart
|
|
51
111
|
```
|
|
52
112
|
|
|
53
113
|
## 配置
|
|
@@ -140,12 +200,17 @@ openclaw configure --section channels
|
|
|
140
200
|
|
|
141
201
|
### 方法 2:手动配置文件
|
|
142
202
|
|
|
143
|
-
在 `~/.openclaw/openclaw.json`
|
|
203
|
+
在 `~/.openclaw/openclaw.json` 中添加(仅作参考,交互式配置会自动生成):
|
|
144
204
|
|
|
145
|
-
>
|
|
205
|
+
> 至少包含 `plugins.allow` 和 `channels.dingtalk` 两部分,内容参考上文钉钉开发者配置指南
|
|
146
206
|
|
|
147
207
|
```json5
|
|
148
208
|
{
|
|
209
|
+
"plugins": {
|
|
210
|
+
"enabled": true,
|
|
211
|
+
"allow": ["dingtalk"]
|
|
212
|
+
},
|
|
213
|
+
|
|
149
214
|
...
|
|
150
215
|
"channels": {
|
|
151
216
|
"telegram": { ... },
|
package/package.json
CHANGED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
function groupMembersFilePath(storePath: string, groupId: string): string {
|
|
5
|
+
const dir = path.join(path.dirname(storePath), "dingtalk-members");
|
|
6
|
+
const safeId = groupId.replace(/\+/g, "-").replace(/\//g, "_");
|
|
7
|
+
return path.join(dir, `${safeId}.json`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function noteGroupMember(
|
|
11
|
+
storePath: string,
|
|
12
|
+
groupId: string,
|
|
13
|
+
userId: string,
|
|
14
|
+
name: string,
|
|
15
|
+
): void {
|
|
16
|
+
if (!userId || !name) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const filePath = groupMembersFilePath(storePath, groupId);
|
|
20
|
+
let roster: Record<string, string> = {};
|
|
21
|
+
try {
|
|
22
|
+
roster = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
23
|
+
} catch {}
|
|
24
|
+
if (roster[userId] === name) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
roster[userId] = name;
|
|
28
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
29
|
+
fs.writeFileSync(filePath, JSON.stringify(roster, null, 2));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function formatGroupMembers(storePath: string, groupId: string): string | undefined {
|
|
33
|
+
const filePath = groupMembersFilePath(storePath, groupId);
|
|
34
|
+
let roster: Record<string, string> = {};
|
|
35
|
+
try {
|
|
36
|
+
roster = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
37
|
+
} catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const entries = Object.entries(roster);
|
|
41
|
+
if (entries.length === 0) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
return entries.map(([id, name]) => `${name} (${id})`).join(", ");
|
|
45
|
+
}
|
package/src/inbound-handler.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import * as fs from "node:fs";
|
|
2
|
-
import * as path from "node:path";
|
|
3
1
|
import axios from "axios";
|
|
4
2
|
import { normalizeAllowFrom, isSenderAllowed, isSenderGroupAllowed } from "./access-control";
|
|
5
3
|
import { getAccessToken } from "./auth";
|
|
@@ -14,6 +12,7 @@ import {
|
|
|
14
12
|
streamAICard,
|
|
15
13
|
} from "./card-service";
|
|
16
14
|
import { resolveGroupConfig } from "./config";
|
|
15
|
+
import { formatGroupMembers, noteGroupMember } from "./group-members-store";
|
|
17
16
|
import { setCurrentLogger } from "./logger-context";
|
|
18
17
|
import { extractMessageContent } from "./message-utils";
|
|
19
18
|
import { registerPeerId } from "./peer-id-registry";
|
|
@@ -23,46 +22,6 @@ import type { DingTalkConfig, HandleDingTalkMessageParams, MediaFile } from "./t
|
|
|
23
22
|
import { AICardStatus } from "./types";
|
|
24
23
|
import { maskSensitiveData } from "./utils";
|
|
25
24
|
|
|
26
|
-
// ============ Group Members Persistence ============
|
|
27
|
-
|
|
28
|
-
function groupMembersFilePath(storePath: string, groupId: string): string {
|
|
29
|
-
const dir = path.join(path.dirname(storePath), "dingtalk-members");
|
|
30
|
-
const safeId = groupId.replace(/\+/g, "-").replace(/\//g, "_");
|
|
31
|
-
return path.join(dir, `${safeId}.json`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function noteGroupMember(storePath: string, groupId: string, userId: string, name: string): void {
|
|
35
|
-
if (!userId || !name) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
const filePath = groupMembersFilePath(storePath, groupId);
|
|
39
|
-
let roster: Record<string, string> = {};
|
|
40
|
-
try {
|
|
41
|
-
roster = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
42
|
-
} catch {}
|
|
43
|
-
if (roster[userId] === name) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
roster[userId] = name;
|
|
47
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
48
|
-
fs.writeFileSync(filePath, JSON.stringify(roster, null, 2));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function formatGroupMembers(storePath: string, groupId: string): string | undefined {
|
|
52
|
-
const filePath = groupMembersFilePath(storePath, groupId);
|
|
53
|
-
let roster: Record<string, string> = {};
|
|
54
|
-
try {
|
|
55
|
-
roster = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
56
|
-
} catch {
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
const entries = Object.entries(roster);
|
|
60
|
-
if (entries.length === 0) {
|
|
61
|
-
return undefined;
|
|
62
|
-
}
|
|
63
|
-
return entries.map(([id, name]) => `${name} (${id})`).join(", ");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
25
|
/**
|
|
67
26
|
* Download DingTalk media file via runtime media service (sandbox-compatible).
|
|
68
27
|
* Files are stored in the global media inbound directory.
|