@izhimu/qq 0.1.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 +21 -0
- package/README.md +388 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +19 -0
- package/dist/src/adapters/message.d.ts +8 -0
- package/dist/src/adapters/message.js +152 -0
- package/dist/src/channel.d.ts +7 -0
- package/dist/src/channel.js +312 -0
- package/dist/src/core/config.d.ts +22 -0
- package/dist/src/core/config.js +32 -0
- package/dist/src/core/connection.d.ts +65 -0
- package/dist/src/core/connection.js +328 -0
- package/dist/src/core/dispatch.d.ts +54 -0
- package/dist/src/core/dispatch.js +285 -0
- package/dist/src/core/request.d.ts +26 -0
- package/dist/src/core/request.js +115 -0
- package/dist/src/core/runtime.d.ts +16 -0
- package/dist/src/core/runtime.js +48 -0
- package/dist/src/onboarding.d.ts +10 -0
- package/dist/src/onboarding.js +98 -0
- package/dist/src/types/index.d.ts +261 -0
- package/dist/src/types/index.js +5 -0
- package/dist/src/utils/cqcode.d.ts +33 -0
- package/dist/src/utils/cqcode.js +102 -0
- package/dist/src/utils/index.d.ts +51 -0
- package/dist/src/utils/index.js +250 -0
- package/dist/src/utils/log.d.ts +6 -0
- package/dist/src/utils/log.js +23 -0
- package/dist/src/utils/markdown.d.ts +29 -0
- package/dist/src/utils/markdown.js +137 -0
- package/openclaw.plugin.json +11 -0
- package/package.json +61 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for QQ NapCat plugin
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generate a unique message ID for OpenClaw
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateMessageId(): string;
|
|
8
|
+
/**
|
|
9
|
+
* Generate a unique echo ID for request correlation
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateEchoId(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Convert NapCat integer message ID to string
|
|
14
|
+
*/
|
|
15
|
+
export declare function messageIdToString(messageId: number | string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Map common QQ face IDs to emoji
|
|
18
|
+
*/
|
|
19
|
+
export declare const FACE_ID_TO_EMOJI: Record<string, string>;
|
|
20
|
+
/**
|
|
21
|
+
* Get emoji for QQ face ID
|
|
22
|
+
* For unknown IDs, shows the ID for reference
|
|
23
|
+
*/
|
|
24
|
+
export declare function getEmojiForFaceId(faceId: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Check if a string is a valid URL
|
|
27
|
+
*/
|
|
28
|
+
export declare function isValidUrl(str: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Extract URL from image data
|
|
31
|
+
* Returns the URL if valid, otherwise returns undefined
|
|
32
|
+
*/
|
|
33
|
+
export declare function extractImageUrl(data: {
|
|
34
|
+
url?: string;
|
|
35
|
+
file?: string;
|
|
36
|
+
}): string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Calculate exponential backoff delay
|
|
39
|
+
*/
|
|
40
|
+
export declare function calculateBackoff(attempt: number, baseMs?: number, maxMs?: number): number;
|
|
41
|
+
/**
|
|
42
|
+
* Chunk an array into smaller arrays
|
|
43
|
+
*/
|
|
44
|
+
export declare function chunk<T>(array: T[], size: number): T[][];
|
|
45
|
+
/**
|
|
46
|
+
* Get a human-readable message for a WebSocket close code
|
|
47
|
+
*/
|
|
48
|
+
export declare function getCloseCodeMessage(code: number): string;
|
|
49
|
+
export { CQCodeUtils, CQNode } from './cqcode.js';
|
|
50
|
+
export { Logger } from './log.js';
|
|
51
|
+
export { MarkdownToText, markdownToText, } from './markdown.js';
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for QQ NapCat plugin
|
|
3
|
+
*/
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// ID Generation
|
|
6
|
+
// =============================================================================
|
|
7
|
+
let idCounter = 0;
|
|
8
|
+
/**
|
|
9
|
+
* Generate a unique message ID for OpenClaw
|
|
10
|
+
*/
|
|
11
|
+
export function generateMessageId() {
|
|
12
|
+
return `qq-${Date.now()}-${++idCounter}`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Generate a unique echo ID for request correlation
|
|
16
|
+
*/
|
|
17
|
+
export function generateEchoId() {
|
|
18
|
+
return `echo-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
19
|
+
}
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// Message ID Conversion
|
|
22
|
+
// =============================================================================
|
|
23
|
+
/**
|
|
24
|
+
* Convert NapCat integer message ID to string
|
|
25
|
+
*/
|
|
26
|
+
export function messageIdToString(messageId) {
|
|
27
|
+
return String(messageId);
|
|
28
|
+
}
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Face/Emoji Mapping
|
|
31
|
+
// =============================================================================
|
|
32
|
+
/**
|
|
33
|
+
* Map common QQ face IDs to emoji
|
|
34
|
+
*/
|
|
35
|
+
export const FACE_ID_TO_EMOJI = {
|
|
36
|
+
'0': 'š',
|
|
37
|
+
'1': 'š
',
|
|
38
|
+
'2': 'āŗļø',
|
|
39
|
+
'3': 'š',
|
|
40
|
+
'4': 'š',
|
|
41
|
+
'5': 'š',
|
|
42
|
+
'6': 'š',
|
|
43
|
+
'7': 'š',
|
|
44
|
+
'8': 'š¤£',
|
|
45
|
+
'9': 'š',
|
|
46
|
+
'10': 'š',
|
|
47
|
+
'11': 'š„°',
|
|
48
|
+
'12': 'š',
|
|
49
|
+
'13': 'š',
|
|
50
|
+
'14': 'š',
|
|
51
|
+
'15': 'š',
|
|
52
|
+
'16': 'š„²',
|
|
53
|
+
'17': 'š',
|
|
54
|
+
'18': 'š',
|
|
55
|
+
'19': 'š',
|
|
56
|
+
'20': 'š',
|
|
57
|
+
'21': 'š',
|
|
58
|
+
'22': 'š„°',
|
|
59
|
+
'23': 'š',
|
|
60
|
+
'24': 'š',
|
|
61
|
+
'25': 'š',
|
|
62
|
+
'26': 'š',
|
|
63
|
+
'27': 'š',
|
|
64
|
+
'28': 'š',
|
|
65
|
+
'29': 'š',
|
|
66
|
+
'30': 'š',
|
|
67
|
+
'31': 'š¤Ŗ',
|
|
68
|
+
'32': 'š¤Ø',
|
|
69
|
+
'33': 'š§',
|
|
70
|
+
'34': 'š¤',
|
|
71
|
+
'35': 'š',
|
|
72
|
+
'36': 'š¤©',
|
|
73
|
+
'37': 'š„³',
|
|
74
|
+
'38': 'š',
|
|
75
|
+
'39': 'š',
|
|
76
|
+
'40': 'š',
|
|
77
|
+
'41': 'š',
|
|
78
|
+
'42': 'š',
|
|
79
|
+
'43': 'š',
|
|
80
|
+
'44': 'š',
|
|
81
|
+
'45': 'š£',
|
|
82
|
+
'46': 'š',
|
|
83
|
+
'47': 'š«',
|
|
84
|
+
'48': 'š©',
|
|
85
|
+
'49': 'š„ŗ',
|
|
86
|
+
'50': 'š¢',
|
|
87
|
+
'51': 'š',
|
|
88
|
+
'52': 'š¤',
|
|
89
|
+
'53': 'š ',
|
|
90
|
+
'54': 'š”',
|
|
91
|
+
'55': 'š¤¬',
|
|
92
|
+
'56': 'š¤Æ',
|
|
93
|
+
'57': 'š³',
|
|
94
|
+
'58': 'š„µ',
|
|
95
|
+
'59': 'š„¶',
|
|
96
|
+
'60': 'š±',
|
|
97
|
+
'61': 'šØ',
|
|
98
|
+
'62': 'š°',
|
|
99
|
+
'63': 'š„',
|
|
100
|
+
'64': 'š',
|
|
101
|
+
'65': 'š¤',
|
|
102
|
+
'66': 'š¤',
|
|
103
|
+
'67': 'š¤',
|
|
104
|
+
'68': 'š¤«',
|
|
105
|
+
'69': 'š¤„',
|
|
106
|
+
'70': 'š¶',
|
|
107
|
+
'71': 'š',
|
|
108
|
+
'72': 'š',
|
|
109
|
+
'73': 'š¬',
|
|
110
|
+
'74': 'š',
|
|
111
|
+
'75': 'šÆ',
|
|
112
|
+
'76': 'š¦',
|
|
113
|
+
'77': 'š§',
|
|
114
|
+
'78': 'š®',
|
|
115
|
+
'79': 'š²',
|
|
116
|
+
'80': 'š„±',
|
|
117
|
+
'81': 'š“',
|
|
118
|
+
'82': 'š¤¤',
|
|
119
|
+
'83': 'šŖ',
|
|
120
|
+
'84': 'šµ',
|
|
121
|
+
'85': 'š¤',
|
|
122
|
+
'86': 'š„“',
|
|
123
|
+
'87': 'š¤¢',
|
|
124
|
+
'88': 'š¤®',
|
|
125
|
+
'89': 'š¤§',
|
|
126
|
+
'90': 'š·',
|
|
127
|
+
'91': 'š¤',
|
|
128
|
+
'92': 'š¤',
|
|
129
|
+
'93': 'š¤',
|
|
130
|
+
'94': 'š¤ ',
|
|
131
|
+
'95': 'š',
|
|
132
|
+
'96': 'šæ',
|
|
133
|
+
'97': 'š¹',
|
|
134
|
+
'98': 'šŗ',
|
|
135
|
+
'99': 'š¤”',
|
|
136
|
+
'100': 'š©',
|
|
137
|
+
'101': 'š»',
|
|
138
|
+
'102': 'š',
|
|
139
|
+
'103': 'ā ļø',
|
|
140
|
+
'104': 'š½',
|
|
141
|
+
'105': 'š¾',
|
|
142
|
+
'106': 'š¤',
|
|
143
|
+
'107': 'š',
|
|
144
|
+
'108': 'šŗ',
|
|
145
|
+
'109': 'šø',
|
|
146
|
+
'110': 'š¹',
|
|
147
|
+
'111': 'š»',
|
|
148
|
+
'112': 'š¼',
|
|
149
|
+
'113': 'š½',
|
|
150
|
+
'114': 'š',
|
|
151
|
+
'115': 'šæ',
|
|
152
|
+
'116': 'š¾',
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Get emoji for QQ face ID
|
|
156
|
+
* For unknown IDs, shows the ID for reference
|
|
157
|
+
*/
|
|
158
|
+
export function getEmojiForFaceId(faceId) {
|
|
159
|
+
return FACE_ID_TO_EMOJI[faceId] || `[蔨ę
:${faceId}]`;
|
|
160
|
+
}
|
|
161
|
+
// =============================================================================
|
|
162
|
+
// URL Validation
|
|
163
|
+
// =============================================================================
|
|
164
|
+
/**
|
|
165
|
+
* Check if a string is a valid URL
|
|
166
|
+
*/
|
|
167
|
+
export function isValidUrl(str) {
|
|
168
|
+
try {
|
|
169
|
+
new URL(str);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Extract URL from image data
|
|
178
|
+
* Returns the URL if valid, otherwise returns undefined
|
|
179
|
+
*/
|
|
180
|
+
export function extractImageUrl(data) {
|
|
181
|
+
if (data.url && isValidUrl(data.url)) {
|
|
182
|
+
return data.url;
|
|
183
|
+
}
|
|
184
|
+
if (data.file && isValidUrl(data.file)) {
|
|
185
|
+
return data.file;
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
// =============================================================================
|
|
190
|
+
// Delay Helpers
|
|
191
|
+
// =============================================================================
|
|
192
|
+
/**
|
|
193
|
+
* Calculate exponential backoff delay
|
|
194
|
+
*/
|
|
195
|
+
export function calculateBackoff(attempt, baseMs = 1000, maxMs = 30000) {
|
|
196
|
+
const delay = baseMs * Math.pow(2, attempt);
|
|
197
|
+
return Math.min(delay, maxMs);
|
|
198
|
+
}
|
|
199
|
+
// =============================================================================
|
|
200
|
+
// Array Helpers
|
|
201
|
+
// =============================================================================
|
|
202
|
+
/**
|
|
203
|
+
* Chunk an array into smaller arrays
|
|
204
|
+
*/
|
|
205
|
+
export function chunk(array, size) {
|
|
206
|
+
const chunks = [];
|
|
207
|
+
for (let i = 0; i < array.length; i += size) {
|
|
208
|
+
chunks.push(array.slice(i, i + size));
|
|
209
|
+
}
|
|
210
|
+
return chunks;
|
|
211
|
+
}
|
|
212
|
+
// =============================================================================
|
|
213
|
+
// WebSocket Helpers
|
|
214
|
+
// =============================================================================
|
|
215
|
+
/**
|
|
216
|
+
* Get a human-readable message for a WebSocket close code
|
|
217
|
+
*/
|
|
218
|
+
export function getCloseCodeMessage(code) {
|
|
219
|
+
const messages = {
|
|
220
|
+
1000: 'Normal closure',
|
|
221
|
+
1001: 'Going away',
|
|
222
|
+
1002: 'Protocol error',
|
|
223
|
+
1003: 'Unsupported data',
|
|
224
|
+
1004: 'Reserved',
|
|
225
|
+
1005: 'No status received',
|
|
226
|
+
1006: 'Abnormal closure',
|
|
227
|
+
1007: 'Invalid frame payload data',
|
|
228
|
+
1008: 'Policy violation',
|
|
229
|
+
1009: 'Message too big',
|
|
230
|
+
1010: 'Missing extension',
|
|
231
|
+
1011: 'Internal error',
|
|
232
|
+
1012: 'Service restart',
|
|
233
|
+
1013: 'Try again later',
|
|
234
|
+
1014: 'Bad gateway',
|
|
235
|
+
1015: 'TLS handshake',
|
|
236
|
+
};
|
|
237
|
+
return messages[code] ?? `Unknown close code: ${code}`;
|
|
238
|
+
}
|
|
239
|
+
// =============================================================================
|
|
240
|
+
// CQ Code Utilities
|
|
241
|
+
// =============================================================================
|
|
242
|
+
export { CQCodeUtils } from './cqcode.js';
|
|
243
|
+
// =============================================================================
|
|
244
|
+
// Log Utilities
|
|
245
|
+
// =============================================================================
|
|
246
|
+
export { Logger } from './log.js';
|
|
247
|
+
// =============================================================================
|
|
248
|
+
// Markdown Utilities
|
|
249
|
+
// =============================================================================
|
|
250
|
+
export { MarkdownToText, markdownToText, } from './markdown.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare class Logger {
|
|
2
|
+
static debug(category: string, message: string, ...args: unknown[]): void;
|
|
3
|
+
static info(category: string, message: string, ...args: unknown[]): void;
|
|
4
|
+
static warn(category: string, message: string, ...args: unknown[]): void;
|
|
5
|
+
static error(category: string, message: string, ...args: unknown[]): void;
|
|
6
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { getRuntime } from "../core/runtime.js";
|
|
2
|
+
function log() {
|
|
3
|
+
return getRuntime()?.logging.getChildLogger({ module: 'channel/qq' }) ?? console;
|
|
4
|
+
}
|
|
5
|
+
function param(args) {
|
|
6
|
+
if (args.length === 0)
|
|
7
|
+
return '';
|
|
8
|
+
return ' ' + args.map(arg => typeof arg === 'string' ? arg : JSON.stringify(arg)).join(' ');
|
|
9
|
+
}
|
|
10
|
+
export class Logger {
|
|
11
|
+
static debug(category, message, ...args) {
|
|
12
|
+
log().debug?.(`[${category}] ${message}${param(args)}`);
|
|
13
|
+
}
|
|
14
|
+
static info(category, message, ...args) {
|
|
15
|
+
log().info?.(`[${category}] ${message}${param(args)}`);
|
|
16
|
+
}
|
|
17
|
+
static warn(category, message, ...args) {
|
|
18
|
+
log().warn?.(`[${category}] ${message}${param(args)}`);
|
|
19
|
+
}
|
|
20
|
+
static error(category, message, ...args) {
|
|
21
|
+
log().error?.(`[${category}] ${message}${param(args)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare class MarkdownToText {
|
|
2
|
+
private codeBlockStore;
|
|
3
|
+
private maskPrefix;
|
|
4
|
+
private maskCounter;
|
|
5
|
+
/**
|
|
6
|
+
* äø»å
„å£ļ¼å° Markdown 转ę¢äøŗēŗÆęę¬
|
|
7
|
+
* @param markdown åå§ Markdown å符串
|
|
8
|
+
*/
|
|
9
|
+
convert(markdown: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* äæę¤ä»£ē å
|
|
12
|
+
* ęÆę ```language å ~~~ äø¤ē§åę³
|
|
13
|
+
*/
|
|
14
|
+
private maskCodeBlocks;
|
|
15
|
+
/**
|
|
16
|
+
* äæę¤č”å
代ē
|
|
17
|
+
* `code` -> 使ēØåå¼å·å
裹ļ¼åŗå«äŗę®éęę¬
|
|
18
|
+
*/
|
|
19
|
+
private maskInlineCode;
|
|
20
|
+
/**
|
|
21
|
+
* čæå被ę©ē ēå
容
|
|
22
|
+
*/
|
|
23
|
+
private unmaskContent;
|
|
24
|
+
/**
|
|
25
|
+
* HTML å®ä½č§£ē
|
|
26
|
+
*/
|
|
27
|
+
private decodeHtmlEntities;
|
|
28
|
+
}
|
|
29
|
+
export declare const markdownToText: (md: string) => string;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export class MarkdownToText {
|
|
2
|
+
// ēØäŗęå被äæę¤ē代ē åļ¼é²ę¢č¢«ę£å误伤
|
|
3
|
+
codeBlockStore = new Map();
|
|
4
|
+
maskPrefix = '%%MD_MASK_';
|
|
5
|
+
maskCounter = 0;
|
|
6
|
+
/**
|
|
7
|
+
* äø»å
„å£ļ¼å° Markdown 转ę¢äøŗēŗÆęę¬
|
|
8
|
+
* @param markdown åå§ Markdown å符串
|
|
9
|
+
*/
|
|
10
|
+
convert(markdown) {
|
|
11
|
+
if (!markdown)
|
|
12
|
+
return '';
|
|
13
|
+
// 1. åå§å
|
|
14
|
+
this.codeBlockStore.clear();
|
|
15
|
+
this.maskCounter = 0;
|
|
16
|
+
let text = markdown;
|
|
17
|
+
// --- é¶ę®µ 1: äæę¤ę§é¢å¤ē (Protect) ---
|
|
18
|
+
// åæ
é”»ęå
ę§č”ļ¼é²ę¢ä»£ē éē注é # 被å½ęę é¢ļ¼ę ** 被å½ęē²ä½
|
|
19
|
+
text = this.maskCodeBlocks(text);
|
|
20
|
+
text = this.maskInlineCode(text);
|
|
21
|
+
// --- é¶ę®µ 2: ē»ęåč½¬ę¢ (Structure) ---
|
|
22
|
+
// 2.1 ęø
ē HTML ę ē¾ (äæē <br> ēę¢č”ęę)
|
|
23
|
+
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}`;
|
|
37
|
+
});
|
|
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
|
+
// ē§»é¤åƹé½č” |---|---|
|
|
47
|
+
text = text.replace(/^\s*\|?[\s\-:|]+\|?\s*$/gm, '');
|
|
48
|
+
// å° | Cell | Cell | 转ę¢äøŗē©ŗę ¼åéļ¼å°½éäæęäøč”
|
|
49
|
+
text = text.replace(/^\|(.*)\|$/gm, (_match, content) => {
|
|
50
|
+
// ē§»é¤é¦å°¾ē®”é符ļ¼äøé“ē®”é符åäøŗē©ŗę ¼
|
|
51
|
+
return content.split('|').map((s) => s.trim()).join(' ');
|
|
52
|
+
});
|
|
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
|
+
// 注ęļ¼åæ
é”»åØå¤ēå®ē²ä½åå¤ēęä½
|
|
59
|
+
text = text.replace(/([*_])([\s\S]*?)\1/g, '$2');
|
|
60
|
+
// 3.3 å é¤ēŗæ (Strikethrough) -> ē§»é¤å
容ęä»
ē§»é¤ē¬¦å·ļ¼éåøøä»
ē§»é¤ē¬¦å·
|
|
61
|
+
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 čæå代ē å
|
|
73
|
+
text = this.unmaskContent(text);
|
|
74
|
+
// 4.2 č§£ē HTML å®ä½ (& -> &)
|
|
75
|
+
text = this.decodeHtmlEntities(text);
|
|
76
|
+
// 4.3 ęē»ęēä¼å
|
|
77
|
+
// ē§»é¤ę®µé¦ę®µå°¾å¤ä½ē©ŗē½ļ¼å°čæē»3个仄äøę¢č”å缩为2äøŖļ¼ę®µč½é“č·ļ¼
|
|
78
|
+
text = text.replace(/\n{3,}/g, '\n\n').trim();
|
|
79
|
+
return text;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* äæę¤ä»£ē å
|
|
83
|
+
* ęÆę ```language å ~~~ äø¤ē§åę³
|
|
84
|
+
*/
|
|
85
|
+
maskCodeBlocks(text) {
|
|
86
|
+
// å¹é
3äøŖęę“å¤åå¼å·/波浪线
|
|
87
|
+
const codeBlockRegex = /(`{3,}|~{3,})(\w*)\n([\s\S]*?)\1/g;
|
|
88
|
+
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`;
|
|
93
|
+
this.codeBlockStore.set(key, formatted);
|
|
94
|
+
return key;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* äæę¤č”å
代ē
|
|
99
|
+
* `code` -> 使ēØåå¼å·å
裹ļ¼åŗå«äŗę®éęę¬
|
|
100
|
+
*/
|
|
101
|
+
maskInlineCode(text) {
|
|
102
|
+
return text.replace(/`([^`]+)`/g, (_match, code) => {
|
|
103
|
+
const key = `${this.maskPrefix}INLINE_${this.maskCounter++}`;
|
|
104
|
+
this.codeBlockStore.set(key, ` ā${code}ā `); // å ē©ŗę ¼é²ę¢ē²čæ
|
|
105
|
+
return key;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* čæå被ę©ē ēå
容
|
|
110
|
+
*/
|
|
111
|
+
unmaskContent(text) {
|
|
112
|
+
// 使ēØę£åå
Øå±å¹é
ęę mask key
|
|
113
|
+
const maskRegex = new RegExp(`${this.maskPrefix}\\w+_\\d+`, 'g');
|
|
114
|
+
return text.replace(maskRegex, (key) => {
|
|
115
|
+
return this.codeBlockStore.get(key) || '';
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* HTML å®ä½č§£ē
|
|
120
|
+
*/
|
|
121
|
+
decodeHtmlEntities(text) {
|
|
122
|
+
const entities = {
|
|
123
|
+
' ': ' ',
|
|
124
|
+
'&': '&',
|
|
125
|
+
'<': '<',
|
|
126
|
+
'>': '>',
|
|
127
|
+
'"': '"',
|
|
128
|
+
''': "'",
|
|
129
|
+
''': "'",
|
|
130
|
+
'©': 'Ā©',
|
|
131
|
+
'®': 'Ā®'
|
|
132
|
+
};
|
|
133
|
+
return text.replace(/&[a-z0-9#]+;/gi, (entity) => entities[entity] || entity);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// 导åŗåä¾č¾
å©å½ę°ļ¼ę¹ä¾æē“ę„č°ēØ
|
|
137
|
+
export const markdownToText = (md) => new MarkdownToText().convert(md);
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@izhimu/qq",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "A QQ channel plugin for OpenClaw using NapCat WebSocket",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE",
|
|
12
|
+
"openclaw.plugin.json"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18.0.0"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"release:patch": "npm version patch && npm publish --access public",
|
|
22
|
+
"release:minor": "npm version minor && npm publish --access public",
|
|
23
|
+
"release:major": "npm version major && npm publish --access public",
|
|
24
|
+
"release": "npm run release:patch"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"openclaw",
|
|
28
|
+
"plugin",
|
|
29
|
+
"qq"
|
|
30
|
+
],
|
|
31
|
+
"author": "izhimu",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"openclaw": {
|
|
34
|
+
"extensions": [
|
|
35
|
+
"./dist/index.js"
|
|
36
|
+
],
|
|
37
|
+
"install": {
|
|
38
|
+
"npmSpec": "@izhimu/qq",
|
|
39
|
+
"localPath": "extensions/qq",
|
|
40
|
+
"defaultChoice": "npm"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"openclaw": "^2026.2.1"
|
|
45
|
+
},
|
|
46
|
+
"peerDependenciesMeta": {
|
|
47
|
+
"openclaw": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"ws": "^8.19.0",
|
|
53
|
+
"zod": "^4.3.6"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^22.0.0",
|
|
57
|
+
"@types/ws": "^8.18.0",
|
|
58
|
+
"openclaw": "^2026.2.1",
|
|
59
|
+
"typescript": "^5.0.0"
|
|
60
|
+
}
|
|
61
|
+
}
|