@izhimu/qq 0.5.0 → 0.6.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 +21 -18
- package/dist/index.d.ts +5 -11
- package/dist/index.js +9 -18
- package/dist/src/adapters/message.d.ts +15 -4
- package/dist/src/adapters/message.js +179 -124
- package/dist/src/channel.d.ts +2 -7
- package/dist/src/channel.js +247 -278
- package/dist/src/core/auth.d.ts +67 -0
- package/dist/src/core/auth.js +154 -0
- package/dist/src/core/config.d.ts +7 -8
- package/dist/src/core/config.js +9 -9
- package/dist/src/core/connection.d.ts +6 -5
- package/dist/src/core/connection.js +17 -70
- package/dist/src/core/dispatch.d.ts +7 -54
- package/dist/src/core/dispatch.js +210 -398
- package/dist/src/core/event-handler.d.ts +42 -0
- package/dist/src/core/event-handler.js +171 -0
- package/dist/src/core/request.d.ts +3 -8
- package/dist/src/core/request.js +13 -126
- package/dist/src/core/runtime.d.ts +2 -11
- package/dist/src/core/runtime.js +0 -50
- package/dist/src/runtime.d.ts +3 -0
- package/dist/src/runtime.js +3 -0
- package/dist/src/setup-surface.d.ts +2 -0
- package/dist/src/setup-surface.js +59 -0
- package/dist/src/types/index.d.ts +69 -24
- package/dist/src/types/index.js +3 -4
- package/dist/src/utils/cqcode.d.ts +0 -9
- package/dist/src/utils/cqcode.js +0 -17
- package/dist/src/utils/index.d.ts +0 -17
- package/dist/src/utils/index.js +17 -154
- package/dist/src/utils/log.js +6 -3
- package/dist/src/utils/markdown.d.ts +5 -0
- package/dist/src/utils/markdown.js +57 -5
- package/openclaw.plugin.json +3 -2
- package/package.json +9 -11
- package/dist/src/onboarding.d.ts +0 -10
- package/dist/src/onboarding.js +0 -98
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
</p>
|
|
28
28
|
|
|
29
29
|
---
|
|
30
|
-

|
|
31
31
|
## 目录
|
|
32
32
|
|
|
33
33
|
- [功能特性](#功能特性)
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
openclaw plugins install @izhimu/qq
|
|
70
70
|
|
|
71
71
|
# 更新插件
|
|
72
|
-
openclaw plugins update
|
|
72
|
+
openclaw plugins update qq
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
### 本地开发安装
|
|
@@ -175,31 +175,17 @@ openclaw gateway restart
|
|
|
175
175
|
}
|
|
176
176
|
```
|
|
177
177
|
|
|
178
|
+

|
|
179
|
+
|
|
178
180
|
---
|
|
179
181
|
|
|
180
182
|
## 使用方法
|
|
181
183
|
|
|
182
|
-
### 发送消息
|
|
183
|
-
|
|
184
|
-
```bash
|
|
185
|
-
# 发送私聊消息
|
|
186
|
-
openclaw message send "你好!" --to qq:private:123456789
|
|
187
|
-
|
|
188
|
-
# 发送群消息
|
|
189
|
-
openclaw message send "大家好!" --to qq:group:123456
|
|
190
|
-
|
|
191
|
-
# 带回复的消息
|
|
192
|
-
openclaw message send "回复你的消息" --to qq:private:123456789 --reply-to <message-id>
|
|
193
|
-
```
|
|
194
|
-
|
|
195
184
|
### 检查状态
|
|
196
185
|
|
|
197
186
|
```bash
|
|
198
187
|
# 查看频道状态
|
|
199
188
|
openclaw channels
|
|
200
|
-
|
|
201
|
-
# 查看日志
|
|
202
|
-
openclaw logs --channel qq
|
|
203
189
|
```
|
|
204
190
|
|
|
205
191
|
### 消息目标格式
|
|
@@ -391,6 +377,23 @@ npm run build
|
|
|
391
377
|
|
|
392
378
|
## 更新日志
|
|
393
379
|
|
|
380
|
+
### [0.6.0] - 2026-03-25
|
|
381
|
+
|
|
382
|
+
#### ⚠️ 重大变更
|
|
383
|
+
- **OpenClaw SDK 升级**:适配 OpenClaw SDK `2026.3.22` 版本,低于此版本的 OpenClaw 将无法使用本插件。
|
|
384
|
+
|
|
385
|
+
#### 重构
|
|
386
|
+
- **连接管理**:重构连接管理逻辑,优化 WebSocket 连接生命周期。
|
|
387
|
+
- **消息处理**:重构消息处理逻辑,优化适配器结构。
|
|
388
|
+
- **配置管理**:统一配置管理和健康状态处理。
|
|
389
|
+
- **频道模块**:重构频道模块代码结构。
|
|
390
|
+
- **授权系统**:重构 QQ 频道授权和配置系统。
|
|
391
|
+
|
|
392
|
+
### [0.5.1] - 2026-03-12
|
|
393
|
+
|
|
394
|
+
#### 修复
|
|
395
|
+
- **连接状态显示**:修复了插件面板中连接状态及错误信息显示不准确的问题。
|
|
396
|
+
|
|
394
397
|
### [0.5.0] - 2026-03-11
|
|
395
398
|
|
|
396
399
|
#### 新增
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
* QQ NapCat Plugin Entry Point
|
|
3
|
-
* Exports the plugin for OpenClaw to load
|
|
4
|
-
*/
|
|
5
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
6
|
-
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
7
|
-
declare const plugin: {
|
|
1
|
+
declare const _default: {
|
|
8
2
|
id: string;
|
|
9
3
|
name: string;
|
|
10
4
|
description: string;
|
|
11
|
-
configSchema:
|
|
12
|
-
register
|
|
13
|
-
}
|
|
14
|
-
export default
|
|
5
|
+
configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
|
|
6
|
+
register: NonNullable<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition["register"]>;
|
|
7
|
+
} & Pick<import("openclaw/plugin-sdk/core").OpenClawPluginDefinition, "kind">;
|
|
8
|
+
export default _default;
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { qqPlugin } from "./src/channel.js";
|
|
7
|
-
import { setRuntime } from "./src/core/runtime.js";
|
|
8
|
-
import { CHANNEL_ID } from "./src/core/config.js";
|
|
9
|
-
const plugin = {
|
|
10
|
-
id: CHANNEL_ID,
|
|
1
|
+
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
|
2
|
+
import { qqPlugin } from "./src/channel";
|
|
3
|
+
import { setQQRuntime } from "./src/runtime";
|
|
4
|
+
export default defineChannelPluginEntry({
|
|
5
|
+
id: "qq",
|
|
11
6
|
name: "QQ",
|
|
12
|
-
description: "QQ channel plugin
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
api.registerChannel({ plugin: qqPlugin });
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
export default plugin;
|
|
7
|
+
description: "QQ Chat channel plugin",
|
|
8
|
+
plugin: qqPlugin,
|
|
9
|
+
setRuntime: setQQRuntime,
|
|
10
|
+
});
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Message Type Adapters for NapCat <-> OpenClaw conversion
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 使用映射表驱动的统一消息转换系统
|
|
5
5
|
*/
|
|
6
|
-
import type { NapCatMessage, OpenClawMessage } from '../types';
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function
|
|
6
|
+
import type { NapCatMessage, OpenClawMessage, QQAccount } from '../types';
|
|
7
|
+
export declare function outboundMessageAdapter(content: OpenClawMessage[], account: QQAccount): Promise<NapCatMessage[]>;
|
|
8
|
+
export declare function inboundMessageAdapter(segments: NapCatMessage[] | string): Promise<OpenClawMessage[]>;
|
|
9
|
+
export type TextFormatter = (content: OpenClawMessage) => string | null;
|
|
10
|
+
/** 将 OpenClaw 消息内容转换为纯文本 */
|
|
11
|
+
export declare function formatContentToText(content: OpenClawMessage[]): string;
|
|
12
|
+
/** 检查是否包含媒体 */
|
|
13
|
+
export declare function hasMediaContent(content: OpenClawMessage[]): boolean;
|
|
14
|
+
/** 从消息内容提取媒体信息 */
|
|
15
|
+
export declare function extractMedia(content: OpenClawMessage[]): {
|
|
16
|
+
type: string;
|
|
17
|
+
path?: string;
|
|
18
|
+
url?: string;
|
|
19
|
+
} | undefined;
|
|
@@ -1,171 +1,226 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Message Type Adapters for NapCat <-> OpenClaw conversion
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 使用映射表驱动的统一消息转换系统
|
|
5
5
|
*/
|
|
6
|
-
import { Logger as log, extractImageUrl, getEmojiForFaceId } from '../utils/index.js';
|
|
6
|
+
import { Logger as log, extractImageUrl, getEmojiForFaceId, markdownToText } from '../utils/index.js';
|
|
7
7
|
import { CQCodeUtils } from '../utils';
|
|
8
8
|
import { getMsg } from "../core/request.js";
|
|
9
9
|
// =============================================================================
|
|
10
|
+
// 工具函数
|
|
11
|
+
// =============================================================================
|
|
12
|
+
/** 安全提取字符串字段 */
|
|
13
|
+
const str = (data, key) => String(data[key] ?? '');
|
|
14
|
+
/** 安全提取数字字段 */
|
|
15
|
+
const num = (data, key) => {
|
|
16
|
+
const v = data[key];
|
|
17
|
+
return v != null ? parseInt(String(v), 10) : undefined;
|
|
18
|
+
};
|
|
19
|
+
// =============================================================================
|
|
10
20
|
// CQ Code Parsing
|
|
11
21
|
// =============================================================================
|
|
12
|
-
/**
|
|
13
|
-
* Convert CQNode to NapCatMessageSegment
|
|
14
|
-
*/
|
|
22
|
+
/** 将 CQNode 转换为 NapCatMessageSegment */
|
|
15
23
|
function cqNodeToNapCat(node) {
|
|
16
|
-
return {
|
|
17
|
-
type: node.type,
|
|
18
|
-
data: node.data,
|
|
19
|
-
};
|
|
24
|
+
return { type: node.type, data: node.data };
|
|
20
25
|
}
|
|
21
|
-
/**
|
|
22
|
-
* Parse CQ codes using CQCodeUtils and convert to NapCatMessageSegment[]
|
|
23
|
-
*/
|
|
26
|
+
/** 解析 CQ 码 */
|
|
24
27
|
function parseCQCode(text) {
|
|
25
|
-
|
|
26
|
-
return nodes.map(cqNodeToNapCat);
|
|
28
|
+
return CQCodeUtils.parse(text).map(cqNodeToNapCat);
|
|
27
29
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Normalize message to segments array (handles string or array format)
|
|
30
|
-
*/
|
|
30
|
+
/** 标准化消息格式 */
|
|
31
31
|
function normalizeMessage(message) {
|
|
32
|
-
if (typeof message === 'string')
|
|
32
|
+
if (typeof message === 'string')
|
|
33
33
|
return parseCQCode(message);
|
|
34
|
-
}
|
|
35
34
|
if (!Array.isArray(message)) {
|
|
36
35
|
log.warn('adapters', `Invalid message format: ${typeof message}`);
|
|
37
36
|
return [{ type: 'text', data: { text: String(message) } }];
|
|
38
37
|
}
|
|
39
38
|
return message;
|
|
40
39
|
}
|
|
40
|
+
// =============================================================================
|
|
41
|
+
// JSON 消息解析
|
|
42
|
+
// =============================================================================
|
|
41
43
|
function parseJsonSegment(segment) {
|
|
44
|
+
const rawData = segment.data.data.trim();
|
|
45
|
+
let prompt;
|
|
42
46
|
try {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
jsonData = JSON.parse(rawData);
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
log.warn('adapters', `Failed to parse JSON message: ${error}`);
|
|
50
|
-
}
|
|
51
|
-
const result = {
|
|
52
|
-
type: 'json',
|
|
53
|
-
data: rawData,
|
|
54
|
-
};
|
|
55
|
-
if (jsonData?.prompt && jsonData.prompt.trim() !== '') {
|
|
56
|
-
result.prompt = jsonData.prompt;
|
|
47
|
+
const jsonData = JSON.parse(rawData);
|
|
48
|
+
if (jsonData?.prompt?.trim()) {
|
|
49
|
+
prompt = jsonData.prompt;
|
|
57
50
|
}
|
|
58
|
-
return result;
|
|
59
51
|
}
|
|
60
|
-
catch
|
|
61
|
-
log.warn('adapters',
|
|
62
|
-
return null;
|
|
52
|
+
catch {
|
|
53
|
+
log.warn('adapters', 'Failed to parse JSON message');
|
|
63
54
|
}
|
|
55
|
+
return { type: 'json', data: rawData, ...(prompt && { prompt }) };
|
|
64
56
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
const inboundConverters = {
|
|
58
|
+
text: (data) => ({ type: 'text', text: str(data, 'text') }),
|
|
59
|
+
at: (data) => ({
|
|
60
|
+
type: 'at',
|
|
61
|
+
userId: str(data, 'qq'),
|
|
62
|
+
isAll: data.qq === 'all',
|
|
63
|
+
}),
|
|
64
|
+
image: (data) => {
|
|
65
|
+
const url = extractImageUrl(data);
|
|
66
|
+
return url ? { type: 'image', url, summary: data.summary } : null;
|
|
67
|
+
},
|
|
68
|
+
reply: async (data) => {
|
|
69
|
+
const response = await getMsg({ message_id: Number(data.id) });
|
|
70
|
+
if (!response.data?.message)
|
|
71
|
+
return null;
|
|
72
|
+
return {
|
|
73
|
+
type: 'reply',
|
|
74
|
+
messageId: str(data, 'id'),
|
|
75
|
+
message: response.data.raw_message,
|
|
76
|
+
senderId: String(response.data.sender.user_id),
|
|
77
|
+
sender: response.data.sender.nickname,
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
video: (data) => ({
|
|
81
|
+
type: 'video',
|
|
82
|
+
url: str(data, 'url'),
|
|
83
|
+
fileSize: num(data, 'file_size'),
|
|
84
|
+
}),
|
|
85
|
+
face: (data) => ({ type: 'text', text: getEmojiForFaceId(str(data, 'id')) }),
|
|
86
|
+
record: (data) => data.path ? {
|
|
87
|
+
type: 'audio',
|
|
88
|
+
path: str(data, 'path'),
|
|
89
|
+
file: str(data, 'file'),
|
|
90
|
+
url: data.url,
|
|
91
|
+
fileSize: num(data, 'file_size'),
|
|
92
|
+
} : null,
|
|
93
|
+
file: (data) => ({
|
|
94
|
+
type: 'file',
|
|
95
|
+
fileId: str(data, 'file'),
|
|
96
|
+
fileSize: num(data, 'file_size'),
|
|
97
|
+
}),
|
|
98
|
+
json: (data) => parseJsonSegment({ type: 'json', data: { data: str(data, 'data') } }),
|
|
99
|
+
};
|
|
68
100
|
async function napCatToOpenClaw(segment) {
|
|
69
101
|
const data = segment.data;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
type: 'at',
|
|
76
|
-
userId: String(data.qq || ''),
|
|
77
|
-
isAll: data.qq === 'all',
|
|
78
|
-
};
|
|
79
|
-
case 'image': {
|
|
80
|
-
const url = extractImageUrl(data);
|
|
81
|
-
return url ? { type: 'image', url, summary: data.summary } : null;
|
|
82
|
-
}
|
|
83
|
-
case 'reply':
|
|
84
|
-
const response = await getMsg({
|
|
85
|
-
message_id: Number(data.id),
|
|
86
|
-
});
|
|
87
|
-
if (response.data?.message == undefined) {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
type: 'reply',
|
|
92
|
-
messageId: String(data.id),
|
|
93
|
-
message: response.data.raw_message,
|
|
94
|
-
senderId: String(response.data.sender.user_id),
|
|
95
|
-
sender: response.data.sender.nickname
|
|
96
|
-
};
|
|
97
|
-
case 'video':
|
|
98
|
-
return {
|
|
99
|
-
type: 'video',
|
|
100
|
-
url: String(data.url || ''),
|
|
101
|
-
fileSize: data.file_size ? parseInt(String(data.file_size), 10) : undefined,
|
|
102
|
-
};
|
|
103
|
-
case 'face':
|
|
104
|
-
return { type: 'text', text: getEmojiForFaceId(String(data.id || '')) };
|
|
105
|
-
case 'record':
|
|
106
|
-
return data.path ? {
|
|
107
|
-
type: 'audio',
|
|
108
|
-
path: String(data.path),
|
|
109
|
-
file: String(data.file || ''),
|
|
110
|
-
url: data.url,
|
|
111
|
-
fileSize: data.file_size ? parseInt(String(data.file_size), 10) : undefined,
|
|
112
|
-
} : null;
|
|
113
|
-
case 'file':
|
|
114
|
-
return {
|
|
115
|
-
type: 'file',
|
|
116
|
-
fileId: String(data.file || ''),
|
|
117
|
-
fileSize: data.file_size ? parseInt(String(data.file_size), 10) : undefined
|
|
118
|
-
};
|
|
119
|
-
case 'json':
|
|
120
|
-
return parseJsonSegment(segment);
|
|
121
|
-
default:
|
|
122
|
-
log.warn('adapters', `Unknown message type (inbound): ${segment.type}`);
|
|
123
|
-
return null;
|
|
102
|
+
const converter = inboundConverters[segment.type];
|
|
103
|
+
if (!converter) {
|
|
104
|
+
log.warn('adapters', `Unknown message type (inbound): ${segment.type}`);
|
|
105
|
+
return null;
|
|
124
106
|
}
|
|
107
|
+
return converter(data);
|
|
125
108
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
109
|
+
const outboundConverters = {
|
|
110
|
+
text: (content, account) => ({
|
|
111
|
+
type: 'text',
|
|
112
|
+
data: { text: account.markdownFormat ? markdownToText(content.text) : content.text },
|
|
113
|
+
}),
|
|
114
|
+
at: (content) => {
|
|
115
|
+
const { isAll, userId } = content;
|
|
116
|
+
return { type: 'at', data: { qq: isAll ? 'all' : userId } };
|
|
117
|
+
},
|
|
118
|
+
image: (content) => {
|
|
119
|
+
const { url } = content;
|
|
120
|
+
return { type: 'image', data: { file: url, url } };
|
|
121
|
+
},
|
|
122
|
+
reply: (content) => ({
|
|
123
|
+
type: 'reply',
|
|
124
|
+
data: { id: content.messageId },
|
|
125
|
+
}),
|
|
126
|
+
file: (content) => {
|
|
127
|
+
const c = content;
|
|
128
|
+
return { type: 'file', data: { file: c.file, url: c.url, file_size: c.fileSize } };
|
|
129
|
+
},
|
|
130
|
+
audio: (content) => {
|
|
131
|
+
const c = content;
|
|
132
|
+
return { type: 'record', data: { file: c.file, path: c.path, url: c.url, file_size: c.fileSize } };
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
function openClawToNapCat(content, account) {
|
|
136
|
+
const converter = outboundConverters[content.type];
|
|
137
|
+
if (!converter) {
|
|
138
|
+
log.warn('adapters', `Unknown content type (outbound): ${content.type}`);
|
|
139
|
+
return null;
|
|
149
140
|
}
|
|
141
|
+
return converter(content, account);
|
|
150
142
|
}
|
|
151
|
-
|
|
143
|
+
// =============================================================================
|
|
144
|
+
// 导出 API
|
|
145
|
+
// =============================================================================
|
|
146
|
+
export async function outboundMessageAdapter(content, account) {
|
|
152
147
|
const segments = [];
|
|
153
148
|
for (const item of content) {
|
|
154
|
-
const segment =
|
|
155
|
-
if (segment)
|
|
149
|
+
const segment = openClawToNapCat(item, account);
|
|
150
|
+
if (segment)
|
|
156
151
|
segments.push(segment);
|
|
157
|
-
}
|
|
158
152
|
}
|
|
159
153
|
return segments;
|
|
160
154
|
}
|
|
161
|
-
export async function
|
|
155
|
+
export async function inboundMessageAdapter(segments) {
|
|
162
156
|
const normalized = normalizeMessage(segments);
|
|
163
157
|
const content = [];
|
|
164
158
|
for (const segment of normalized) {
|
|
165
159
|
const result = await napCatToOpenClaw(segment);
|
|
166
|
-
if (result)
|
|
160
|
+
if (result)
|
|
167
161
|
content.push(result);
|
|
168
|
-
}
|
|
169
162
|
}
|
|
170
163
|
return content;
|
|
171
164
|
}
|
|
165
|
+
const textFormatters = {
|
|
166
|
+
text: (c) => c.text,
|
|
167
|
+
at: (c) => {
|
|
168
|
+
const { isAll, userId } = c;
|
|
169
|
+
return `[提及]${isAll ? '@全体成员' : `@${userId || 'unknown'}`}`;
|
|
170
|
+
},
|
|
171
|
+
image: (c) => `[图片]${c.url || ''}`,
|
|
172
|
+
audio: (c) => `[音频]${c.path || ''}`,
|
|
173
|
+
video: (c) => `[视频]${c.url || ''}`,
|
|
174
|
+
file: (c) => `[文件]${c.fileId || ''}`,
|
|
175
|
+
json: (c) => `[JSON]\n\n\`\`\`json\n${c.data || ''}\n\`\`\``,
|
|
176
|
+
reply: (c) => {
|
|
177
|
+
const { sender, senderId, message } = c;
|
|
178
|
+
const senderInfo = sender && senderId ? `${sender}(${senderId})` : '(未知用户)';
|
|
179
|
+
const replyMsg = message ?? '(无法获取原消息)';
|
|
180
|
+
const quotedContent = `${senderInfo}:\n${replyMsg}`.replace(/^/gm, '> ');
|
|
181
|
+
return `[回复]\n\n${quotedContent}`;
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
/** 将 OpenClaw 消息内容转换为纯文本 */
|
|
185
|
+
export function formatContentToText(content) {
|
|
186
|
+
return content
|
|
187
|
+
.map(c => textFormatters[c.type]?.(c) ?? null)
|
|
188
|
+
.filter((v) => v !== null)
|
|
189
|
+
.join('\n');
|
|
190
|
+
}
|
|
191
|
+
const mediaExtractors = [
|
|
192
|
+
{
|
|
193
|
+
check: (c) => c.some(x => x.type === 'image'),
|
|
194
|
+
extract: (c) => {
|
|
195
|
+
const img = c.find(x => x.type === 'image');
|
|
196
|
+
return img ? { type: 'image/jpeg', path: img.url, url: img.url } : undefined;
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
check: (c) => c.some(x => x.type === 'audio'),
|
|
201
|
+
extract: (c) => {
|
|
202
|
+
const audio = c.find(x => x.type === 'audio');
|
|
203
|
+
return audio ? { type: 'audio/amr', path: audio.path, url: audio.url } : undefined;
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
check: (c) => c.some(x => x.type === 'file'),
|
|
208
|
+
extract: (c) => {
|
|
209
|
+
const file = c.find(x => x.type === 'file');
|
|
210
|
+
return file ? { type: 'application/octet-stream', path: file.file, url: file.url } : undefined;
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
];
|
|
214
|
+
/** 检查是否包含媒体 */
|
|
215
|
+
export function hasMediaContent(content) {
|
|
216
|
+
return content.some(c => c.type === 'image' || c.type === 'audio' || c.type === 'file');
|
|
217
|
+
}
|
|
218
|
+
/** 从消息内容提取媒体信息 */
|
|
219
|
+
export function extractMedia(content) {
|
|
220
|
+
for (const extractor of mediaExtractors) {
|
|
221
|
+
if (extractor.check(content)) {
|
|
222
|
+
return extractor.extract(content);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
package/dist/src/channel.d.ts
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Main plugin entry point
|
|
4
|
-
*/
|
|
5
|
-
import type { ChannelPlugin } from "openclaw/plugin-sdk";
|
|
6
|
-
import type { QQConfig } from "./types";
|
|
7
|
-
export declare const qqPlugin: ChannelPlugin<QQConfig>;
|
|
1
|
+
import type { QQAccount } from "./types";
|
|
2
|
+
export declare const qqPlugin: import("openclaw/plugin-sdk").ChannelPlugin<QQAccount, unknown, unknown>;
|