dsh-dingtalk 0.2.2 → 0.2.3
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 +16 -0
- package/README.en.md +24 -107
- package/README.md +24 -122
- package/docs/USAGE.en.md +124 -0
- package/docs/USAGE.md +130 -0
- package/docs/VALIDATION.md +15 -0
- package/docs/validation-0.2.3.md +19 -0
- package/lib/client.d.ts +2 -2
- package/lib/client.js +42 -30
- package/lib/index.js +10 -4
- package/package.json +5 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 更新记录
|
|
2
|
+
|
|
3
|
+
[返回简介](README.md) · [使用说明](docs/USAGE.md) · [验证记录](docs/VALIDATION.md)
|
|
4
|
+
|
|
5
|
+
## 0.2.3(2026-10-26)
|
|
6
|
+
|
|
7
|
+
- 宿主取消信号传递到发送请求,超时覆盖响应体读取,错误脱敏机器人凭据与签名;超时明确提示送达状态未知,避免盲目重复发送。
|
|
8
|
+
- Windows 回归 32 项通过、0 项跳过;宿主与未验收范围见[本次记录](docs/validation-0.2.3.md)。
|
|
9
|
+
|
|
10
|
+
## 0.2.2 (2026-09-28)
|
|
11
|
+
|
|
12
|
+
- 更新官方 Harness 0.2.0-rc.1 的兼容声明和共同加载验证;运行时代码未变。验证范围见[验证记录](docs/VALIDATION.md)。
|
|
13
|
+
|
|
14
|
+
## 更早的改动
|
|
15
|
+
|
|
16
|
+
完整历史可查阅 [GitHub 提交记录](https://github.com/STARDUSTLC666/dsh-dingtalk/commits/main)。
|
package/README.en.md
CHANGED
|
@@ -1,131 +1,48 @@
|
|
|
1
1
|
# dsh-dingtalk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[中文](README.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Send task notifications to a DingTalk group robot.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-dingtalk) [](https://www.npmjs.com/package/dsh-dingtalk)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Feedback and contributions are welcome: report [issues](https://github.com/STARDUSTLC666/dsh-dingtalk/issues) or submit [pull requests](https://github.com/STARDUSTLC666/dsh-dingtalk/pulls).
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|---|---|
|
|
15
|
-
| `dingtalk_notify` | Send a **Markdown** message to the configured DingTalk group (`title` heading + `text` Markdown body) |
|
|
16
|
-
| `dingtalk_text` | Send a **plain-text** message to the configured DingTalk group (`content`) |
|
|
13
|
+
## What it does
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
- Send Markdown or plain-text group notifications.
|
|
16
|
+
- Support signed robot requests.
|
|
17
|
+
- Check configuration and explain common errors.
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
## Install
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Validation host: Harness `0.2.0-rc.1` built from official sources (commit `407e65c8`) with Node `24.16.0` on 2026-09-28. All 28 plugin tests pass in an isolated environment; all 18 plugins mount together in one host registering 3 tools, with tool schemas and health-check contracts passing. No live ports or external services were exercised in this round.
|
|
25
|
-
|
|
26
|
-
## Installation
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
dsh plugin --profile web add dsh-dingtalk
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
After installing, restart `dsh web`. The plugin ships with an empty config and **won't crash startup**; calling any `dingtalk_*` tool before configuration returns a clear Chinese configuration hint.
|
|
33
|
-
|
|
34
|
-
## Uninstall
|
|
21
|
+
In DSH Desktop, install `dsh-dingtalk` from the Plugins panel. If the bundled dsh command is available:
|
|
35
22
|
|
|
36
23
|
```bash
|
|
37
|
-
dsh plugin --profile
|
|
24
|
+
dsh plugin --profile desktop add dsh-dingtalk
|
|
38
25
|
```
|
|
39
26
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
## Step 1: Get the webhook and signing secret
|
|
27
|
+
For the web version, replace `desktop` with `web`. Restart DSH after installation.
|
|
44
28
|
|
|
45
|
-
|
|
29
|
+
## Start using it
|
|
46
30
|
|
|
47
|
-
|
|
48
|
-
2. The robot name is arbitrary; under **Security settings, check "Sign" (加签)**, then finish
|
|
49
|
-
3. Copy two things:
|
|
50
|
-
- **Webhook URL**: like `https://oapi.dingtalk.com/robot/send?access_token=xxxx`
|
|
51
|
-
- **Signing secret (secret)**: like `SECxxxx` (only appears after checking "Sign")
|
|
31
|
+
Configure the robot webhook and signing secret, then ask to send a result summary to your group.
|
|
52
32
|
|
|
53
|
-
|
|
33
|
+
## Requirements and configuration
|
|
54
34
|
|
|
55
|
-
|
|
35
|
+
Requires a DingTalk robot webhook. This plugin provides one-way notifications.
|
|
56
36
|
|
|
57
|
-
|
|
37
|
+
Detailed configuration, tool arguments and troubleshooting are in the [usage guide](docs/USAGE.en.md). For standalone development, follow the Node requirement in [package.json](package.json).
|
|
58
38
|
|
|
59
|
-
|
|
60
|
-
- id: tool-dingtalk
|
|
61
|
-
config:
|
|
62
|
-
webhook: https://oapi.dingtalk.com/robot/send?access_token=你的token
|
|
63
|
-
secret: SEC你的加签密钥 # 可选,但强烈建议;也可用环境变量 DSH_DINGTALK_SECRET
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Full configuration
|
|
67
|
-
|
|
68
|
-
| Field | Default | Description |
|
|
69
|
-
|---|---|---|
|
|
70
|
-
| `webhook` | required | Full webhook URL of the robot (`?access_token=...`) |
|
|
71
|
-
| `secret` | empty | Signing secret; when omitted, falls back to the `DSH_DINGTALK_SECRET` env var. Empty = no signing |
|
|
72
|
-
| `timeoutMs` | `10000` | Request timeout in milliseconds (1000–60000) |
|
|
73
|
-
|
|
74
|
-
### Store the secret in an environment variable
|
|
75
|
-
|
|
76
|
-
If you don't want to write the secret into YAML, write only `webhook` and set an environment variable:
|
|
77
|
-
|
|
78
|
-
```sh
|
|
79
|
-
# Windows PowerShell
|
|
80
|
-
$env:DSH_DINGTALK_SECRET = "SEC你的加签密钥"
|
|
81
|
-
# 或系统环境变量里加一条 DSH_DINGTALK_SECRET
|
|
82
|
-
```
|
|
39
|
+
## Documentation
|
|
83
40
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
- **The webhook is the key to group notifications**: anyone who knows it can post to the group. Don't commit `cordis.patch.yml` to any Git repo; for the `secret`, prefer the `DSH_DINGTALK_SECRET` env var.
|
|
89
|
-
- This plugin is **one-way notification only** (agent → DingTalk group); it never reads messages from the group and has no two-way bot capability.
|
|
90
|
-
- This plugin performs no outbound telemetry; the `secret` is used in memory only to compute an HMAC signature, which is appended to the URL and sent to DingTalk's official endpoint.
|
|
91
|
-
|
|
92
|
-
## Signing algorithm (matches the official one)
|
|
93
|
-
|
|
94
|
-
When a `secret` is present, the request URL appends `timestamp` (millisecond timestamp) and `sign`:
|
|
95
|
-
|
|
96
|
-
```text
|
|
97
|
-
stringToSign = timestamp + "
|
|
98
|
-
" + secret
|
|
99
|
-
sign = urlencode(base64(HmacSHA256(secret, stringToSign)))
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Identical to this plugin's `computeDingTalkSign`, equivalent to `urllib.parse.quote_plus(base64(...))` in the official Python example.
|
|
103
|
-
|
|
104
|
-
## Known limitations
|
|
105
|
-
|
|
106
|
-
- **One-way notification only**: no support for enterprise self-built apps, no receiving group messages / two-way bots (@robot replies, etc.); these are v0.2+ directions.
|
|
107
|
-
- **No automatic workaround for custom keyword validation**: if the robot checks "Custom keywords" on DingTalk's side, messages must contain the keyword or DingTalk returns an error (errcode 310000 can also appear for this reason). This plugin attributes 310000 to signing; please also check the keyword setting.
|
|
108
|
-
- **No settings page**: v0.1 is a "node half-body"; configuration only via `cordis.patch.yml` (+ env var). A Web settings page is planned for v0.2+.
|
|
109
|
-
- **Message types**: currently only Markdown (`dingtalk_notify`) and plain text (`dingtalk_text`); image / Link / FeedCard / ActionCard etc. come later.
|
|
110
|
-
- **Markdown rendering**: DingTalk robot Markdown syntax differs slightly from standard Markdown (e.g., headings, links); write according to DingTalk's syntax.
|
|
111
|
-
|
|
112
|
-
## Error code reference
|
|
113
|
-
|
|
114
|
-
| errcode | Meaning | Suggestion |
|
|
115
|
-
|---|---|---|
|
|
116
|
-
| `0` | Success | — |
|
|
117
|
-
| `310000` | Signing validation failed (see the keyword note above) | Check that `secret` is correct and matches the robot's "Sign" setting |
|
|
118
|
-
| `120001` | `access_token` expired | Re-copy the webhook URL from the DingTalk group and overwrite `webhook` |
|
|
119
|
-
| other | Server error | Check the returned `errmsg` |
|
|
120
|
-
|
|
121
|
-
## Development
|
|
122
|
-
|
|
123
|
-
```sh
|
|
124
|
-
pnpm install
|
|
125
|
-
pnpm run build # tsc → lib/
|
|
126
|
-
pnpm test # 构建 + node --test(加签算法/配置解析/客户端封装/注册与中文报错)
|
|
127
|
-
```
|
|
41
|
+
- [Usage and troubleshooting](docs/USAGE.en.md)
|
|
42
|
+
- [Changelog](CHANGELOG.md)
|
|
43
|
+
- [Validation scope and history](docs/VALIDATION.md)
|
|
44
|
+
- [Report a problem or suggest a feature](https://github.com/STARDUSTLC666/dsh-dingtalk/issues)
|
|
128
45
|
|
|
129
46
|
## License
|
|
130
47
|
|
|
131
|
-
MIT
|
|
48
|
+
[MIT](LICENSE)
|
package/README.md
CHANGED
|
@@ -1,146 +1,48 @@
|
|
|
1
|
-
[English](README.en.md)
|
|
2
|
-
|
|
3
1
|
# dsh-dingtalk
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
   
|
|
8
|
-
|
|
9
|
-
[](https://awesome-dsh-plugin.com)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
DeepSeek Harness 钉钉群机器人通知插件:让 agent 能**单向推送 Markdown / 纯文本消息到钉钉群**。纯插件实现,零核心改动,安装即可用。
|
|
13
|
-
|
|
14
|
-
纯 Node 实现,**全平台通用**(Windows / macOS / Linux 同一份代码),只依赖 `node:crypto` 与内置 `fetch`,无运行时依赖、无原生二进制。
|
|
15
|
-
|
|
16
|
-
## 兼容性
|
|
17
|
-
|
|
18
|
-
验证宿主:官方源码构建的 Harness `0.2.0-rc.1`(commit `407e65c8`)+ Node `24.16.0`(2026-09-28)。28 项插件测试在隔离环境全部通过;同一个宿主里 18 个插件共同加载,注册 3 个工具,工具 schema 与健康检查契约通过。本轮未启用真实端口与外部服务。
|
|
3
|
+
[English](README.en.md)
|
|
19
4
|
|
|
20
|
-
|
|
5
|
+

|
|
21
6
|
|
|
22
|
-
|
|
23
|
-
|---|---|
|
|
24
|
-
| `dingtalk_notify` | 向配置的钉钉群发一条 **Markdown** 消息(`title` 标题 + `text` Markdown 正文) |
|
|
25
|
-
| `dingtalk_text` | 向配置的钉钉群发一条 **纯文本** 消息(`content`) |
|
|
7
|
+
把任务结果发送到钉钉群机器人的通知插件。
|
|
26
8
|
|
|
27
|
-
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-dingtalk) [](https://www.npmjs.com/package/dsh-dingtalk)
|
|
28
10
|
|
|
29
|
-
|
|
11
|
+
欢迎使用,遇到问题或有改进建议,请提交 [issues](https://github.com/STARDUSTLC666/dsh-dingtalk/issues) 和 [PR](https://github.com/STARDUSTLC666/dsh-dingtalk/pulls)。
|
|
30
12
|
|
|
31
|
-
##
|
|
13
|
+
## 功能
|
|
32
14
|
|
|
33
|
-
|
|
15
|
+
- 发送 Markdown 或纯文本群通知。
|
|
16
|
+
- 支持机器人加签。
|
|
17
|
+
- 提供配置自检与常见错误说明。
|
|
34
18
|
|
|
35
19
|
## 安装
|
|
36
20
|
|
|
37
|
-
|
|
38
|
-
dsh plugin --profile web add dsh-dingtalk
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
装好后重启 `dsh web`。插件自带空配置,**不会弄崩启动**;配置前调用任何 `dingtalk_*` 工具都会返回明确的中文配置提示。
|
|
42
|
-
|
|
43
|
-
## 卸载
|
|
21
|
+
桌面版可在「插件」面板按包名 `dsh-dingtalk` 安装。已配置 dsh 命令时也可使用:
|
|
44
22
|
|
|
45
23
|
```bash
|
|
46
|
-
dsh plugin --profile
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
卸载后重启 Web 服务。如需彻底清理,可再手动删除自己 profile `cordis.patch.yml` 中覆盖的插件行。
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
## 第一步:拿到 webhook 与加签密钥
|
|
53
|
-
|
|
54
|
-
用钉钉【自定义机器人】方式,**不需要企业应用、不需要管理员权限**:
|
|
55
|
-
|
|
56
|
-
1. 打开目标钉钉群 → **群设置** → **智能群助手** → **添加机器人** → **自定义(通过 Webhook 接入自定义服务)**
|
|
57
|
-
2. 机器人名称随意,**安全设置勾选「加签」**,完成
|
|
58
|
-
3. 复制两样东西:
|
|
59
|
-
- **Webhook 地址**:形如 `https://oapi.dingtalk.com/robot/send?access_token=xxxx`
|
|
60
|
-
- **加签密钥(secret)**:形如 `SECxxxx`(勾选「加签」后才会出现)
|
|
61
|
-
|
|
62
|
-
> 安全设置推荐只勾选「加签」。如果同时勾选了「自定义关键词」,机器人会校验消息里是否包含关键词——那是钉钉服务端行为,本插件不处理(见「已知限制」)。
|
|
63
|
-
|
|
64
|
-
## 配置
|
|
65
|
-
|
|
66
|
-
在你 profile 的 `cordis.patch.yml` 里覆盖 `tool-dingtalk` 行(在 `$DSH_HOME/profiles/<name>/` 下),然后重启:
|
|
67
|
-
|
|
68
|
-
```yaml
|
|
69
|
-
- id: tool-dingtalk
|
|
70
|
-
config:
|
|
71
|
-
webhook: https://oapi.dingtalk.com/robot/send?access_token=你的token
|
|
72
|
-
secret: SEC你的加签密钥 # 可选,但强烈建议;也可用环境变量 DSH_DINGTALK_SECRET
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### 完整配置项
|
|
76
|
-
|
|
77
|
-
| 字段 | 默认 | 说明 |
|
|
78
|
-
|---|---|---|
|
|
79
|
-
| `webhook` | 必填 | 机器人完整 Webhook 地址(`?access_token=...`) |
|
|
80
|
-
| `secret` | 空 | 加签密钥;省略时回退环境变量 `DSH_DINGTALK_SECRET`。不填=不加签 |
|
|
81
|
-
| `timeoutMs` | `10000` | 请求超时毫秒数(1000–60000) |
|
|
82
|
-
|
|
83
|
-
### 用环境变量存 secret
|
|
84
|
-
|
|
85
|
-
不想把密钥写进 YAML 的话,可以只写 `webhook`,然后设置环境变量:
|
|
86
|
-
|
|
87
|
-
```sh
|
|
88
|
-
# Windows PowerShell
|
|
89
|
-
$env:DSH_DINGTALK_SECRET = "SEC你的加签密钥"
|
|
90
|
-
# 或系统环境变量里加一条 DSH_DINGTALK_SECRET
|
|
24
|
+
dsh plugin --profile desktop add dsh-dingtalk
|
|
91
25
|
```
|
|
92
26
|
|
|
93
|
-
|
|
27
|
+
网页版把命令中的 `desktop` 改为 `web`。安装后重启 DSH。
|
|
94
28
|
|
|
95
|
-
##
|
|
29
|
+
## 开始使用
|
|
96
30
|
|
|
97
|
-
|
|
98
|
-
- 本插件**只做单向通知**(agent → 钉钉群),不会读取群里任何消息,也没有双向机器人能力。
|
|
99
|
-
- 本插件不做任何联网上报;`secret` 只在内存中用于计算 HMAC 签名,签名拼进 URL 发给钉钉官方接口。
|
|
31
|
+
配置群机器人 webhook 和加签密钥后,可说:“把这份结果摘要发到钉钉群。”
|
|
100
32
|
|
|
101
|
-
##
|
|
33
|
+
## 依赖与配置
|
|
102
34
|
|
|
103
|
-
|
|
35
|
+
需要钉钉群机器人 webhook;本插件提供单向通知。
|
|
104
36
|
|
|
105
|
-
|
|
106
|
-
stringToSign = timestamp + "
|
|
107
|
-
" + secret
|
|
108
|
-
sign = urlencode(base64(HmacSHA256(secret, stringToSign)))
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
与本插件 `computeDingTalkSign` 一致,等价于官方 Python 示例的 `urllib.parse.quote_plus(base64(...))`。
|
|
112
|
-
|
|
113
|
-
## 已知限制
|
|
114
|
-
|
|
115
|
-
- **仅单向通知**:不支持企业自建应用、不支持接收群消息 / 双向机器人(@机器人 回复等),这些是 v0.2+ 的方向。
|
|
116
|
-
- **不支持自定义关键词校验的自动规避**:如果机器人在钉钉侧勾选了「自定义关键词」,消息必须包含关键词,否则钉钉返回错误(errcode 310000 也可能因此出现)。本插件把 310000 归因于加签,请同时检查关键词设置。
|
|
117
|
-
- **无设置页**:v0.1 是「node 半身」,配置只能走 `cordis.patch.yml`(+ 环境变量)。Web 设置页在 v0.2+。
|
|
118
|
-
- **消息类型**:目前只支持 Markdown(`dingtalk_notify`)与纯文本(`dingtalk_text`)两种;图片/Link/FeedCard/ActionCard 等类型后续再加。
|
|
119
|
-
- **Markdown 渲染**:钉钉机器人 Markdown 语法与标准 Markdown 略有差异(如标题、链接),请按钉钉语法书写。
|
|
120
|
-
|
|
121
|
-
## 错误码速查
|
|
122
|
-
|
|
123
|
-
| errcode | 含义 | 建议 |
|
|
124
|
-
|---|---|---|
|
|
125
|
-
| `0` | 成功 | — |
|
|
126
|
-
| `310000` | 加签校验失败(也见上面关键词说明) | 检查 `secret` 是否填对、是否与机器人「加签」设置一致 |
|
|
127
|
-
| `120001` | `access_token` 失效 | 到钉钉群重新复制 webhook 地址,覆盖 `webhook` |
|
|
128
|
-
| 其他 | 服务端错误 | 看返回的 `errmsg` |
|
|
129
|
-
|
|
130
|
-
## 开发
|
|
131
|
-
|
|
132
|
-
```sh
|
|
133
|
-
pnpm install
|
|
134
|
-
pnpm run build # tsc → lib/
|
|
135
|
-
pnpm test # 构建 + node --test(加签算法/配置解析/客户端封装/注册与中文报错)
|
|
136
|
-
```
|
|
37
|
+
详细配置、工具参数与排错见[使用说明](docs/USAGE.md)。从源码独立开发时,Node 要求以 [package.json](package.json) 为准。
|
|
137
38
|
|
|
138
|
-
##
|
|
39
|
+
## 文档
|
|
139
40
|
|
|
140
|
-
|
|
41
|
+
- [使用与排错](docs/USAGE.md)
|
|
42
|
+
- [更新记录](CHANGELOG.md)
|
|
43
|
+
- [验证范围与历史记录](docs/VALIDATION.md)
|
|
44
|
+
- [问题反馈与功能建议](https://github.com/STARDUSTLC666/dsh-dingtalk/issues)
|
|
141
45
|
|
|
142
|
-
##
|
|
46
|
+
## License
|
|
143
47
|
|
|
144
|
-
|
|
145
|
-
- [dsh-dingtalk](https://github.com/STARDUSTLC666/dsh-dingtalk) — 钉钉群通知(零依赖)
|
|
146
|
-
- [dsh-email](https://github.com/STARDUSTLC666/dsh-email) — 邮件六件套 + Web 设置页
|
|
48
|
+
[MIT](LICENSE)
|
package/docs/USAGE.en.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# dsh-dingtalk usage guide
|
|
2
|
+
|
|
3
|
+
[Overview](../README.en.md) · [Changelog](../CHANGELOG.md) · [Validation](VALIDATION.md)
|
|
4
|
+
|
|
5
|
+
## Cancellation and delivery
|
|
6
|
+
|
|
7
|
+
Cancellation aborts the HTTP wait, including response-body reads. A timeout does not prove the message was not received: check the group before retrying. The plugin never retries automatically and redacts robot credentials/signatures from errors.
|
|
8
|
+
|
|
9
|
+
## Tools
|
|
10
|
+
|
|
11
|
+
| Tool | Purpose |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `dingtalk_notify` | Send a **Markdown** message to the configured DingTalk group (`title` heading + `text` Markdown body) |
|
|
14
|
+
| `dingtalk_text` | Send a **plain-text** message to the configured DingTalk group (`content`) |
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
|
|
18
|
+
> Send a message to the DingTalk group: title "Build complete", body "Pipeline #123 passed ✅".
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
dsh plugin --profile web add dsh-dingtalk
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
After installing, restart `dsh web`. The plugin ships with an empty config and **won't crash startup**; calling any `dingtalk_*` tool before configuration returns a clear Chinese configuration hint.
|
|
27
|
+
|
|
28
|
+
## Uninstall
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
dsh plugin --profile web remove dsh-dingtalk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then restart the web service. To clean up fully, also remove the plugin entry from your profile `cordis.patch.yml` if you overrode it.
|
|
35
|
+
|
|
36
|
+
## Step 1: Get the webhook and signing secret
|
|
37
|
+
|
|
38
|
+
Use DingTalk's **custom robot** method — **no enterprise app or admin permission needed**:
|
|
39
|
+
|
|
40
|
+
1. Open the target DingTalk group → **Group settings** → **Smart group assistant** → **Add robot** → **Custom (connect a custom service via Webhook)**
|
|
41
|
+
2. The robot name is arbitrary; under **Security settings, check "Sign" (加签)**, then finish
|
|
42
|
+
3. Copy two things:
|
|
43
|
+
- **Webhook URL**: like `https://oapi.dingtalk.com/robot/send?access_token=xxxx`
|
|
44
|
+
- **Signing secret (secret)**: like `SECxxxx` (only appears after checking "Sign")
|
|
45
|
+
|
|
46
|
+
> For security settings it's recommended to check only "Sign". If you also check "Custom keywords", the robot validates whether the message contains the keyword — that's DingTalk server-side behavior, not handled by this plugin (see "Known limitations").
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
In your profile's `cordis.patch.yml` (under `$DSH_HOME/profiles/<name>/`), override the `tool-dingtalk` line, then restart:
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
- id: tool-dingtalk
|
|
54
|
+
config:
|
|
55
|
+
webhook: https://oapi.dingtalk.com/robot/send?access_token=你的token
|
|
56
|
+
secret: SEC你的加签密钥 # 可选,但强烈建议;也可用环境变量 DSH_DINGTALK_SECRET
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Full configuration
|
|
60
|
+
|
|
61
|
+
| Field | Default | Description |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `webhook` | required | Full webhook URL of the robot (`?access_token=...`) |
|
|
64
|
+
| `secret` | empty | Signing secret; when omitted, falls back to the `DSH_DINGTALK_SECRET` env var. Empty = no signing |
|
|
65
|
+
| `timeoutMs` | `10000` | Request timeout in milliseconds (1000–60000) |
|
|
66
|
+
|
|
67
|
+
### Store the secret in an environment variable
|
|
68
|
+
|
|
69
|
+
If you don't want to write the secret into YAML, write only `webhook` and set an environment variable:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
# Windows PowerShell
|
|
73
|
+
$env:DSH_DINGTALK_SECRET = "SEC你的加签密钥"
|
|
74
|
+
# 或系统环境变量里加一条 DSH_DINGTALK_SECRET
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
An explicit `secret` in YAML takes precedence over the environment variable.
|
|
78
|
+
|
|
79
|
+
## Security notes
|
|
80
|
+
|
|
81
|
+
- **The webhook is the key to group notifications**: anyone who knows it can post to the group. Don't commit `cordis.patch.yml` to any Git repo; for the `secret`, prefer the `DSH_DINGTALK_SECRET` env var.
|
|
82
|
+
- This plugin is **one-way notification only** (agent → DingTalk group); it never reads messages from the group and has no two-way bot capability.
|
|
83
|
+
- This plugin performs no outbound telemetry; the `secret` is used in memory only to compute an HMAC signature, which is appended to the URL and sent to DingTalk's official endpoint.
|
|
84
|
+
|
|
85
|
+
## Signing algorithm (matches the official one)
|
|
86
|
+
|
|
87
|
+
When a `secret` is present, the request URL appends `timestamp` (millisecond timestamp) and `sign`:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
stringToSign = timestamp + "
|
|
91
|
+
" + secret
|
|
92
|
+
sign = urlencode(base64(HmacSHA256(secret, stringToSign)))
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Identical to this plugin's `computeDingTalkSign`, equivalent to `urllib.parse.quote_plus(base64(...))` in the official Python example.
|
|
96
|
+
|
|
97
|
+
## Known limitations
|
|
98
|
+
|
|
99
|
+
- **One-way notification only**: no support for enterprise self-built apps, no receiving group messages / two-way bots (@robot replies, etc.); these are v0.2+ directions.
|
|
100
|
+
- **No automatic workaround for custom keyword validation**: if the robot checks "Custom keywords" on DingTalk's side, messages must contain the keyword or DingTalk returns an error (errcode 310000 can also appear for this reason). This plugin attributes 310000 to signing; please also check the keyword setting.
|
|
101
|
+
- **No settings page**: v0.1 is a "node half-body"; configuration only via `cordis.patch.yml` (+ env var). A Web settings page is planned for v0.2+.
|
|
102
|
+
- **Message types**: currently only Markdown (`dingtalk_notify`) and plain text (`dingtalk_text`); image / Link / FeedCard / ActionCard etc. come later.
|
|
103
|
+
- **Markdown rendering**: DingTalk robot Markdown syntax differs slightly from standard Markdown (e.g., headings, links); write according to DingTalk's syntax.
|
|
104
|
+
|
|
105
|
+
## Error code reference
|
|
106
|
+
|
|
107
|
+
| errcode | Meaning | Suggestion |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `0` | Success | — |
|
|
110
|
+
| `310000` | Signing validation failed (see the keyword note above) | Check that `secret` is correct and matches the robot's "Sign" setting |
|
|
111
|
+
| `120001` | `access_token` expired | Re-copy the webhook URL from the DingTalk group and overwrite `webhook` |
|
|
112
|
+
| other | Server error | Check the returned `errmsg` |
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
pnpm install
|
|
118
|
+
pnpm run build # tsc → lib/
|
|
119
|
+
pnpm test # 构建 + node --test(加签算法/配置解析/客户端封装/注册与中文报错)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT. This is a community plugin, not affiliated with DeepSeek or DingTalk; `@deepseek-ai/*` is an officially reserved namespace.
|
package/docs/USAGE.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# dsh-dingtalk 使用说明
|
|
2
|
+
|
|
3
|
+
[返回简介](../README.md) · [更新记录](../CHANGELOG.md) · [验证记录](VALIDATION.md)
|
|
4
|
+
|
|
5
|
+
## 取消和送达状态
|
|
6
|
+
|
|
7
|
+
取消会终止正在等待的 HTTP 请求,包括响应体读取。超时无法证明远端未收到消息;先核对群记录再决定是否重发,插件不会自动重发。诊断错误会隐藏机器人凭据与签名。
|
|
8
|
+
|
|
9
|
+
## 工具一览
|
|
10
|
+
|
|
11
|
+
| 工具 | 作用 |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `dingtalk_notify` | 向配置的钉钉群发一条 **Markdown** 消息(`title` 标题 + `text` Markdown 正文) |
|
|
14
|
+
| `dingtalk_text` | 向配置的钉钉群发一条 **纯文本** 消息(`content`) |
|
|
15
|
+
|
|
16
|
+
示例对话:
|
|
17
|
+
|
|
18
|
+
> 帮我给钉钉群发一条消息:标题「构建完成」,正文「流水线 #123 已通过 ✅」。
|
|
19
|
+
|
|
20
|
+
## 安装
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
dsh plugin --profile web add dsh-dingtalk
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
装好后重启 `dsh web`。插件自带空配置,**不会弄崩启动**;配置前调用任何 `dingtalk_*` 工具都会返回明确的中文配置提示。
|
|
27
|
+
|
|
28
|
+
## 卸载
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
dsh plugin --profile web remove dsh-dingtalk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
卸载后重启 Web 服务。如需彻底清理,可再手动删除自己 profile `cordis.patch.yml` 中覆盖的插件行。
|
|
35
|
+
|
|
36
|
+
## 第一步:拿到 webhook 与加签密钥
|
|
37
|
+
|
|
38
|
+
用钉钉【自定义机器人】方式,**不需要企业应用、不需要管理员权限**:
|
|
39
|
+
|
|
40
|
+
1. 打开目标钉钉群 → **群设置** → **智能群助手** → **添加机器人** → **自定义(通过 Webhook 接入自定义服务)**
|
|
41
|
+
2. 机器人名称随意,**安全设置勾选「加签」**,完成
|
|
42
|
+
3. 复制两样东西:
|
|
43
|
+
- **Webhook 地址**:形如 `https://oapi.dingtalk.com/robot/send?access_token=xxxx`
|
|
44
|
+
- **加签密钥(secret)**:形如 `SECxxxx`(勾选「加签」后才会出现)
|
|
45
|
+
|
|
46
|
+
> 安全设置推荐只勾选「加签」。如果同时勾选了「自定义关键词」,机器人会校验消息里是否包含关键词——那是钉钉服务端行为,本插件不处理(见「已知限制」)。
|
|
47
|
+
|
|
48
|
+
## 配置
|
|
49
|
+
|
|
50
|
+
在你 profile 的 `cordis.patch.yml` 里覆盖 `tool-dingtalk` 行(在 `$DSH_HOME/profiles/<name>/` 下),然后重启:
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
- id: tool-dingtalk
|
|
54
|
+
config:
|
|
55
|
+
webhook: https://oapi.dingtalk.com/robot/send?access_token=你的token
|
|
56
|
+
secret: SEC你的加签密钥 # 可选,但强烈建议;也可用环境变量 DSH_DINGTALK_SECRET
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 完整配置项
|
|
60
|
+
|
|
61
|
+
| 字段 | 默认 | 说明 |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `webhook` | 必填 | 机器人完整 Webhook 地址(`?access_token=...`) |
|
|
64
|
+
| `secret` | 空 | 加签密钥;省略时回退环境变量 `DSH_DINGTALK_SECRET`。不填=不加签 |
|
|
65
|
+
| `timeoutMs` | `10000` | 请求超时毫秒数(1000–60000) |
|
|
66
|
+
|
|
67
|
+
### 用环境变量存 secret
|
|
68
|
+
|
|
69
|
+
不想把密钥写进 YAML 的话,可以只写 `webhook`,然后设置环境变量:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
# Windows PowerShell
|
|
73
|
+
$env:DSH_DINGTALK_SECRET = "SEC你的加签密钥"
|
|
74
|
+
# 或系统环境变量里加一条 DSH_DINGTALK_SECRET
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
显式写在 YAML 里的 `secret` 优先于环境变量。
|
|
78
|
+
|
|
79
|
+
## 安全须知
|
|
80
|
+
|
|
81
|
+
- **webhook 就是群通知的钥匙**:知道它的人可以往群里发消息。请勿把 `cordis.patch.yml` 提交到任何 Git 仓库;`secret` 更推荐用环境变量 `DSH_DINGTALK_SECRET`。
|
|
82
|
+
- 本插件**只做单向通知**(agent → 钉钉群),不会读取群里任何消息,也没有双向机器人能力。
|
|
83
|
+
- 本插件不做任何联网上报;`secret` 只在内存中用于计算 HMAC 签名,签名拼进 URL 发给钉钉官方接口。
|
|
84
|
+
|
|
85
|
+
## 加签算法(对照官方)
|
|
86
|
+
|
|
87
|
+
`secret` 存在时,请求 URL 追加 `timestamp`(毫秒时间戳)与 `sign`:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
stringToSign = timestamp + "
|
|
91
|
+
" + secret
|
|
92
|
+
sign = urlencode(base64(HmacSHA256(secret, stringToSign)))
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
与本插件 `computeDingTalkSign` 一致,等价于官方 Python 示例的 `urllib.parse.quote_plus(base64(...))`。
|
|
96
|
+
|
|
97
|
+
## 已知限制
|
|
98
|
+
|
|
99
|
+
- **仅单向通知**:不支持企业自建应用、不支持接收群消息 / 双向机器人(@机器人 回复等),这些是 v0.2+ 的方向。
|
|
100
|
+
- **不支持自定义关键词校验的自动规避**:如果机器人在钉钉侧勾选了「自定义关键词」,消息必须包含关键词,否则钉钉返回错误(errcode 310000 也可能因此出现)。本插件把 310000 归因于加签,请同时检查关键词设置。
|
|
101
|
+
- **无设置页**:v0.1 是「node 半身」,配置只能走 `cordis.patch.yml`(+ 环境变量)。Web 设置页在 v0.2+。
|
|
102
|
+
- **消息类型**:目前只支持 Markdown(`dingtalk_notify`)与纯文本(`dingtalk_text`)两种;图片/Link/FeedCard/ActionCard 等类型后续再加。
|
|
103
|
+
- **Markdown 渲染**:钉钉机器人 Markdown 语法与标准 Markdown 略有差异(如标题、链接),请按钉钉语法书写。
|
|
104
|
+
|
|
105
|
+
## 错误码速查
|
|
106
|
+
|
|
107
|
+
| errcode | 含义 | 建议 |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `0` | 成功 | — |
|
|
110
|
+
| `310000` | 加签校验失败(也见上面关键词说明) | 检查 `secret` 是否填对、是否与机器人「加签」设置一致 |
|
|
111
|
+
| `120001` | `access_token` 失效 | 到钉钉群重新复制 webhook 地址,覆盖 `webhook` |
|
|
112
|
+
| 其他 | 服务端错误 | 看返回的 `errmsg` |
|
|
113
|
+
|
|
114
|
+
## 开发
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
pnpm install
|
|
118
|
+
pnpm run build # tsc → lib/
|
|
119
|
+
pnpm test # 构建 + node --test(加签算法/配置解析/客户端封装/注册与中文报错)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## 协议
|
|
123
|
+
|
|
124
|
+
MIT。这是一个社区插件,与 DeepSeek 官方及钉钉官方无关;`@deepseek-ai/*` 为官方保留命名空间。
|
|
125
|
+
|
|
126
|
+
## 相关插件
|
|
127
|
+
|
|
128
|
+
- [dsh-calendar](https://github.com/STARDUSTLC666/dsh-calendar) — CalDAV 日历五件套
|
|
129
|
+
- [dsh-dingtalk](https://github.com/STARDUSTLC666/dsh-dingtalk) — 钉钉群通知(零依赖)
|
|
130
|
+
- [dsh-email](https://github.com/STARDUSTLC666/dsh-email) — 邮件六件套 + Web 设置页
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# dsh-dingtalk 验证记录
|
|
2
|
+
|
|
3
|
+
## 2026-10-26:0.2.3
|
|
4
|
+
|
|
5
|
+
[本次修复、回归与未验收范围](validation-0.2.3.md)。
|
|
6
|
+
|
|
7
|
+
本页整理原 README 的历史验证说明,保留当时的版本、日期与范围。自动测试、启动检查、浏览器操作和真实服务验收分别记录,不能相互替代。更详细的版本验收文件仍保留在仓库中。
|
|
8
|
+
|
|
9
|
+
## 原中文记录
|
|
10
|
+
|
|
11
|
+
验证宿主:官方源码构建的 Harness `0.2.0-rc.1`(commit `407e65c8`)+ Node `24.16.0`(2026-09-28)。28 项插件测试在隔离环境全部通过;同一个宿主里 18 个插件共同加载,注册 3 个工具,工具 schema 与健康检查契约通过。本轮未启用真实端口与外部服务。
|
|
12
|
+
|
|
13
|
+
## Original English record
|
|
14
|
+
|
|
15
|
+
Validation host: Harness `0.2.0-rc.1` built from official sources (commit `407e65c8`) with Node `24.16.0` on 2026-09-28. All 28 plugin tests pass in an isolated environment; all 18 plugins mount together in one host registering 3 tools, with tool schemas and health-check contracts passing. No live ports or external services were exercised in this round.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# 0.2.3 验证记录
|
|
2
|
+
|
|
3
|
+
日期:2026-10-26。官方 Windows 宿主:DSH 0.2.1-rc.3。
|
|
4
|
+
|
|
5
|
+
## 行为变化
|
|
6
|
+
|
|
7
|
+
宿主取消信号传递到发送请求,超时覆盖响应体读取,错误脱敏机器人凭据与签名;超时明确提示送达状态未知,避免盲目重复发送。
|
|
8
|
+
|
|
9
|
+
## 验证与限制
|
|
10
|
+
|
|
11
|
+
- Windows 本地:32 项,32 通过、0 跳过;没有把跳过计为通过。
|
|
12
|
+
- 官方 RC.3 tools.execute 接入本地模拟 HTTP 对端,验证正常回执、读取响应体时取消和超时,以及连接关闭;没有向真实钉钉群发送消息。
|
|
13
|
+
- GitHub 的跨平台 CI 和 npm 发布完整性以该版本工作流及 registry 为准;本记录不把本地测试等同于已发布。
|
|
14
|
+
|
|
15
|
+
## English
|
|
16
|
+
|
|
17
|
+
Host cancellation reaches the HTTP request; timeout also covers the response body. Errors redact webhook credentials and signatures. A timeout reports unknown delivery and asks users to check the group before retrying.
|
|
18
|
+
|
|
19
|
+
Runtime/account limits are listed above. Synthetic peers and model adapters do not establish live account delivery or model quality.
|
package/lib/client.d.ts
CHANGED
|
@@ -27,8 +27,8 @@ export declare class DingTalkClient {
|
|
|
27
27
|
private readonly fetchImpl;
|
|
28
28
|
constructor(options: DingTalkClientOptions);
|
|
29
29
|
/** 发送 Markdown 消息:{ msgtype: 'markdown', markdown: { title, text } } */
|
|
30
|
-
sendMarkdown(title: string, text: string): Promise<DingTalkSendResult>;
|
|
30
|
+
sendMarkdown(title: string, text: string, signal?: AbortSignal): Promise<DingTalkSendResult>;
|
|
31
31
|
/** 发送纯文本消息:{ msgtype: 'text', text: { content } } */
|
|
32
|
-
sendText(content: string): Promise<DingTalkSendResult>;
|
|
32
|
+
sendText(content: string, signal?: AbortSignal): Promise<DingTalkSendResult>;
|
|
33
33
|
private post;
|
|
34
34
|
}
|
package/lib/client.js
CHANGED
|
@@ -31,52 +31,64 @@ export class DingTalkClient {
|
|
|
31
31
|
this.fetchImpl = options.fetchImpl ?? defaultFetch;
|
|
32
32
|
}
|
|
33
33
|
/** 发送 Markdown 消息:{ msgtype: 'markdown', markdown: { title, text } } */
|
|
34
|
-
async sendMarkdown(title, text) {
|
|
35
|
-
return this.post({ msgtype: 'markdown', markdown: { title, text } });
|
|
34
|
+
async sendMarkdown(title, text, signal) {
|
|
35
|
+
return this.post({ msgtype: 'markdown', markdown: { title, text } }, signal);
|
|
36
36
|
}
|
|
37
37
|
/** 发送纯文本消息:{ msgtype: 'text', text: { content } } */
|
|
38
|
-
async sendText(content) {
|
|
39
|
-
return this.post({ msgtype: 'text', text: { content } });
|
|
38
|
+
async sendText(content, signal) {
|
|
39
|
+
return this.post({ msgtype: 'text', text: { content } }, signal);
|
|
40
40
|
}
|
|
41
|
-
async post(payload) {
|
|
41
|
+
async post(payload, signal) {
|
|
42
|
+
signal?.throwIfAborted();
|
|
42
43
|
const url = signedUrl(this.webhook, this.secret || undefined);
|
|
44
|
+
const parsedUrl = new URL(url);
|
|
45
|
+
const redact = (text) => [url, this.webhook, this.secret, parsedUrl.searchParams.get('access_token'), parsedUrl.searchParams.get('sign')]
|
|
46
|
+
.filter((value) => typeof value === 'string' && value !== '')
|
|
47
|
+
.reduce((safe, value) => safe.replaceAll(value, '[已隐藏]').replaceAll(encodeURIComponent(value), '[已隐藏]'), text);
|
|
43
48
|
const controller = new AbortController();
|
|
44
|
-
const
|
|
45
|
-
|
|
49
|
+
const requestSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
50
|
+
const timer = setTimeout(() => controller.abort(new Error('请求超时')), this.timeoutMs);
|
|
46
51
|
try {
|
|
47
|
-
response = await this.fetchImpl(url, {
|
|
52
|
+
const response = await this.fetchImpl(url, {
|
|
48
53
|
method: 'POST',
|
|
49
54
|
headers: { 'Content-Type': 'application/json' },
|
|
50
55
|
body: JSON.stringify(payload),
|
|
51
|
-
signal:
|
|
56
|
+
signal: requestSignal,
|
|
52
57
|
});
|
|
58
|
+
requestSignal.throwIfAborted();
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
const body = await response.text().catch(() => '');
|
|
61
|
+
requestSignal.throwIfAborted();
|
|
62
|
+
throw new DingTalkError('钉钉机器人返回 HTTP ' + response.status + ':' + body.slice(0, 200));
|
|
63
|
+
}
|
|
64
|
+
let body;
|
|
65
|
+
try {
|
|
66
|
+
body = await response.json();
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
requestSignal.throwIfAborted();
|
|
70
|
+
throw new DingTalkError('钉钉机器人返回非 JSON 响应(HTTP ' + response.status + ')');
|
|
71
|
+
}
|
|
72
|
+
requestSignal.throwIfAborted();
|
|
73
|
+
const result = body;
|
|
74
|
+
if (!Number.isSafeInteger(result?.errcode))
|
|
75
|
+
throw new DingTalkError('钉钉机器人返回格式异常:缺少 errcode');
|
|
76
|
+
if (result.errcode !== 0)
|
|
77
|
+
throw new DingTalkError(dingTalkErrorMessage(result.errcode, result.errmsg ?? ''), result.errcode);
|
|
78
|
+
return result;
|
|
53
79
|
}
|
|
54
80
|
catch (error) {
|
|
81
|
+
signal?.throwIfAborted();
|
|
82
|
+
if (controller.signal.aborted)
|
|
83
|
+
throw new DingTalkError('钉钉机器人请求超时,尚未确认远端是否收到;请先核对群消息,再决定是否重发。');
|
|
84
|
+
if (error instanceof DingTalkError)
|
|
85
|
+
throw new DingTalkError(redact(error.message), error.errcode);
|
|
55
86
|
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
-
const
|
|
57
|
-
throw new DingTalkError('
|
|
87
|
+
const safe = redact(message);
|
|
88
|
+
throw new DingTalkError('钉钉机器人请求失败:' + safe);
|
|
58
89
|
}
|
|
59
90
|
finally {
|
|
60
91
|
clearTimeout(timer);
|
|
61
92
|
}
|
|
62
|
-
if (!response.ok) {
|
|
63
|
-
const body = await response.text().catch(() => '');
|
|
64
|
-
throw new DingTalkError('钉钉机器人返回 HTTP ' + response.status + ':' + body.slice(0, 200));
|
|
65
|
-
}
|
|
66
|
-
let body;
|
|
67
|
-
try {
|
|
68
|
-
body = await response.json();
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
throw new DingTalkError('钉钉机器人返回非 JSON 响应(HTTP ' + response.status + ')');
|
|
72
|
-
}
|
|
73
|
-
const result = body;
|
|
74
|
-
if (typeof result?.errcode !== 'number') {
|
|
75
|
-
throw new DingTalkError('钉钉机器人返回格式异常:缺少 errcode');
|
|
76
|
-
}
|
|
77
|
-
if (result.errcode !== 0) {
|
|
78
|
-
throw new DingTalkError(dingTalkErrorMessage(result.errcode, result.errmsg ?? ''), result.errcode);
|
|
79
|
-
}
|
|
80
|
-
return result;
|
|
81
93
|
}
|
|
82
94
|
}
|
package/lib/index.js
CHANGED
|
@@ -36,6 +36,10 @@ const notifySchema = {
|
|
|
36
36
|
function oneText(text) {
|
|
37
37
|
return [{ type: 'text', text }];
|
|
38
38
|
}
|
|
39
|
+
function executionSignal(exec) {
|
|
40
|
+
const signal = exec?.signal;
|
|
41
|
+
return signal instanceof AbortSignal ? signal : undefined;
|
|
42
|
+
}
|
|
39
43
|
function renderNotify(value) {
|
|
40
44
|
if (value.msgtype === 'markdown') {
|
|
41
45
|
return oneText('已通过钉钉机器人发送 Markdown 消息(标题「' + value.title + '」),钉钉返回 errcode=' + value.errcode + ',errmsg=' + value.errmsg);
|
|
@@ -72,13 +76,14 @@ export function apply(ctx, config = {}) {
|
|
|
72
76
|
schema: notifySchema,
|
|
73
77
|
render: (_args, value) => renderNotify(value),
|
|
74
78
|
},
|
|
75
|
-
|
|
79
|
+
timeoutMs: 65000,
|
|
80
|
+
async execute(rawArgs, exec) {
|
|
76
81
|
const args = rawArgs;
|
|
77
82
|
if (typeof args.title !== 'string' || args.title.trim() === '')
|
|
78
83
|
throw new Error('title 不能为空');
|
|
79
84
|
if (typeof args.text !== 'string' || args.text.trim() === '')
|
|
80
85
|
throw new Error('text 不能为空');
|
|
81
|
-
const result = await getClient().sendMarkdown(args.title, args.text);
|
|
86
|
+
const result = await getClient().sendMarkdown(args.title, args.text, executionSignal(exec));
|
|
82
87
|
return { ok: true, msgtype: 'markdown', title: args.title, errcode: result.errcode, errmsg: result.errmsg };
|
|
83
88
|
},
|
|
84
89
|
});
|
|
@@ -92,11 +97,12 @@ export function apply(ctx, config = {}) {
|
|
|
92
97
|
schema: notifySchema,
|
|
93
98
|
render: (_args, value) => renderNotify(value),
|
|
94
99
|
},
|
|
95
|
-
|
|
100
|
+
timeoutMs: 65000,
|
|
101
|
+
async execute(rawArgs, exec) {
|
|
96
102
|
const args = rawArgs;
|
|
97
103
|
if (typeof args.content !== 'string' || args.content.trim() === '')
|
|
98
104
|
throw new Error('content 不能为空');
|
|
99
|
-
const result = await getClient().sendText(args.content);
|
|
105
|
+
const result = await getClient().sendText(args.content, executionSignal(exec));
|
|
100
106
|
return { ok: true, msgtype: 'text', title: '', errcode: result.errcode, errmsg: result.errmsg };
|
|
101
107
|
},
|
|
102
108
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-dingtalk",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "把任务结果发送到钉钉群机器人的通知插件。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
"lib",
|
|
19
19
|
"cordis.patch.yml",
|
|
20
20
|
"README.md",
|
|
21
|
-
"README.en.md"
|
|
21
|
+
"README.en.md",
|
|
22
|
+
"docs",
|
|
23
|
+
"CHANGELOG.md"
|
|
22
24
|
],
|
|
23
25
|
"scripts": {
|
|
24
26
|
"build": "tsc",
|