@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.
Files changed (38) hide show
  1. package/README.md +21 -18
  2. package/dist/index.d.ts +5 -11
  3. package/dist/index.js +9 -18
  4. package/dist/src/adapters/message.d.ts +15 -4
  5. package/dist/src/adapters/message.js +179 -124
  6. package/dist/src/channel.d.ts +2 -7
  7. package/dist/src/channel.js +247 -278
  8. package/dist/src/core/auth.d.ts +67 -0
  9. package/dist/src/core/auth.js +154 -0
  10. package/dist/src/core/config.d.ts +7 -8
  11. package/dist/src/core/config.js +9 -9
  12. package/dist/src/core/connection.d.ts +6 -5
  13. package/dist/src/core/connection.js +17 -70
  14. package/dist/src/core/dispatch.d.ts +7 -54
  15. package/dist/src/core/dispatch.js +210 -398
  16. package/dist/src/core/event-handler.d.ts +42 -0
  17. package/dist/src/core/event-handler.js +171 -0
  18. package/dist/src/core/request.d.ts +3 -8
  19. package/dist/src/core/request.js +13 -126
  20. package/dist/src/core/runtime.d.ts +2 -11
  21. package/dist/src/core/runtime.js +0 -50
  22. package/dist/src/runtime.d.ts +3 -0
  23. package/dist/src/runtime.js +3 -0
  24. package/dist/src/setup-surface.d.ts +2 -0
  25. package/dist/src/setup-surface.js +59 -0
  26. package/dist/src/types/index.d.ts +69 -24
  27. package/dist/src/types/index.js +3 -4
  28. package/dist/src/utils/cqcode.d.ts +0 -9
  29. package/dist/src/utils/cqcode.js +0 -17
  30. package/dist/src/utils/index.d.ts +0 -17
  31. package/dist/src/utils/index.js +17 -154
  32. package/dist/src/utils/log.js +6 -3
  33. package/dist/src/utils/markdown.d.ts +5 -0
  34. package/dist/src/utils/markdown.js +57 -5
  35. package/openclaw.plugin.json +3 -2
  36. package/package.json +9 -11
  37. package/dist/src/onboarding.d.ts +0 -10
  38. package/dist/src/onboarding.js +0 -98
@@ -1,7 +1,39 @@
1
- /**
2
- * NapCat WebSocket API Types
3
- * Based on NapCat OneBot 11 implementation
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;
@@ -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
  }
@@ -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
  */
@@ -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
- * Map common QQ face IDs to emoji
26
+ * QQ 表情 ID emoji 的映射数组(下标即 ID)
36
27
  */
37
- export const FACE_ID_TO_EMOJI = {
38
- '0': '😊',
39
- '1': '😅',
40
- '2': '☺️',
41
- '3': '😄',
42
- '4': '😁',
43
- '5': '😆',
44
- '6': '😃',
45
- '7': '😂',
46
- '8': '🤣',
47
- '9': '😊',
48
- '10': '😍',
49
- '11': '🥰',
50
- '12': '😘',
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
- return FACE_ID_TO_EMOJI[faceId] || `[表情:${faceId}]`;
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
  /**
@@ -1,6 +1,7 @@
1
- import { getRuntime } from "../core/runtime.js";
1
+ import { DEBUG_MODE } from "../core/config";
2
+ import { getQQRuntime } from "../runtime.js";
2
3
  function log() {
3
- return getRuntime()?.logging.getChildLogger({ module: 'channel/qq' }) ?? console;
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
- log().debug?.(`[${category}] ${message}${param(args)}`);
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)}`);
@@ -6,6 +6,11 @@ export declare class MarkdownToText {
6
6
  * 主入口:将 Markdown 转换为纯文本
7
7
  */
8
8
  convert(markdown: string): string;
9
+ /**
10
+ * 转换 Markdown 表格为可读文本
11
+ * 支持对齐和多行表格
12
+ */
13
+ private convertTables;
9
14
  /**
10
15
  * 保护代码块 (```)
11
16
  * 生成 Key 格式:%%MD-MASK-BLOCK-0
@@ -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 = text.replace(/^\s*\|?[\s\-:|]+\|?\s*$/gm, ''); // 移除 |---|---| 分隔行
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
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "id": "qq",
3
- "name": "QQ",
3
+ "kind": "channel",
4
4
  "channels": ["qq"],
5
- "description": "QQ channel plugin for OpenClaw using NapCat WebSocket API",
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.5.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
- "./dist/index.js"
36
- ],
37
- "install": {
38
- "npmSpec": "@izhimu/qq",
39
- "localPath": "extensions/qq",
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.1"
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.1",
57
+ "openclaw": "^2026.3.22",
60
58
  "typescript": "^5.0.0"
61
59
  }
62
60
  }
@@ -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;
@@ -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
- };