@largezhou/ddingtalk 1.3.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 ADDED
@@ -0,0 +1,119 @@
1
+ # @largezhou/ddingtalk
2
+
3
+ OpenClaw 钉钉(DingTalk)渠道插件,使用 Stream 模式接入企业机器人。
4
+
5
+ ## 功能特点
6
+
7
+ - ✅ **Stream 模式**:无需公网 IP 和域名,开箱即用
8
+ - ✅ **私聊/群聊**:支持私聊,群聊(仅@机器人)
9
+ - ✅ **文本消息收发**:接收和发送文本消息
10
+ - ✅ **Markdown回复**:机器人回复 Markdown 格式
11
+ - ✅ **图片消息收发**:接收用户发送的图片,支持发送本地/远程图片
12
+ - ✅ **语音、视频、文件、图文混排**:接收用户发送语音、视频、文件、图文混排消息
13
+ - ✅ **回复文件**:支持回复文件,音频、视频等统一按文件发送(按语音、视频发送,需要获取时长、视频封面,以后再支持)
14
+ - ✅ **主动推送消息**:支持主动推送消息,可以配置提醒或定时任务
15
+ - ✅ **支持OpenClaw命令**:支持 /new、/compact 等 OpenClaw 官方命令
16
+
17
+ ## 安装
18
+
19
+ ```bash
20
+ # 从 GitHub 安装
21
+ openclaw plugins install https://github.com/largezhou/openclaw-dingtalk.git
22
+ ```
23
+
24
+ ## 前置准备
25
+
26
+ ### 1. 创建钉钉企业内部应用
27
+
28
+ 1. 登录 [钉钉开发者后台](https://open-dev.dingtalk.com/fe/app)
29
+ 2. 创建应用
30
+ 3. 记录 **AppKey** (ClientID) 和 **AppSecret** (ClientSecret)
31
+
32
+ ### 2. 开通机器人能力
33
+
34
+ 1. 在应用详情页,点击 **应用能力** -> **添加应用能力**
35
+ 2. 选择 **机器人**
36
+ 3. 填写机器人基本信息
37
+ 4. **重要**: 消息接收模式选择 **Stream 模式**
38
+ 5. 发布应用
39
+
40
+ ### 3. 配置应用权限
41
+
42
+ 在应用的权限管理中,确保开通以下权限:
43
+
44
+ - 企业内机器人发送消息权限
45
+ - 根据 downloadCode 获取机器人接收消息的下载链接(用于接收图片)
46
+
47
+ ## 配置
48
+
49
+ ### 方式一:交互式配置(推荐)
50
+
51
+ ```bash
52
+ openclaw channels add
53
+ ```
54
+
55
+ 选择 DingTalk,按提示输入 AppKey 和 AppSecret 即可。
56
+
57
+ ### 方式二:手动配置
58
+
59
+ 在 OpenClaw 配置文件 `~/.openclaw/openclaw.json` 中添加:
60
+
61
+ ```json
62
+ {
63
+ "channels": {
64
+ "ddingtalk": {
65
+ "enabled": true,
66
+ "clientId": "your_app_key",
67
+ "clientSecret": "your_app_secret",
68
+ "allowFrom": ["*"]
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### allowFrom 白名单
75
+
76
+ `allowFrom` 控制哪些用户可以与机器人交互并执行命令:
77
+
78
+ - **默认值**:`["*"]`(不配置的情况下,默认允许所有人)
79
+ - **指定用户**:填入钉钉用户的 `staffId`,只有白名单内的用户才能使用命令(如 `/compact`、`/new` 等),白名单外的用户消息会被忽略
80
+ - `allowFrom[0]` 同时作为主动推送消息(`openclaw send`)的默认目标
81
+
82
+ ```json
83
+ {
84
+ "allowFrom": ["用户ID_1", "用户ID_2"]
85
+ }
86
+ ```
87
+
88
+ ## Demo
89
+
90
+ 项目包含独立的 demo 示例,可以脱离 OpenClaw 框架单独测试钉钉机器人
91
+
92
+ ```bash
93
+ # 配置环境变量
94
+ cp .env.example .env
95
+ # 编辑 .env 填入 CLIENT_ID 和 CLIENT_SECRET
96
+
97
+ # 运行 demo
98
+ npm run demo
99
+ ```
100
+
101
+ ## 开发
102
+
103
+ ```bash
104
+ # 安装依赖
105
+ npm install
106
+
107
+ # 打包
108
+ npm pack
109
+ ```
110
+
111
+ ## 参考文档
112
+
113
+ - [钉钉开放平台 - Stream 模式说明](https://opensource.dingtalk.com/developerpedia/docs/learn/stream/overview)
114
+ - [钉钉开放平台 - 机器人接收消息](https://open.dingtalk.com/document/orgapp/robot-receive-message)
115
+ - [钉钉开放平台 - 机器人发送消息](https://open.dingtalk.com/document/orgapp/robot-send-message)
116
+
117
+ ## License
118
+
119
+ MIT
package/index.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+ import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
3
+ import { dingtalkPlugin } from "./src/channel.js";
4
+ import { setDingTalkRuntime } from "./src/runtime.js";
5
+ import { PLUGIN_ID } from "./src/constants.js";
6
+
7
+ const plugin: {
8
+ id: string;
9
+ name: string;
10
+ description: string;
11
+ configSchema: ReturnType<typeof emptyPluginConfigSchema>;
12
+ register: (api: OpenClawPluginApi) => void;
13
+ } = {
14
+ id: PLUGIN_ID,
15
+ name: "DingTalk",
16
+ description: "DingTalk (钉钉) enterprise robot channel plugin",
17
+ configSchema: emptyPluginConfigSchema(),
18
+ register(api: OpenClawPluginApi) {
19
+ setDingTalkRuntime(api.runtime);
20
+ api.registerChannel({ plugin: dingtalkPlugin });
21
+ },
22
+ };
23
+
24
+ export default plugin;
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "ddingtalk",
3
+ "channels": ["ddingtalk"],
4
+ "configSchema": {
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "properties": {}
8
+ }
9
+ }
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@largezhou/ddingtalk",
3
+ "version": "1.3.0",
4
+ "description": "OpenClaw DingTalk (钉钉) channel plugin",
5
+ "main": "index.ts",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/largezhou/openclaw-dingtalk.git"
10
+ },
11
+ "homepage": "https://github.com/largezhou/openclaw-dingtalk#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/largezhou/openclaw-dingtalk/issues"
14
+ },
15
+ "files": [
16
+ "src",
17
+ "index.ts",
18
+ "openclaw.plugin.json",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "demo": "npx tsx demo/demo.ts"
23
+ },
24
+ "keywords": [
25
+ "dingtalk",
26
+ "openclaw",
27
+ "robot",
28
+ "stream",
29
+ "typescript"
30
+ ],
31
+ "author": "largezhou",
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "@alicloud/dingtalk": "^2.2.38",
35
+ "@alicloud/openapi-client": "^0.4.15",
36
+ "@alicloud/tea-util": "^1.4.11",
37
+ "dingtalk-stream": "^2.1.4"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^20.10.0",
41
+ "openclaw": "*",
42
+ "tsx": "^4.6.0",
43
+ "typescript": "^5.3.0"
44
+ },
45
+ "peerDependencies": {
46
+ "openclaw": "*",
47
+ "zod": "^4.0.0"
48
+ },
49
+ "openclaw": {
50
+ "extensions": [
51
+ "./index.ts"
52
+ ],
53
+ "channel": {
54
+ "id": "ddingtalk",
55
+ "label": "DingTalk",
56
+ "selectionLabel": "DingTalk",
57
+ "docsPath": "/channels/ddingtalk",
58
+ "docsLabel": "ddingtalk",
59
+ "blurb": "DingTalk enterprise robot with Stream mode for Chinese market.",
60
+ "order": 80,
61
+ "quickstartAllowFrom": true
62
+ },
63
+ "install": {
64
+ "npmSpec": "@largezhou/ddingtalk",
65
+ "localPath": "",
66
+ "defaultChoice": "npm"
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,82 @@
1
+ import { DEFAULT_ACCOUNT_ID, type OpenClawConfig } from "openclaw/plugin-sdk";
2
+ import type { DingTalkConfig, ResolvedDingTalkAccount } from "./types.js";
3
+ import { PLUGIN_ID } from "./constants.js";
4
+
5
+ /**
6
+ * 规范化账户 ID(始终返回默认账户 ID)
7
+ */
8
+ export function normalizeAccountId(_accountId?: string | null): string {
9
+ return DEFAULT_ACCOUNT_ID;
10
+ }
11
+
12
+ /**
13
+ * 列出所有钉钉账户 ID(单账户,只返回默认账户)
14
+ */
15
+ export function listDingTalkAccountIds(cfg: OpenClawConfig): string[] {
16
+ const dingtalkConfig = cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
17
+ if (!dingtalkConfig?.clientId) {
18
+ return [];
19
+ }
20
+ return [DEFAULT_ACCOUNT_ID];
21
+ }
22
+
23
+ /**
24
+ * 解析默认钉钉账户 ID
25
+ */
26
+ export function resolveDefaultDingTalkAccountId(_cfg: OpenClawConfig): string {
27
+ return DEFAULT_ACCOUNT_ID;
28
+ }
29
+
30
+ /**
31
+ * 解析钉钉账户配置(单账户模式)
32
+ */
33
+ export function resolveDingTalkAccount(params: {
34
+ cfg: OpenClawConfig;
35
+ accountId?: string;
36
+ }): ResolvedDingTalkAccount {
37
+ const { cfg } = params;
38
+ const dingtalkConfig = cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
39
+
40
+ // 默认返回值
41
+ const defaultResult: ResolvedDingTalkAccount = {
42
+ accountId: DEFAULT_ACCOUNT_ID,
43
+ enabled: false,
44
+ clientId: "",
45
+ clientSecret: "",
46
+ tokenSource: "none",
47
+ allowFrom: ["*"],
48
+ groupPolicy: "open",
49
+ groupAllowFrom: [],
50
+ groups: {},
51
+ };
52
+
53
+ if (!dingtalkConfig) {
54
+ return defaultResult;
55
+ }
56
+
57
+ let clientId = "";
58
+ let clientSecret = "";
59
+ let tokenSource: ResolvedDingTalkAccount["tokenSource"] = "none";
60
+
61
+ if (dingtalkConfig.clientId?.trim()) {
62
+ clientId = dingtalkConfig.clientId.trim();
63
+ tokenSource = "config";
64
+ }
65
+
66
+ if (dingtalkConfig.clientSecret?.trim()) {
67
+ clientSecret = dingtalkConfig.clientSecret.trim();
68
+ }
69
+
70
+ return {
71
+ accountId: DEFAULT_ACCOUNT_ID,
72
+ name: dingtalkConfig.name,
73
+ enabled: dingtalkConfig.enabled ?? true,
74
+ clientId,
75
+ clientSecret,
76
+ tokenSource,
77
+ allowFrom: dingtalkConfig.allowFrom ?? ["*"],
78
+ groupPolicy: dingtalkConfig.groupPolicy ?? "open",
79
+ groupAllowFrom: dingtalkConfig.groupAllowFrom ?? [],
80
+ groups: dingtalkConfig.groups ?? {},
81
+ };
82
+ }