@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
|
@@ -1,7 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/core";
|
|
2
|
+
export interface QQAccount {
|
|
3
|
+
accountId: string;
|
|
4
|
+
wsUrl: string;
|
|
5
|
+
token?: string;
|
|
6
|
+
accessToken?: string;
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
markdownFormat: boolean;
|
|
9
|
+
messageDirect: QQAllowConfig;
|
|
10
|
+
messageGroup: QQGroupConfig;
|
|
11
|
+
messageGroupsCustom: Record<string, QQGroupConfig>;
|
|
12
|
+
}
|
|
13
|
+
export interface InboundMessage {
|
|
14
|
+
targetId: string;
|
|
15
|
+
messageId: string;
|
|
16
|
+
senderId: string;
|
|
17
|
+
senderName?: string;
|
|
18
|
+
text: string;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
isGroup: boolean;
|
|
21
|
+
groupId?: string;
|
|
22
|
+
wasMentioned?: boolean;
|
|
23
|
+
replyToId?: string;
|
|
24
|
+
hasMedia?: boolean;
|
|
25
|
+
media?: DispatchMessageMedia;
|
|
26
|
+
authorization?: {
|
|
27
|
+
isAuthorizedSender: boolean;
|
|
28
|
+
denialReason?: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export interface ProcessInboundParams {
|
|
32
|
+
cfg: OpenClawConfig;
|
|
33
|
+
account: QQAccount;
|
|
34
|
+
runtime: PluginRuntime;
|
|
35
|
+
msg: InboundMessage;
|
|
36
|
+
}
|
|
5
37
|
export interface NapCatReq<T = unknown> {
|
|
6
38
|
action: NapCatAction;
|
|
7
39
|
params?: T;
|
|
@@ -26,6 +58,39 @@ export interface NapCatMetaEvent extends NapCatEvent {
|
|
|
26
58
|
meta_event_type: 'lifecycle' | 'heartbeat';
|
|
27
59
|
sub_type?: 'connect' | 'disconnect' | 'enable' | 'disable';
|
|
28
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* NapCat 消息事件类型
|
|
63
|
+
*/
|
|
64
|
+
export interface NapCatMessageEvent extends NapCatEvent {
|
|
65
|
+
post_type: "message";
|
|
66
|
+
message_type: "group" | "private";
|
|
67
|
+
message: NapCatMessage[];
|
|
68
|
+
raw_message: string;
|
|
69
|
+
user_id: number;
|
|
70
|
+
group_id?: number;
|
|
71
|
+
message_id?: number;
|
|
72
|
+
time: number;
|
|
73
|
+
sender?: {
|
|
74
|
+
nickname?: string;
|
|
75
|
+
card?: string;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* NapCat 通知事件类型
|
|
80
|
+
*/
|
|
81
|
+
export interface NapCatNoticeEvent extends NapCatEvent {
|
|
82
|
+
post_type: "notice";
|
|
83
|
+
notice_type: "poke" | "notify";
|
|
84
|
+
sub_type?: string;
|
|
85
|
+
user_id: number;
|
|
86
|
+
target_id: number;
|
|
87
|
+
group_id?: number;
|
|
88
|
+
time: number;
|
|
89
|
+
raw_info?: Array<{
|
|
90
|
+
type: string;
|
|
91
|
+
txt?: string;
|
|
92
|
+
}>;
|
|
93
|
+
}
|
|
29
94
|
export type NapCatMessage = NapCatTextSegment | NapCatAtSegment | NapCatImageSegment | NapCatReplySegment | NapCatFaceSegment | NapCatRecordSegment | NapCatFileSegment | NapCatJsonSegment | NapCatUnknownSegment | NapCatVideoSegment;
|
|
30
95
|
export interface NapCatTextSegment {
|
|
31
96
|
type: 'text';
|
|
@@ -100,9 +165,7 @@ export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'fai
|
|
|
100
165
|
export interface ConnectionStatus {
|
|
101
166
|
state: ConnectionState;
|
|
102
167
|
lastConnected?: number;
|
|
103
|
-
lastAttempted?: number;
|
|
104
168
|
error?: string;
|
|
105
|
-
reconnectAttempts?: number;
|
|
106
169
|
}
|
|
107
170
|
export type OpenClawMessage = OpenClawTextContent | OpenClawAtContent | OpenClawImageContent | OpenClawReplyContent | OpenClawAudioContent | OpenClawJsonContent | OpenClawFileContent | OpenClawVideoContent;
|
|
108
171
|
export interface OpenClawTextContent {
|
|
@@ -276,15 +339,6 @@ export interface DispatchMessageParams {
|
|
|
276
339
|
timestamp: number;
|
|
277
340
|
targetId?: string;
|
|
278
341
|
}
|
|
279
|
-
export interface QQConfig {
|
|
280
|
-
wsUrl: string;
|
|
281
|
-
accessToken?: string;
|
|
282
|
-
enabled: boolean;
|
|
283
|
-
markdownFormat: boolean;
|
|
284
|
-
messageDirect: QQAllowConfig;
|
|
285
|
-
messageGroup: QQGroupConfig;
|
|
286
|
-
messageGroupsCustom: Record<string, QQGroupConfig>;
|
|
287
|
-
}
|
|
288
342
|
export interface QQGroupConfig extends QQAllowConfig {
|
|
289
343
|
requireMention: boolean;
|
|
290
344
|
requirePoke: boolean;
|
|
@@ -296,15 +350,6 @@ export interface QQAllowConfig {
|
|
|
296
350
|
allowFrom: string[];
|
|
297
351
|
denyFrom: string[];
|
|
298
352
|
}
|
|
299
|
-
export type QQProbe = {
|
|
300
|
-
ok: boolean;
|
|
301
|
-
status?: number | null;
|
|
302
|
-
error?: string | null;
|
|
303
|
-
};
|
|
304
|
-
export type QQSession = {
|
|
305
|
-
abortController?: AbortController;
|
|
306
|
-
aborted?: boolean;
|
|
307
|
-
};
|
|
308
353
|
export type QQLoginInfo = {
|
|
309
354
|
userId: string;
|
|
310
355
|
nickname: string;
|
package/dist/src/types/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NapCat WebSocket API Types
|
|
3
|
-
* Based on NapCat OneBot 11 implementation
|
|
4
|
-
*/
|
|
5
1
|
export {};
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Event Handler Types
|
|
4
|
+
// =============================================================================
|
|
@@ -21,13 +21,4 @@ export declare class CQCodeUtils {
|
|
|
21
21
|
* @param text 原始消息字符串
|
|
22
22
|
*/
|
|
23
23
|
static parse(text: string): CQNode[];
|
|
24
|
-
/**
|
|
25
|
-
* 辅助方法:从解析结果中提取所有纯文本内容(去除 CQ 码)
|
|
26
|
-
* 场景:用于生成通知摘要、日志记录等
|
|
27
|
-
*/
|
|
28
|
-
static getTextOnly(nodes: CQNode[]): string;
|
|
29
|
-
/**
|
|
30
|
-
* 辅助方法:判断消息是否提及了某个 QQ
|
|
31
|
-
*/
|
|
32
|
-
static isMentioned(nodes: CQNode[], qq: string | number): boolean;
|
|
33
24
|
}
|
package/dist/src/utils/cqcode.js
CHANGED
|
@@ -82,21 +82,4 @@ export class CQCodeUtils {
|
|
|
82
82
|
}
|
|
83
83
|
return nodes;
|
|
84
84
|
}
|
|
85
|
-
/**
|
|
86
|
-
* 辅助方法:从解析结果中提取所有纯文本内容(去除 CQ 码)
|
|
87
|
-
* 场景:用于生成通知摘要、日志记录等
|
|
88
|
-
*/
|
|
89
|
-
static getTextOnly(nodes) {
|
|
90
|
-
return nodes
|
|
91
|
-
.filter(node => node.type === 'text')
|
|
92
|
-
.map(node => node.data.text)
|
|
93
|
-
.join('');
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* 辅助方法:判断消息是否提及了某个 QQ
|
|
97
|
-
*/
|
|
98
|
-
static isMentioned(nodes, qq) {
|
|
99
|
-
const targetQQ = String(qq);
|
|
100
|
-
return nodes.some(node => node.type === 'at' && (node.data.qq === targetQQ || node.data.qq === 'all'));
|
|
101
|
-
}
|
|
102
85
|
}
|
|
@@ -12,17 +12,8 @@ export declare function generateMessageId(): string;
|
|
|
12
12
|
* Uses UUID for thread-safe and collision-free ID generation
|
|
13
13
|
*/
|
|
14
14
|
export declare function generateEchoId(): string;
|
|
15
|
-
/**
|
|
16
|
-
* Convert NapCat integer message ID to string
|
|
17
|
-
*/
|
|
18
|
-
export declare function messageIdToString(messageId: number | string): string;
|
|
19
|
-
/**
|
|
20
|
-
* Map common QQ face IDs to emoji
|
|
21
|
-
*/
|
|
22
|
-
export declare const FACE_ID_TO_EMOJI: Record<string, string>;
|
|
23
15
|
/**
|
|
24
16
|
* Get emoji for QQ face ID
|
|
25
|
-
* For unknown IDs, shows the ID for reference
|
|
26
17
|
*/
|
|
27
18
|
export declare function getEmojiForFaceId(faceId: string): string;
|
|
28
19
|
/**
|
|
@@ -37,14 +28,6 @@ export declare function extractImageUrl(data: {
|
|
|
37
28
|
url?: string;
|
|
38
29
|
file?: string;
|
|
39
30
|
}): string | undefined;
|
|
40
|
-
/**
|
|
41
|
-
* Calculate exponential backoff delay
|
|
42
|
-
*/
|
|
43
|
-
export declare function calculateBackoff(attempt: number, baseMs?: number, maxMs?: number): number;
|
|
44
|
-
/**
|
|
45
|
-
* Chunk an array into smaller arrays
|
|
46
|
-
*/
|
|
47
|
-
export declare function chunk<T>(array: T[], size: number): T[][];
|
|
48
31
|
/**
|
|
49
32
|
* Get a human-readable message for a WebSocket close code
|
|
50
33
|
*/
|
package/dist/src/utils/index.js
CHANGED
|
@@ -20,145 +20,31 @@ export function generateEchoId() {
|
|
|
20
20
|
return `echo-${randomUUID()}`;
|
|
21
21
|
}
|
|
22
22
|
// =============================================================================
|
|
23
|
-
// Message ID Conversion
|
|
24
|
-
// =============================================================================
|
|
25
|
-
/**
|
|
26
|
-
* Convert NapCat integer message ID to string
|
|
27
|
-
*/
|
|
28
|
-
export function messageIdToString(messageId) {
|
|
29
|
-
return String(messageId);
|
|
30
|
-
}
|
|
31
|
-
// =============================================================================
|
|
32
23
|
// Face/Emoji Mapping
|
|
33
24
|
// =============================================================================
|
|
34
25
|
/**
|
|
35
|
-
*
|
|
26
|
+
* QQ 表情 ID 到 emoji 的映射数组(下标即 ID)
|
|
36
27
|
*/
|
|
37
|
-
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
|
|
51
|
-
'13': '😗',
|
|
52
|
-
'14': '😙',
|
|
53
|
-
'15': '😚',
|
|
54
|
-
'16': '🥲',
|
|
55
|
-
'17': '🙂',
|
|
56
|
-
'18': '🙃',
|
|
57
|
-
'19': '😉',
|
|
58
|
-
'20': '😌',
|
|
59
|
-
'21': '😍',
|
|
60
|
-
'22': '🥰',
|
|
61
|
-
'23': '😘',
|
|
62
|
-
'24': '😗',
|
|
63
|
-
'25': '😙',
|
|
64
|
-
'26': '😚',
|
|
65
|
-
'27': '😋',
|
|
66
|
-
'28': '😛',
|
|
67
|
-
'29': '😝',
|
|
68
|
-
'30': '😜',
|
|
69
|
-
'31': '🤪',
|
|
70
|
-
'32': '🤨',
|
|
71
|
-
'33': '🧐',
|
|
72
|
-
'34': '🤓',
|
|
73
|
-
'35': '😎',
|
|
74
|
-
'36': '🤩',
|
|
75
|
-
'37': '🥳',
|
|
76
|
-
'38': '😏',
|
|
77
|
-
'39': '😒',
|
|
78
|
-
'40': '😞',
|
|
79
|
-
'41': '😔',
|
|
80
|
-
'42': '😟',
|
|
81
|
-
'43': '😕',
|
|
82
|
-
'44': '🙁',
|
|
83
|
-
'45': '😣',
|
|
84
|
-
'46': '😖',
|
|
85
|
-
'47': '😫',
|
|
86
|
-
'48': '😩',
|
|
87
|
-
'49': '🥺',
|
|
88
|
-
'50': '😢',
|
|
89
|
-
'51': '😭',
|
|
90
|
-
'52': '😤',
|
|
91
|
-
'53': '😠',
|
|
92
|
-
'54': '😡',
|
|
93
|
-
'55': '🤬',
|
|
94
|
-
'56': '🤯',
|
|
95
|
-
'57': '😳',
|
|
96
|
-
'58': '🥵',
|
|
97
|
-
'59': '🥶',
|
|
98
|
-
'60': '😱',
|
|
99
|
-
'61': '😨',
|
|
100
|
-
'62': '😰',
|
|
101
|
-
'63': '😥',
|
|
102
|
-
'64': '😓',
|
|
103
|
-
'65': '🤗',
|
|
104
|
-
'66': '🤔',
|
|
105
|
-
'67': '🤭',
|
|
106
|
-
'68': '🤫',
|
|
107
|
-
'69': '🤥',
|
|
108
|
-
'70': '😶',
|
|
109
|
-
'71': '😐',
|
|
110
|
-
'72': '😑',
|
|
111
|
-
'73': '😬',
|
|
112
|
-
'74': '🙄',
|
|
113
|
-
'75': '😯',
|
|
114
|
-
'76': '😦',
|
|
115
|
-
'77': '😧',
|
|
116
|
-
'78': '😮',
|
|
117
|
-
'79': '😲',
|
|
118
|
-
'80': '🥱',
|
|
119
|
-
'81': '😴',
|
|
120
|
-
'82': '🤤',
|
|
121
|
-
'83': '😪',
|
|
122
|
-
'84': '😵',
|
|
123
|
-
'85': '🤐',
|
|
124
|
-
'86': '🥴',
|
|
125
|
-
'87': '🤢',
|
|
126
|
-
'88': '🤮',
|
|
127
|
-
'89': '🤧',
|
|
128
|
-
'90': '😷',
|
|
129
|
-
'91': '🤒',
|
|
130
|
-
'92': '🤕',
|
|
131
|
-
'93': '🤑',
|
|
132
|
-
'94': '🤠',
|
|
133
|
-
'95': '😈',
|
|
134
|
-
'96': '👿',
|
|
135
|
-
'97': '👹',
|
|
136
|
-
'98': '👺',
|
|
137
|
-
'99': '🤡',
|
|
138
|
-
'100': '💩',
|
|
139
|
-
'101': '👻',
|
|
140
|
-
'102': '💀',
|
|
141
|
-
'103': '☠️',
|
|
142
|
-
'104': '👽',
|
|
143
|
-
'105': '👾',
|
|
144
|
-
'106': '🤖',
|
|
145
|
-
'107': '🎃',
|
|
146
|
-
'108': '😺',
|
|
147
|
-
'109': '😸',
|
|
148
|
-
'110': '😹',
|
|
149
|
-
'111': '😻',
|
|
150
|
-
'112': '😼',
|
|
151
|
-
'113': '😽',
|
|
152
|
-
'114': '🙀',
|
|
153
|
-
'115': '😿',
|
|
154
|
-
'116': '😾',
|
|
155
|
-
};
|
|
28
|
+
const FACE_EMOJI_ARRAY = [
|
|
29
|
+
'😊', '😅', '☺️', '😄', '😁', '😆', '😃', '😂', '🤣', '😊', // 0-9
|
|
30
|
+
'😍', '🥰', '😘', '😗', '😙', '😚', '🥲', '🙂', '🙃', '😉', // 10-19
|
|
31
|
+
'😌', '😍', '🥰', '😘', '😗', '😙', '😚', '😋', '😛', '😝', // 20-29
|
|
32
|
+
'😜', '🤪', '🤨', '🧐', '🤓', '😎', '🤩', '🥳', '😏', '😒', // 30-39
|
|
33
|
+
'😞', '😔', '😟', '😕', '🙁', '😣', '😖', '😫', '😩', '🥺', // 40-49
|
|
34
|
+
'😢', '😭', '😤', '😠', '😡', '🤬', '🤯', '😳', '🥵', '🥶', // 50-59
|
|
35
|
+
'😱', '😨', '😰', '😥', '😓', '🤗', '🤔', '🤭', '🤫', '🤥', // 60-69
|
|
36
|
+
'😶', '😐', '😑', '😬', '🙄', '😯', '😦', '😧', '😮', '😲', // 70-79
|
|
37
|
+
'🥱', '😴', '🤤', '😪', '😵', '🤐', '🥴', '🤢', '🤮', '🤧', // 80-89
|
|
38
|
+
'😷', '🤒', '🤕', '🤑', '🤠', '😈', '👿', '👹', '👺', '🤡', // 90-99
|
|
39
|
+
'💩', '👻', '💀', '☠️', '👽', '👾', '🤖', '🎃', '😺', '😸', // 100-109
|
|
40
|
+
'😹', '😻', '😼', '😽', '🙀', '😿', '😾', // 110-116
|
|
41
|
+
];
|
|
156
42
|
/**
|
|
157
43
|
* Get emoji for QQ face ID
|
|
158
|
-
* For unknown IDs, shows the ID for reference
|
|
159
44
|
*/
|
|
160
45
|
export function getEmojiForFaceId(faceId) {
|
|
161
|
-
|
|
46
|
+
const id = parseInt(faceId, 10);
|
|
47
|
+
return FACE_EMOJI_ARRAY[id] ?? `[表情:${faceId}]`;
|
|
162
48
|
}
|
|
163
49
|
// =============================================================================
|
|
164
50
|
// URL Validation
|
|
@@ -189,29 +75,6 @@ export function extractImageUrl(data) {
|
|
|
189
75
|
return undefined;
|
|
190
76
|
}
|
|
191
77
|
// =============================================================================
|
|
192
|
-
// Delay Helpers
|
|
193
|
-
// =============================================================================
|
|
194
|
-
/**
|
|
195
|
-
* Calculate exponential backoff delay
|
|
196
|
-
*/
|
|
197
|
-
export function calculateBackoff(attempt, baseMs = 1000, maxMs = 30000) {
|
|
198
|
-
const delay = baseMs * Math.pow(2, attempt);
|
|
199
|
-
return Math.min(delay, maxMs);
|
|
200
|
-
}
|
|
201
|
-
// =============================================================================
|
|
202
|
-
// Array Helpers
|
|
203
|
-
// =============================================================================
|
|
204
|
-
/**
|
|
205
|
-
* Chunk an array into smaller arrays
|
|
206
|
-
*/
|
|
207
|
-
export function chunk(array, size) {
|
|
208
|
-
const chunks = [];
|
|
209
|
-
for (let i = 0; i < array.length; i += size) {
|
|
210
|
-
chunks.push(array.slice(i, i + size));
|
|
211
|
-
}
|
|
212
|
-
return chunks;
|
|
213
|
-
}
|
|
214
|
-
// =============================================================================
|
|
215
78
|
// WebSocket Helpers
|
|
216
79
|
// =============================================================================
|
|
217
80
|
/**
|
package/dist/src/utils/log.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DEBUG_MODE } from "../core/config";
|
|
2
|
+
import { getQQRuntime } from "../runtime.js";
|
|
2
3
|
function log() {
|
|
3
|
-
return
|
|
4
|
+
return getQQRuntime()?.logging.getChildLogger({ module: 'channel/qq' }) ?? console;
|
|
4
5
|
}
|
|
5
6
|
function param(args) {
|
|
6
7
|
if (args.length === 0)
|
|
@@ -9,7 +10,9 @@ function param(args) {
|
|
|
9
10
|
}
|
|
10
11
|
export class Logger {
|
|
11
12
|
static debug(category, message, ...args) {
|
|
12
|
-
|
|
13
|
+
if (DEBUG_MODE) {
|
|
14
|
+
log().debug?.(`[${category}] ${message}${param(args)}`);
|
|
15
|
+
}
|
|
13
16
|
}
|
|
14
17
|
static info(category, message, ...args) {
|
|
15
18
|
log().info?.(`[${category}] ${message}${param(args)}`);
|
|
@@ -58,11 +58,8 @@ export class MarkdownToText {
|
|
|
58
58
|
text = text.replace(/^(\s*)-\s\[x]\s/gim, '$1✅ '); // 完成的任务
|
|
59
59
|
text = text.replace(/^(\s*)-\s\[\s]\s/gim, '$1⬜ '); // 未完成的任务
|
|
60
60
|
text = text.replace(/^(\s*)[-*+]\s+(.*)$/gm, '$1• $2'); // 列表项变圆点
|
|
61
|
-
// 3.7 表格 (Tables) ->
|
|
62
|
-
text =
|
|
63
|
-
text = text.replace(/^\|(.*)\|$/gm, (_match, content) => {
|
|
64
|
-
return content.split('|').map((s) => s.trim()).join(' ');
|
|
65
|
-
});
|
|
61
|
+
// 3.7 表格 (Tables) -> 结构化文本
|
|
62
|
+
text = this.convertTables(text);
|
|
66
63
|
// ============================================================
|
|
67
64
|
// 阶段 4: 行内格式 (Inline Formatting)
|
|
68
65
|
// ============================================================
|
|
@@ -84,6 +81,61 @@ export class MarkdownToText {
|
|
|
84
81
|
text = text.replace(/\n{3,}/g, '\n\n').trim();
|
|
85
82
|
return text;
|
|
86
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* 转换 Markdown 表格为可读文本
|
|
86
|
+
* 支持对齐和多行表格
|
|
87
|
+
*/
|
|
88
|
+
convertTables(text) {
|
|
89
|
+
// 匹配整个表格块(连续的表格行)
|
|
90
|
+
const tableRegex = /^(\|.+\|[\n\r]?)+/gm;
|
|
91
|
+
return text.replace(tableRegex, (tableBlock) => {
|
|
92
|
+
const lines = tableBlock.trim().split(/\r?\n/);
|
|
93
|
+
if (lines.length < 2)
|
|
94
|
+
return tableBlock; // 至少需要表头和分隔行
|
|
95
|
+
// 解析所有行
|
|
96
|
+
const rows = [];
|
|
97
|
+
let separatorIndex = -1;
|
|
98
|
+
for (let i = 0; i < lines.length; i++) {
|
|
99
|
+
const line = lines[i].trim();
|
|
100
|
+
if (!line.startsWith('|') || !line.endsWith('|'))
|
|
101
|
+
continue;
|
|
102
|
+
const cells = line.slice(1, -1).split('|').map((c) => c.trim());
|
|
103
|
+
// 检测分隔行 (|---|---|)
|
|
104
|
+
if (cells.every((c) => /^[-:]+$/.test(c))) {
|
|
105
|
+
separatorIndex = i;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
rows.push(cells);
|
|
109
|
+
}
|
|
110
|
+
if (rows.length === 0)
|
|
111
|
+
return tableBlock;
|
|
112
|
+
// 计算每列最大宽度
|
|
113
|
+
const colCount = Math.max(...rows.map((r) => r.length));
|
|
114
|
+
const colWidths = [];
|
|
115
|
+
for (let col = 0; col < colCount; col++) {
|
|
116
|
+
colWidths[col] = Math.max(...rows.map((r) => (r[col] || '').length), 3 // 最小宽度
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
// 构建格式化输出
|
|
120
|
+
const result = [];
|
|
121
|
+
const separator = '│';
|
|
122
|
+
const horizontalLine = '─'.repeat(colWidths.reduce((a, b) => a + b, 0) + colCount - 1);
|
|
123
|
+
rows.forEach((row, rowIndex) => {
|
|
124
|
+
// 填充单元格并对齐
|
|
125
|
+
const formattedCells = row.map((cell, colIndex) => {
|
|
126
|
+
const width = colWidths[colIndex] || 3;
|
|
127
|
+
return (cell || '').padEnd(width);
|
|
128
|
+
});
|
|
129
|
+
// 添加行内容
|
|
130
|
+
result.push(formattedCells.join(separator));
|
|
131
|
+
// 在表头后添加分隔线
|
|
132
|
+
if (separatorIndex !== -1 && rowIndex === 0) {
|
|
133
|
+
result.push(horizontalLine);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return '\n' + result.join('\n') + '\n';
|
|
137
|
+
});
|
|
138
|
+
}
|
|
87
139
|
/**
|
|
88
140
|
* 保护代码块 (```)
|
|
89
141
|
* 生成 Key 格式:%%MD-MASK-BLOCK-0
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "qq",
|
|
3
|
-
"
|
|
3
|
+
"kind": "channel",
|
|
4
4
|
"channels": ["qq"],
|
|
5
|
-
"
|
|
5
|
+
"name": "QQ",
|
|
6
|
+
"description": "QQ Chat channel plugin",
|
|
6
7
|
"configSchema": {
|
|
7
8
|
"type": "object",
|
|
8
9
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@izhimu/qq",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "A QQ channel plugin for OpenClaw using NapCat WebSocket",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,17 +31,16 @@
|
|
|
31
31
|
"author": "izhimu",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"openclaw": {
|
|
34
|
-
"extensions": [
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"defaultChoice": "npm"
|
|
34
|
+
"extensions": ["./dist/index.js"],
|
|
35
|
+
"setupEntry": "",
|
|
36
|
+
"channel": {
|
|
37
|
+
"id": "qq",
|
|
38
|
+
"label": "QQ",
|
|
39
|
+
"blurb": "Connect OpenClaw to QQ Chat"
|
|
41
40
|
}
|
|
42
41
|
},
|
|
43
42
|
"peerDependencies": {
|
|
44
|
-
"openclaw": "^2026.3.
|
|
43
|
+
"openclaw": "^2026.3.22"
|
|
45
44
|
},
|
|
46
45
|
"peerDependenciesMeta": {
|
|
47
46
|
"openclaw": {
|
|
@@ -49,14 +48,13 @@
|
|
|
49
48
|
}
|
|
50
49
|
},
|
|
51
50
|
"dependencies": {
|
|
52
|
-
"p-limit": "^7.3.0",
|
|
53
51
|
"ws": "^8.19.0",
|
|
54
52
|
"zod": "^4.3.6"
|
|
55
53
|
},
|
|
56
54
|
"devDependencies": {
|
|
57
55
|
"@types/node": "^22.0.0",
|
|
58
56
|
"@types/ws": "^8.18.0",
|
|
59
|
-
"openclaw": "^2026.3.
|
|
57
|
+
"openclaw": "^2026.3.22",
|
|
60
58
|
"typescript": "^5.0.0"
|
|
61
59
|
}
|
|
62
60
|
}
|
package/dist/src/onboarding.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QQ NapCat CLI Onboarding Adapter
|
|
3
|
-
*
|
|
4
|
-
* 提供 openclaw onboard 命令的交互式配置支持
|
|
5
|
-
*/
|
|
6
|
-
import type { ChannelOnboardingAdapter } from "openclaw/plugin-sdk";
|
|
7
|
-
/**
|
|
8
|
-
* QQ NapCat Onboarding Adapter
|
|
9
|
-
*/
|
|
10
|
-
export declare const qqOnboardingAdapter: ChannelOnboardingAdapter;
|
package/dist/src/onboarding.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { CHANNEL_ID, resolveQQAccount } from "./core/config.js";
|
|
2
|
-
/**
|
|
3
|
-
* QQ NapCat Onboarding Adapter
|
|
4
|
-
*/
|
|
5
|
-
export const qqOnboardingAdapter = {
|
|
6
|
-
channel: CHANNEL_ID,
|
|
7
|
-
getStatus: async (ctx) => {
|
|
8
|
-
const { cfg } = ctx;
|
|
9
|
-
const config = cfg.channels?.[CHANNEL_ID];
|
|
10
|
-
const configured = Boolean(config.wsUrl);
|
|
11
|
-
return {
|
|
12
|
-
channel: CHANNEL_ID,
|
|
13
|
-
configured,
|
|
14
|
-
statusLines: configured
|
|
15
|
-
? ["QQ (NapCat): 已配置"]
|
|
16
|
-
: ["QQ (NapCat): 未配置"],
|
|
17
|
-
selectionHint: configured ? "已配置" : "未配置",
|
|
18
|
-
quickstartScore: configured ? 1 : 10,
|
|
19
|
-
};
|
|
20
|
-
},
|
|
21
|
-
configure: async (ctx) => {
|
|
22
|
-
const { cfg, prompter } = ctx;
|
|
23
|
-
let next = cfg;
|
|
24
|
-
const resolvedAccount = resolveQQAccount({ cfg });
|
|
25
|
-
const accountConfigured = Boolean(resolvedAccount.wsUrl);
|
|
26
|
-
// 显示帮助
|
|
27
|
-
if (!accountConfigured) {
|
|
28
|
-
await prompter.note([
|
|
29
|
-
"1) 确保已安装 NapCat: https://github.com/NapNeko/NapCatQQ",
|
|
30
|
-
"2) 在 NapCat 配置中启用 WebSocket (正向 WS)",
|
|
31
|
-
"3) 默认地址: ws://localhost:3001",
|
|
32
|
-
"4) 如需访问控制,可设置 accessToken",
|
|
33
|
-
"",
|
|
34
|
-
"NapCat 文档: https://napneko.github.io/",
|
|
35
|
-
].join("\n"), "QQ NapCat 配置");
|
|
36
|
-
}
|
|
37
|
-
let wsUrl = null;
|
|
38
|
-
let accessToken = null;
|
|
39
|
-
// 检查是否已配置
|
|
40
|
-
if (accountConfigured) {
|
|
41
|
-
const keep = await prompter.confirm({
|
|
42
|
-
message: "QQ NapCat 已配置,是否保留当前配置?",
|
|
43
|
-
initialValue: true,
|
|
44
|
-
});
|
|
45
|
-
if (!keep) {
|
|
46
|
-
wsUrl = String(await prompter.text({
|
|
47
|
-
message: "请输入 NapCat WebSocket URL",
|
|
48
|
-
placeholder: "ws://localhost:3001",
|
|
49
|
-
initialValue: resolvedAccount.wsUrl,
|
|
50
|
-
validate: (value) => (value?.trim() ? undefined : "WebSocket URL 不能为空"),
|
|
51
|
-
})).trim();
|
|
52
|
-
accessToken = String(await prompter.text({
|
|
53
|
-
message: "请输入 Access Token (可选,直接回车跳过)",
|
|
54
|
-
placeholder: "留空表示不使用 token",
|
|
55
|
-
initialValue: resolvedAccount.accessToken || undefined,
|
|
56
|
-
})).trim();
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
// 新配置
|
|
61
|
-
wsUrl = String(await prompter.text({
|
|
62
|
-
message: "请输入 NapCat WebSocket URL",
|
|
63
|
-
placeholder: "ws://localhost:3001",
|
|
64
|
-
validate: (value) => (value?.trim() ? undefined : "WebSocket URL 不能为空"),
|
|
65
|
-
})).trim();
|
|
66
|
-
accessToken = String(await prompter.text({
|
|
67
|
-
message: "请输入 Access Token (可选,直接回车跳过)",
|
|
68
|
-
placeholder: "留空表示不使用 token",
|
|
69
|
-
})).trim();
|
|
70
|
-
}
|
|
71
|
-
// 应用配置
|
|
72
|
-
if (wsUrl) {
|
|
73
|
-
next = {
|
|
74
|
-
...next,
|
|
75
|
-
channels: {
|
|
76
|
-
...next.channels,
|
|
77
|
-
qq: {
|
|
78
|
-
...next.channels?.[CHANNEL_ID],
|
|
79
|
-
enabled: true,
|
|
80
|
-
wsUrl,
|
|
81
|
-
...(accessToken ? { accessToken } : {}),
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
return { cfg: next };
|
|
87
|
-
},
|
|
88
|
-
disable: (cfg) => ({
|
|
89
|
-
...cfg,
|
|
90
|
-
channels: {
|
|
91
|
-
...cfg.channels,
|
|
92
|
-
qq: {
|
|
93
|
-
...cfg.channels?.[CHANNEL_ID],
|
|
94
|
-
enabled: false,
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
}),
|
|
98
|
-
};
|