@izhimu/qq 0.2.0 → 0.2.1

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 izhimu
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 izhimu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,388 +1,388 @@
1
- # @izhimu/qq
2
-
3
- <p align="center">
4
- <a href="https://opensource.org/licenses/MIT">
5
- <img src="https://img.shields.io/badge/License-MIT-green.svg" alt="License: MIT">
6
- </a>
7
- <a href="https://docs.openclaw.ai/">
8
- <img src="https://img.shields.io/badge/OpenClaw-Plugin-blueviolet.svg" alt="OpenClaw">
9
- </a>
10
- <a href="https://github.com/botuniverse/onebot-11">
11
- <img src="https://img.shields.io/badge/Protocol-OneBot_11-yellow.svg" alt="Protocol">
12
- </a>
13
- <a href="https://www.typescriptlang.org/">
14
- <img src="https://img.shields.io/badge/Language-TypeScript-blue.svg" alt="TypeScript">
15
- </a>
16
- </p>
17
-
18
- <p align="center">
19
- <strong>QQ Channel Plugin for OpenClaw</strong>
20
- </p>
21
-
22
- <p align="center">
23
- 通过 NapCat WebSocket API 连接 QQ 机器人,支持私聊、群聊、图片、回复等多种消息类型
24
- </p>
25
-
26
- ---
27
-
28
- ## 目录
29
-
30
- - [功能特性](#功能特性)
31
- - [安装](#安装)
32
- - [快速开始](#快速开始)
33
- - [配置](#配置)
34
- - [使用方法](#使用方法)
35
- - [架构设计](#架构设计)
36
- - [API 文档](#api-文档)
37
- - [开发指南](#开发指南)
38
- - [故障排查](#故障排查)
39
-
40
- ---
41
-
42
- ## 功能特性
43
-
44
- - **多渠道支持** - 同时支持 QQ 私聊和群聊
45
- - **消息类型** - 文本、@提及、图片、表情、语音、文件、回复
46
- - **实时通信** - WebSocket 全双工连接,支持自动重连
47
- - **心跳监测** - 内置健康检查和连接状态监控
48
- - **交互式配置** - 提供向导式配置界面
49
- - **TypeScript** - 完整的类型定义和类型安全
50
-
51
- ---
52
-
53
- ## 安装
54
-
55
- ### 前提条件
56
-
57
- 1. 安装 [OpenClaw](https://docs.openclaw.ai/) >= 2026.2.1
58
- 2. 安装并配置 [NapCat](https://github.com/NapNeko/NapCatQQ) WebSocket 服务
59
-
60
- ### 通过 OpenClaw CLI 安装
61
-
62
- ```bash
63
- # 安装插件
64
- openclaw plugins install @izhimu/qq
65
- ```
66
-
67
- ### 本地开发安装
68
-
69
- ```bash
70
- # 克隆仓库
71
- git clone <repository-url>
72
- cd openclaw-channel-qq
73
-
74
- # 安装依赖
75
- npm install
76
-
77
- # 构建项目
78
- npm run build
79
-
80
- # 安装到 OpenClaw
81
- openclaw plugins install /path/to/openclaw-channel-qq
82
- ```
83
-
84
- ---
85
-
86
- ## 快速开始
87
-
88
- ### 1. 配置 NapCat
89
-
90
- 在 NapCat 的 `config.yml` 中启用 WebSocket:
91
-
92
- ```yaml
93
- ws:
94
- servers:
95
- - url: ws://0.0.0.0:3001
96
- token: "" # 如需认证请设置
97
- enableHeart: true
98
- ```
99
-
100
- ### 2. 配置 OpenClaw
101
-
102
- 运行交互式配置向导:
103
-
104
- ```bash
105
- openclaw onboard
106
- ```
107
-
108
- 或手动编辑配置:
109
-
110
- ```json
111
- {
112
- "channels": {
113
- "qq": {
114
- "wsUrl": "ws://127.0.0.1:3001",
115
- "accessToken": "",
116
- "enabled": true
117
- }
118
- }
119
- }
120
- ```
121
-
122
- ### 3. 启动 Gateway
123
-
124
- ```bash
125
- openclaw gateway restart
126
- ```
127
-
128
- ---
129
-
130
- ## 配置
131
-
132
- ### 配置选项
133
-
134
- | 属性 | 类型 | 必填 | 默认值 | 描述 |
135
- |------|------|------|--------|------|
136
- | `wsUrl` | `string` | 是 | - | NapCat WebSocket 地址 |
137
- | `accessToken` | `string` | 否 | `""` | 访问令牌(如配置了认证) |
138
- | `enabled` | `boolean` | 否 | `true` | 是否启用该账号 |
139
-
140
- ### 配置示例
141
-
142
- ```json
143
- {
144
- "channels": {
145
- "qq": {
146
- "wsUrl": "ws://127.0.0.1:3001",
147
- "accessToken": "your-token",
148
- "enabled": true
149
- }
150
- }
151
- }
152
- ```
153
-
154
- ---
155
-
156
- ## 使用方法
157
-
158
- ### 发送消息
159
-
160
- ```bash
161
- # 发送私聊消息
162
- openclaw message send "你好!" --to qq:private:123456789
163
-
164
- # 发送群消息
165
- openclaw message send "大家好!" --to qq:group:123456
166
-
167
- # 带回复的消息
168
- openclaw message send "回复你的消息" --to qq:private:123456789 --reply-to <message-id>
169
- ```
170
-
171
- ### 检查状态
172
-
173
- ```bash
174
- # 查看频道状态
175
- openclaw channels
176
-
177
- # 查看日志
178
- openclaw logs --channel qq
179
- ```
180
-
181
- ### 消息目标格式
182
-
183
- | 类型 | 格式 | 示例 |
184
- |------|------|------|
185
- | 私聊 | `private:<QQ号>` | `qq:private:123456789` |
186
- | 群聊 | `group:<群号>` | `qq:group:123456` |
187
-
188
- ---
189
-
190
- ## 架构设计
191
-
192
- ### 项目结构
193
-
194
- ```
195
- openclaw-channel-qq/
196
- ├── src/
197
- │ ├── channel.ts # 插件主入口
198
- │ ├── core/
199
- │ │ ├── connection.ts # WebSocket 连接管理
200
- │ │ ├── dispatch.ts # 事件分发器
201
- │ │ ├── request.ts # API 请求处理
202
- │ │ ├── runtime.ts # 运行时状态管理
203
- │ │ └── config.ts # 配置解析
204
- │ ├── adapters/
205
- │ │ └── message.ts # 消息格式转换
206
- │ ├── types/
207
- │ │ └── index.ts # TypeScript 类型定义
208
- │ ├── utils/
209
- │ │ ├── index.ts # 工具函数
210
- │ │ ├── log.ts # 日志工具
211
- │ │ ├── markdown.ts # Markdown 处理
212
- │ │ └── cqcode.ts # CQ 码解析
213
- │ └── onboarding.ts # 交互式配置向导
214
- ├── docs/
215
- │ ├── napcat-websocket-api.md # NapCat API 文档
216
- │ └── plugin-development-guide.md # 插件开发指南
217
- ├── openclaw.plugin.json # 插件清单
218
- ├── package.json
219
- └── tsconfig.json
220
- ```
221
-
222
- ### 核心组件
223
-
224
- #### 1. ConnectionManager
225
-
226
- 负责 WebSocket 连接的生命周期管理:
227
- - 连接建立和断开
228
- - 自动重连机制(指数退避)
229
- - 心跳监测
230
- - 请求/响应关联
231
-
232
- #### 2. MessageAdapter
233
-
234
- 处理 NapCat 与 OpenClaw 消息格式的双向转换:
235
- - 入站消息解析(NapCat → OpenClaw)
236
- - 出站消息构建(OpenClaw → NapCat)
237
- - 支持多种消息段类型
238
-
239
- #### 3. EventDispatcher
240
-
241
- 处理 NapCat 上报的各类事件:
242
- - 消息事件(私聊、群聊)
243
- - 通知事件(戳一戳、撤回等)
244
- - 元事件(心跳、生命周期)
245
-
246
- ---
247
-
248
- ## API 文档
249
-
250
- ### 支持的消息类型
251
-
252
- | 类型 | 入站 | 出站 | 说明 |
253
- |------|------|------|------|
254
- | `text` | ✓ | ✓ | 文本消息 |
255
- | `at` | ✓ | ✓ | @提及 |
256
- | `image` | ✓ | ✓ | 图片 |
257
- | `face` | ✓ | - | QQ 表情 |
258
- | `reply` | ✓ | ✓ | 消息回复 |
259
- | `record` | ✓ | - | 语音消息 |
260
- | `file` | ✓ | - | 文件 |
261
- | `json` | ✓ | - | JSON 富文本 |
262
-
263
- ### OneBot 11 接口
264
-
265
- ```typescript
266
- // 发送消息
267
- {
268
- action: 'send_msg',
269
- params: {
270
- message_type: 'private' | 'group',
271
- user_id?: string, // 私聊时必填
272
- group_id?: string, // 群聊时必填
273
- message: NapCatMessage[] | string
274
- }
275
- }
276
-
277
- // 获取消息
278
- {
279
- action: 'get_msg',
280
- params: {
281
- message_id: number
282
- }
283
- }
284
-
285
- // 获取运行状态
286
- {
287
- action: 'get_status',
288
- params: {}
289
- }
290
- ```
291
-
292
- 完整 API 文档请参考 [NapCat WebSocket API 文档](./docs/napcat-websocket-api.md)。
293
-
294
- ---
295
-
296
- ## 开发指南
297
-
298
- ### 开发环境设置
299
-
300
- ```bash
301
- # 安装依赖
302
- npm install
303
-
304
- # 开发模式(热重载)
305
- npm run dev
306
-
307
- # 构建
308
- npm run build
309
- ```
310
-
311
- ### 目录结构说明
312
-
313
- | 目录 | 说明 |
314
- |------|------|
315
- | `src/core/` | 核心功能模块 |
316
- | `src/adapters/` | 适配器(消息转换等) |
317
- | `src/types/` | TypeScript 类型定义 |
318
- | `src/utils/` | 工具函数 |
319
- | `docs/` | 文档 |
320
- | `openspec/` | OpenSpec 规范 |
321
-
322
- ### 插件开发参考
323
-
324
- 详细开发指南请参考 [插件开发指南](./docs/plugin-development-guide.md)。
325
-
326
- ---
327
-
328
- ## 故障排查
329
-
330
- ### 连接问题
331
-
332
- 1. **检查 NapCat 是否运行**
333
- ```bash
334
- # 检查服务状态
335
- curl http://localhost:3001/get_status
336
- ```
337
-
338
- 2. **验证 WebSocket 配置**
339
- - 确认 `wsUrl` 与 NapCat 配置一致
340
- - 检查访问令牌是否正确
341
-
342
- 3. **防火墙设置**
343
- - 确保 WebSocket 端口未被防火墙拦截
344
-
345
- ### 消息发送失败
346
-
347
- 1. **检查账号状态**
348
- ```bash
349
- openclaw channels
350
- ```
351
-
352
- 2. **查看日志**
353
- ```bash
354
- openclaw logs --channel qq --verbose
355
- ```
356
-
357
- 3. **验证消息格式**
358
- - 确保目标格式正确:`private:<QQ>` 或 `group:<群号>`
359
-
360
- ### 常见问题
361
-
362
- | 问题 | 可能原因 | 解决方案 |
363
- |------|----------|----------|
364
- | 连接失败 | NapCat 未启动 | 启动 NapCat 服务 |
365
- | 认证失败 | accessToken 错误 | 检查配置中的令牌 |
366
- | 消息未收到 | 账号被禁用 | 检查 `enabled: true` |
367
- | 图片发送失败 | URL 不可访问 | 确保图片 URL 可公网访问 |
368
-
369
- ---
370
-
371
- ## 相关链接
372
-
373
- - [OpenClaw 官方文档](https://docs.openclaw.ai/)
374
- - [OpenClaw 插件开发指南](https://docs.openclaw.ai/plugin)
375
- - [NapCat GitHub](https://github.com/NapNeko/NapCatQQ)
376
- - [OneBot 11 协议标准](https://github.com/botuniverse/onebot-11)
377
-
378
- ---
379
-
380
- ## 开源协议
381
-
382
- [MIT](LICENSE) © izhimu
383
-
384
- ---
385
-
386
- <p align="center">
387
- 如有问题或建议,欢迎提交 Issue 或 PR
388
- </p>
1
+ # @izhimu/qq
2
+
3
+ <p align="center">
4
+ <a href="https://opensource.org/licenses/MIT">
5
+ <img src="https://img.shields.io/badge/License-MIT-green.svg" alt="License: MIT">
6
+ </a>
7
+ <a href="https://docs.openclaw.ai/">
8
+ <img src="https://img.shields.io/badge/OpenClaw-Plugin-blueviolet.svg" alt="OpenClaw">
9
+ </a>
10
+ <a href="https://github.com/botuniverse/onebot-11">
11
+ <img src="https://img.shields.io/badge/Protocol-OneBot_11-yellow.svg" alt="Protocol">
12
+ </a>
13
+ <a href="https://www.typescriptlang.org/">
14
+ <img src="https://img.shields.io/badge/Language-TypeScript-blue.svg" alt="TypeScript">
15
+ </a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <strong>QQ Channel Plugin for OpenClaw</strong>
20
+ </p>
21
+
22
+ <p align="center">
23
+ 通过 NapCat WebSocket API 连接 QQ 机器人,支持私聊、群聊、图片、回复等多种消息类型
24
+ </p>
25
+
26
+ ---
27
+
28
+ ## 目录
29
+
30
+ - [功能特性](#功能特性)
31
+ - [安装](#安装)
32
+ - [快速开始](#快速开始)
33
+ - [配置](#配置)
34
+ - [使用方法](#使用方法)
35
+ - [架构设计](#架构设计)
36
+ - [API 文档](#api-文档)
37
+ - [开发指南](#开发指南)
38
+ - [故障排查](#故障排查)
39
+
40
+ ---
41
+
42
+ ## 功能特性
43
+
44
+ - **多渠道支持** - 同时支持 QQ 私聊和群聊
45
+ - **消息类型** - 文本、@提及、图片、表情、语音、文件、回复
46
+ - **实时通信** - WebSocket 全双工连接,支持自动重连
47
+ - **心跳监测** - 内置健康检查和连接状态监控
48
+ - **交互式配置** - 提供向导式配置界面
49
+ - **TypeScript** - 完整的类型定义和类型安全
50
+
51
+ ---
52
+
53
+ ## 安装
54
+
55
+ ### 前提条件
56
+
57
+ 1. 安装 [OpenClaw](https://docs.openclaw.ai/) >= 2026.2.1
58
+ 2. 安装并配置 [NapCat](https://github.com/NapNeko/NapCatQQ) WebSocket 服务
59
+
60
+ ### 通过 OpenClaw CLI 安装
61
+
62
+ ```bash
63
+ # 安装插件
64
+ openclaw plugins install @izhimu/qq
65
+ ```
66
+
67
+ ### 本地开发安装
68
+
69
+ ```bash
70
+ # 克隆仓库
71
+ git clone <repository-url>
72
+ cd openclaw-channel-qq
73
+
74
+ # 安装依赖
75
+ npm install
76
+
77
+ # 构建项目
78
+ npm run build
79
+
80
+ # 安装到 OpenClaw
81
+ openclaw plugins install /path/to/openclaw-channel-qq
82
+ ```
83
+
84
+ ---
85
+
86
+ ## 快速开始
87
+
88
+ ### 1. 配置 NapCat
89
+
90
+ 在 NapCat 的 `config.yml` 中启用 WebSocket:
91
+
92
+ ```yaml
93
+ ws:
94
+ servers:
95
+ - url: ws://0.0.0.0:3001
96
+ token: "" # 如需认证请设置
97
+ enableHeart: true
98
+ ```
99
+
100
+ ### 2. 配置 OpenClaw
101
+
102
+ 运行交互式配置向导:
103
+
104
+ ```bash
105
+ openclaw onboard
106
+ ```
107
+
108
+ 或手动编辑配置:
109
+
110
+ ```json
111
+ {
112
+ "channels": {
113
+ "qq": {
114
+ "wsUrl": "ws://127.0.0.1:3001",
115
+ "accessToken": "",
116
+ "enabled": true
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ### 3. 启动 Gateway
123
+
124
+ ```bash
125
+ openclaw gateway restart
126
+ ```
127
+
128
+ ---
129
+
130
+ ## 配置
131
+
132
+ ### 配置选项
133
+
134
+ | 属性 | 类型 | 必填 | 默认值 | 描述 |
135
+ |------|------|------|--------|------|
136
+ | `wsUrl` | `string` | 是 | - | NapCat WebSocket 地址 |
137
+ | `accessToken` | `string` | 否 | `""` | 访问令牌(如配置了认证) |
138
+ | `enabled` | `boolean` | 否 | `true` | 是否启用该账号 |
139
+
140
+ ### 配置示例
141
+
142
+ ```json
143
+ {
144
+ "channels": {
145
+ "qq": {
146
+ "wsUrl": "ws://127.0.0.1:3001",
147
+ "accessToken": "your-token",
148
+ "enabled": true
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ ---
155
+
156
+ ## 使用方法
157
+
158
+ ### 发送消息
159
+
160
+ ```bash
161
+ # 发送私聊消息
162
+ openclaw message send "你好!" --to qq:private:123456789
163
+
164
+ # 发送群消息
165
+ openclaw message send "大家好!" --to qq:group:123456
166
+
167
+ # 带回复的消息
168
+ openclaw message send "回复你的消息" --to qq:private:123456789 --reply-to <message-id>
169
+ ```
170
+
171
+ ### 检查状态
172
+
173
+ ```bash
174
+ # 查看频道状态
175
+ openclaw channels
176
+
177
+ # 查看日志
178
+ openclaw logs --channel qq
179
+ ```
180
+
181
+ ### 消息目标格式
182
+
183
+ | 类型 | 格式 | 示例 |
184
+ |------|------|------|
185
+ | 私聊 | `private:<QQ号>` | `qq:private:123456789` |
186
+ | 群聊 | `group:<群号>` | `qq:group:123456` |
187
+
188
+ ---
189
+
190
+ ## 架构设计
191
+
192
+ ### 项目结构
193
+
194
+ ```
195
+ openclaw-channel-qq/
196
+ ├── src/
197
+ │ ├── channel.ts # 插件主入口
198
+ │ ├── core/
199
+ │ │ ├── connection.ts # WebSocket 连接管理
200
+ │ │ ├── dispatch.ts # 事件分发器
201
+ │ │ ├── request.ts # API 请求处理
202
+ │ │ ├── runtime.ts # 运行时状态管理
203
+ │ │ └── config.ts # 配置解析
204
+ │ ├── adapters/
205
+ │ │ └── message.ts # 消息格式转换
206
+ │ ├── types/
207
+ │ │ └── index.ts # TypeScript 类型定义
208
+ │ ├── utils/
209
+ │ │ ├── index.ts # 工具函数
210
+ │ │ ├── log.ts # 日志工具
211
+ │ │ ├── markdown.ts # Markdown 处理
212
+ │ │ └── cqcode.ts # CQ 码解析
213
+ │ └── onboarding.ts # 交互式配置向导
214
+ ├── docs/
215
+ │ ├── napcat-websocket-api.md # NapCat API 文档
216
+ │ └── plugin-development-guide.md # 插件开发指南
217
+ ├── openclaw.plugin.json # 插件清单
218
+ ├── package.json
219
+ └── tsconfig.json
220
+ ```
221
+
222
+ ### 核心组件
223
+
224
+ #### 1. ConnectionManager
225
+
226
+ 负责 WebSocket 连接的生命周期管理:
227
+ - 连接建立和断开
228
+ - 自动重连机制(指数退避)
229
+ - 心跳监测
230
+ - 请求/响应关联
231
+
232
+ #### 2. MessageAdapter
233
+
234
+ 处理 NapCat 与 OpenClaw 消息格式的双向转换:
235
+ - 入站消息解析(NapCat → OpenClaw)
236
+ - 出站消息构建(OpenClaw → NapCat)
237
+ - 支持多种消息段类型
238
+
239
+ #### 3. EventDispatcher
240
+
241
+ 处理 NapCat 上报的各类事件:
242
+ - 消息事件(私聊、群聊)
243
+ - 通知事件(戳一戳、撤回等)
244
+ - 元事件(心跳、生命周期)
245
+
246
+ ---
247
+
248
+ ## API 文档
249
+
250
+ ### 支持的消息类型
251
+
252
+ | 类型 | 入站 | 出站 | 说明 |
253
+ |------|------|------|------|
254
+ | `text` | ✓ | ✓ | 文本消息 |
255
+ | `at` | ✓ | ✓ | @提及 |
256
+ | `image` | ✓ | ✓ | 图片 |
257
+ | `face` | ✓ | - | QQ 表情 |
258
+ | `reply` | ✓ | ✓ | 消息回复 |
259
+ | `record` | ✓ | - | 语音消息 |
260
+ | `file` | ✓ | - | 文件 |
261
+ | `json` | ✓ | - | JSON 富文本 |
262
+
263
+ ### OneBot 11 接口
264
+
265
+ ```typescript
266
+ // 发送消息
267
+ {
268
+ action: 'send_msg',
269
+ params: {
270
+ message_type: 'private' | 'group',
271
+ user_id?: string, // 私聊时必填
272
+ group_id?: string, // 群聊时必填
273
+ message: NapCatMessage[] | string
274
+ }
275
+ }
276
+
277
+ // 获取消息
278
+ {
279
+ action: 'get_msg',
280
+ params: {
281
+ message_id: number
282
+ }
283
+ }
284
+
285
+ // 获取运行状态
286
+ {
287
+ action: 'get_status',
288
+ params: {}
289
+ }
290
+ ```
291
+
292
+ 完整 API 文档请参考 [NapCat WebSocket API 文档](./docs/napcat-websocket-api.md)。
293
+
294
+ ---
295
+
296
+ ## 开发指南
297
+
298
+ ### 开发环境设置
299
+
300
+ ```bash
301
+ # 安装依赖
302
+ npm install
303
+
304
+ # 开发模式(热重载)
305
+ npm run dev
306
+
307
+ # 构建
308
+ npm run build
309
+ ```
310
+
311
+ ### 目录结构说明
312
+
313
+ | 目录 | 说明 |
314
+ |------|------|
315
+ | `src/core/` | 核心功能模块 |
316
+ | `src/adapters/` | 适配器(消息转换等) |
317
+ | `src/types/` | TypeScript 类型定义 |
318
+ | `src/utils/` | 工具函数 |
319
+ | `docs/` | 文档 |
320
+ | `openspec/` | OpenSpec 规范 |
321
+
322
+ ### 插件开发参考
323
+
324
+ 详细开发指南请参考 [插件开发指南](./docs/plugin-development-guide.md)。
325
+
326
+ ---
327
+
328
+ ## 故障排查
329
+
330
+ ### 连接问题
331
+
332
+ 1. **检查 NapCat 是否运行**
333
+ ```bash
334
+ # 检查服务状态
335
+ curl http://localhost:3001/get_status
336
+ ```
337
+
338
+ 2. **验证 WebSocket 配置**
339
+ - 确认 `wsUrl` 与 NapCat 配置一致
340
+ - 检查访问令牌是否正确
341
+
342
+ 3. **防火墙设置**
343
+ - 确保 WebSocket 端口未被防火墙拦截
344
+
345
+ ### 消息发送失败
346
+
347
+ 1. **检查账号状态**
348
+ ```bash
349
+ openclaw channels
350
+ ```
351
+
352
+ 2. **查看日志**
353
+ ```bash
354
+ openclaw logs --channel qq --verbose
355
+ ```
356
+
357
+ 3. **验证消息格式**
358
+ - 确保目标格式正确:`private:<QQ>` 或 `group:<群号>`
359
+
360
+ ### 常见问题
361
+
362
+ | 问题 | 可能原因 | 解决方案 |
363
+ |------|----------|----------|
364
+ | 连接失败 | NapCat 未启动 | 启动 NapCat 服务 |
365
+ | 认证失败 | accessToken 错误 | 检查配置中的令牌 |
366
+ | 消息未收到 | 账号被禁用 | 检查 `enabled: true` |
367
+ | 图片发送失败 | URL 不可访问 | 确保图片 URL 可公网访问 |
368
+
369
+ ---
370
+
371
+ ## 相关链接
372
+
373
+ - [OpenClaw 官方文档](https://docs.openclaw.ai/)
374
+ - [OpenClaw 插件开发指南](https://docs.openclaw.ai/plugin)
375
+ - [NapCat GitHub](https://github.com/NapNeko/NapCatQQ)
376
+ - [OneBot 11 协议标准](https://github.com/botuniverse/onebot-11)
377
+
378
+ ---
379
+
380
+ ## 开源协议
381
+
382
+ [MIT](LICENSE) © izhimu
383
+
384
+ ---
385
+
386
+ <p align="center">
387
+ 如有问题或建议,欢迎提交 Issue 或 PR
388
+ </p>
@@ -3,22 +3,19 @@ export declare class MarkdownToText {
3
3
  private maskPrefix;
4
4
  private maskCounter;
5
5
  /**
6
- * 主入口:将 Markdown 转换为纯文本
7
- * @param markdown 原始 Markdown 字符串
6
+ * 主入口:将 Markdown 转换为纯文本(移动端优化版)
8
7
  */
9
8
  convert(markdown: string): string;
10
9
  /**
11
10
  * 保护代码块
12
- * 支持 ```language 和 ~~~ 两种写法
13
11
  */
14
12
  private maskCodeBlocks;
15
13
  /**
16
14
  * 保护行内代码
17
- * `code` -> 使用单引号包裹,区别于普通文本
18
15
  */
19
16
  private maskInlineCode;
20
17
  /**
21
- * 还原被掩码的内容
18
+ * 还原掩码内容
22
19
  */
23
20
  private unmaskContent;
24
21
  /**
@@ -1,116 +1,121 @@
1
1
  export class MarkdownToText {
2
- // 用于暂存被保护的代码块,防止被正则误伤
3
2
  codeBlockStore = new Map();
4
- maskPrefix = '%%MD_MASK_';
3
+ maskPrefix = '%%MD-MASK-';
5
4
  maskCounter = 0;
6
5
  /**
7
- * 主入口:将 Markdown 转换为纯文本
8
- * @param markdown 原始 Markdown 字符串
6
+ * 主入口:将 Markdown 转换为纯文本(移动端优化版)
9
7
  */
10
8
  convert(markdown) {
11
9
  if (!markdown)
12
10
  return '';
13
- // 1. 初始化
11
+ // 初始化
14
12
  this.codeBlockStore.clear();
15
13
  this.maskCounter = 0;
16
14
  let text = markdown;
17
- // --- 阶段 1: 保护性预处理 (Protect) ---
18
- // 必须最先执行,防止代码里的注释 # 被当成标题,或 ** 被当成粗体
15
+ // ============================================================
16
+ // 阶段 1: 保护性预处理 (Protect)
17
+ // ============================================================
19
18
  text = this.maskCodeBlocks(text);
20
19
  text = this.maskInlineCode(text);
21
- // --- 阶段 2: 结构化转换 (Structure) ---
22
- // 2.1 清理 HTML 标签 (保留 <br> 的换行效果)
20
+ // ============================================================
21
+ // 阶段 2: 优先处理特殊标签 (Priority Tags)
22
+ // ============================================================
23
+ // 2.1 图片 -> [图片] url(保留 URL,放在同一行)
24
+ text = text.replace(/!\[([^\]]*)]\(([^)]+)\)/g, (_match, alt, url) => {
25
+ const displayText = alt ? `[图片: ${alt}]` : '[图片]';
26
+ return `${displayText} ${url}`;
27
+ });
28
+ // 2.2 自动链接 <http://...> -> http://...
29
+ text = text.replace(/<((?:https?|ftp|email|mailto):[^>]+)>/g, '$1');
30
+ // 2.3 普通链接 [Text](url) -> Text: url(冒号分隔,更清晰)
31
+ text = text.replace(/\[([^\]]+)]\(([^)]+)\)/g, (_match, linkText, url) => {
32
+ // 如果链接文本就是 URL,只显示一次
33
+ if (linkText === url || linkText.trim() === url.trim()) {
34
+ return url;
35
+ }
36
+ return `${linkText}: ${url}`;
37
+ });
38
+ // ============================================================
39
+ // 阶段 3: 结构化转换 & 清理 (Structure & Clean)
40
+ // ============================================================
41
+ // 3.1 预处理换行和分割线标签
23
42
  text = text.replace(/<br\s*\/?>/gi, '\n');
24
- text = text.replace(/<hr\s*\/?>/gi, '\n────────────────────\n');
25
- text = text.replace(/<[^>]+>/g, ''); // 移除剩余所有标签
26
- // 2.2 标题 (Headers) -> 转换为视觉醒目的文本
27
- // H1/H2 使用双线/单线分隔,H3+ 使用括号包裹
28
- text = text.replace(/^#\s+(.*)$/gm, '\n$1\n════════════════════\n');
29
- text = text.replace(/^##\s+(.*)$/gm, '\n$1\n────────────────────\n');
30
- text = text.replace(/^(#{3,6})\s+(.*)$/gm, '\n【 $2 】\n');
31
- // 2.3 水平分割线 (---, ***, ___)
32
- text = text.replace(/^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm, '────────────────────');
33
- // 2.4 引用 (Blockquotes) -> 使用竖线前缀
34
- // 处理多级引用 >> Text
35
- text = text.replace(/^(>+)\s?(.*)$/gm, (_match, _arrows, content) => {
36
- return `▎ ${content}`;
43
+ text = text.replace(/<hr\s*\/?>/gi, '\n---\n');
44
+ // 3.2 安全清理 HTML 标签
45
+ text = text.replace(/<(script|style)[^>]*>[\s\S]*?<\/\1>/gi, '');
46
+ text = text.replace(/<!--[\s\S]*?-->/g, '');
47
+ text = text.replace(/<\/?([a-z][a-z0-9]*)\b[^>]*>/gi, '');
48
+ // 3.3 标题 -> 简洁样式
49
+ text = text.replace(/^#\s+(.*)$/gm, '\n【$1】\n');
50
+ text = text.replace(/^##\s+(.*)$/gm, '\n■ $1\n');
51
+ text = text.replace(/^(#{3,6})\s+(.*)$/gm, '\n▸ $2\n');
52
+ // 3.4 Markdown 分割线
53
+ text = text.replace(/^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm, '---');
54
+ // 3.5 引用
55
+ text = text.replace(/^(>+)\s?(.*)$/gm, (_match, arrows, content) => {
56
+ const level = arrows.length;
57
+ return level > 1 ? ` ${content}` : `${content}`;
37
58
  });
38
- // 2.5 任务列表 (Task Lists)
39
- text = text.replace(/^(\s*)-\s\[x]\s/gim, '$1 '); // 完成
40
- text = text.replace(/^(\s*)-\s\[\s]\s/gim, '$1 '); // 未完成
41
- // 2.6 无序与有序列表 (Lists)
42
- // 保留 $1 (缩进空格),将 -/*/+ 替换为 •
43
- text = text.replace(/^(\s*)[-*+]\s+(.*)$/gm, '$1• $2');
44
- // 有序列表 1. 2. 通常不需要改动,保留原样即可
45
- // 2.7 表格 (Tables)
46
- // 移除对齐行 |---|---|
59
+ // 3.6 任务列表 & 无序列表
60
+ text = text.replace(/^(\s*)-\s\[x]\s/gim, '$1 ');
61
+ text = text.replace(/^(\s*)-\s\[\s]\s/gim, '$1 ');
62
+ text = text.replace(/^(\s*)[-*+]\s+(.*)$/gm, '$1· $2');
63
+ // 3.7 表格
47
64
  text = text.replace(/^\s*\|?[\s\-:|]+\|?\s*$/gm, '');
48
- // 将 | Cell | Cell | 转换为空格分隔,尽量保持一行
49
65
  text = text.replace(/^\|(.*)\|$/gm, (_match, content) => {
50
- // 移除首尾管道符,中间管道符变为空格
51
- return content.split('|').map((s) => s.trim()).join(' ');
66
+ return content.split('|').map((s) => s.trim()).join(' | ');
52
67
  });
53
- // --- 阶段 3: 行内格式清理 (Inline Formatting) ---
54
- // 3.1 粗体 (Bold) -> 使用中文引号或双星号强调
55
- // 使用 [\s\S] 确保匹配跨行粗体
56
- text = text.replace(/(\*\*|__)([\s\S]*?)\1/g, '“$2”');
57
- // 3.2 斜体 (Italic) -> 直接移除符号,纯文本很难表现斜体
58
- // 注意:必须在处理完粗体后处理斜体
68
+ // ============================================================
69
+ // 阶段 4: 行内格式 (Inline Formatting)
70
+ // ============================================================
71
+ // 4.1 粗体 -> 保留文字
72
+ text = text.replace(/(\*\*|__)([\s\S]*?)\1/g, '$2');
73
+ // 4.2 斜体 -> 保留文字
59
74
  text = text.replace(/([*_])([\s\S]*?)\1/g, '$2');
60
- // 3.3 删除线 (Strikethrough) -> 移除内容或仅移除符号?通常仅移除符号
75
+ // 4.3 删除线 -> 保留文字
61
76
  text = text.replace(/~~([\s\S]*?)~~/g, '$1');
62
- // 3.4 图片 (Images) -> 转换为占位符
63
- text = text.replace(/!\[([^\]]*)]\(([^)]+)\)/g, (_match, alt) => {
64
- return `[图片: ${alt || 'Image'}]`;
65
- });
66
- // 3.5 链接 (Links) -> Text (URL)
67
- // 排除锚点链接或空链接
68
- text = text.replace(/\[([^\]]+)]\(([^)]+)\)/g, '$1 ($2)');
69
- // 处理自动链接 <http://example.com>
70
- text = text.replace(/<((?:https?|ftp|email):[^>]+)>/g, '$1');
71
- // --- 阶段 4: 还原与收尾 (Restore & Finalize) ---
72
- // 4.1 还原代码块
77
+ // ============================================================
78
+ // 阶段 5: 还原与收尾 (Restore & Finalize)
79
+ // ============================================================
80
+ // 5.1 还原代码块
73
81
  text = this.unmaskContent(text);
74
- // 4.2 解码 HTML 实体 (&amp; -> &)
82
+ // 5.2 解码 HTML 实体
75
83
  text = this.decodeHtmlEntities(text);
76
- // 4.3 最终排版优化
77
- // 移除段首段尾多余空白,将连续3个以上换行压缩为2个(段落间距)
78
- text = text.replace(/\n{3,}/g, '\n\n').trim();
84
+ // 5.3 最终排版优化
85
+ text = text.replace(/\n{3,}/g, '\n\n');
86
+ text = text.replace(/[ \t]+/g, ' ');
87
+ text = text.replace(/^\s+|\s+$/gm, '');
88
+ text = text.trim();
79
89
  return text;
80
90
  }
81
91
  /**
82
92
  * 保护代码块
83
- * 支持 ```language 和 ~~~ 两种写法
84
93
  */
85
94
  maskCodeBlocks(text) {
86
- // 匹配 3个或更多反引号/波浪线
87
- const codeBlockRegex = /(`{3,}|~{3,})(\w*)\n([\s\S]*?)\1/g;
95
+ const codeBlockRegex = /(`{3,}|~{3,})(\w*)\n?([\s\S]*?)\n?\1/g;
88
96
  return text.replace(codeBlockRegex, (_match, _fence, lang, code) => {
89
- const key = `${this.maskPrefix}BLOCK_${this.maskCounter++}`;
90
- const langTag = lang ? ` [${lang}]` : '';
91
- // 构造美观的代码块样式
92
- const formatted = `\n────────────────────${langTag}\n${code.replace(/^\n+|\n+$/g, '')}\n────────────────────\n`;
97
+ const key = `${this.maskPrefix}BLOCK-${this.maskCounter++}`;
98
+ const langTag = lang ? `[${lang}]\n` : '';
99
+ const formatted = `\n${langTag}${code.trim()}\n`;
93
100
  this.codeBlockStore.set(key, formatted);
94
101
  return key;
95
102
  });
96
103
  }
97
104
  /**
98
105
  * 保护行内代码
99
- * `code` -> 使用单引号包裹,区别于普通文本
100
106
  */
101
107
  maskInlineCode(text) {
102
108
  return text.replace(/`([^`]+)`/g, (_match, code) => {
103
- const key = `${this.maskPrefix}INLINE_${this.maskCounter++}`;
104
- this.codeBlockStore.set(key, ` ‘${code}`); // 加空格防止粘连
109
+ const key = `${this.maskPrefix}INLINE-${this.maskCounter++}`;
110
+ this.codeBlockStore.set(key, `'${code}'`);
105
111
  return key;
106
112
  });
107
113
  }
108
114
  /**
109
- * 还原被掩码的内容
115
+ * 还原掩码内容
110
116
  */
111
117
  unmaskContent(text) {
112
- // 使用正则全局匹配所有 mask key
113
- const maskRegex = new RegExp(`${this.maskPrefix}\\w+_\\d+`, 'g');
118
+ const maskRegex = new RegExp(`${this.maskPrefix}[\\w-]+`, 'g');
114
119
  return text.replace(maskRegex, (key) => {
115
120
  return this.codeBlockStore.get(key) || '';
116
121
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izhimu/qq",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A QQ channel plugin for OpenClaw using NapCat WebSocket",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",