chat-adapter-qq 0.0.0 → 0.0.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/README.md CHANGED
@@ -1,8 +1,49 @@
1
1
  # chat-adapter-qq
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/chat-adapter-qq)](https://www.npmjs.com/package/chat-adapter-qq)
4
+ [![npm downloads](https://img.shields.io/npm/dm/chat-adapter-qq)](https://www.npmjs.com/package/chat-adapter-qq)
3
5
  [![CI](https://github.com/yjl9903/openana/actions/workflows/ci.yml/badge.svg)](https://github.com/yjl9903/openana/actions/workflows/ci.yml)
4
6
 
5
- Work in progress.
7
+ QQ adapter for [Chat SDK](https://chat-sdk.dev/docs).
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install chat chat-adapter-qq
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ```ts
18
+ import { Chat } from 'chat';
19
+ import { createMemoryState } from '@chat-adapter/state-memory';
20
+ import { create } from 'chat-adapter-qq';
21
+
22
+ const bot = new Chat({
23
+ userName: '', // leave for empty
24
+ adapters: {
25
+ qq: createQQAdapter({
26
+ napcat: {
27
+ protocol: 'wss',
28
+ host: '<your napcat host>',
29
+ port: 443,
30
+ accessToken: '<your napcat access token>',
31
+ // ↓ 自动重连 (可选)
32
+ reconnection: {
33
+ enable: true,
34
+ attempts: 10,
35
+ delay: 5000
36
+ }
37
+ }
38
+ })
39
+ },
40
+ state: createMemoryState()
41
+ });
42
+
43
+ bot.onNewMention(async (thread, message) => {
44
+ await thread.post('Hello from QQ bot!');
45
+ });
46
+ ```
6
47
 
7
48
  ## License
8
49
 
package/dist/index.cjs CHANGED
@@ -1,7 +1,330 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region src/index.ts
3
- function hello() {
4
- return "world";
2
+ let chat = require("chat");
3
+ let _chat_adapter_shared = require("@chat-adapter/shared");
4
+ let node_napcat_ts = require("node-napcat-ts");
5
+ //#region src/format-converter.ts
6
+ /**
7
+ * @todo
8
+ */
9
+ var QQFormatConverter = class extends chat.BaseFormatConverter {
10
+ toAst(platformText) {
11
+ return (0, chat.parseMarkdown)(platformText);
12
+ }
13
+ fromAst(ast) {
14
+ return (0, chat.stringifyMarkdown)(ast);
15
+ }
16
+ };
17
+ //#endregion
18
+ //#region src/utils.ts
19
+ /** 将字符串 ID 转为正整数,失败时抛出 ValidationError。 */
20
+ function toNumberId(value, fieldName) {
21
+ const num = Number(value);
22
+ if (!Number.isInteger(num) || num <= 0) throw new _chat_adapter_shared.ValidationError("qq", `QQ ${fieldName} must be a positive integer: ${value}`);
23
+ return num;
24
+ }
25
+ /** 判断消息是否由 bot 自己发送(用于避免回环处理)。 */
26
+ function isSelfMessage(raw, selfId) {
27
+ if (!selfId) return false;
28
+ return String(raw.user_id) === selfId;
29
+ }
30
+ /** 判断消息是否命中 mention 语义。 */
31
+ function isMention(raw, selfId) {
32
+ if (raw.message_type === "private") return true;
33
+ if (!selfId) return false;
34
+ return raw.message.some((segment) => segment.type === "at" && segment.data.qq !== "all" && String(segment.data.qq) === selfId);
35
+ }
36
+ /** 将 QQ 原始消息映射为统一的 QQ thread ID 结构。 */
37
+ function toThreadId(raw) {
38
+ if (raw.message_type === "group") return {
39
+ chatType: "group",
40
+ peerId: String(raw.group_id)
41
+ };
42
+ return {
43
+ chatType: "private",
44
+ peerId: String(raw.user_id)
45
+ };
46
+ }
47
+ /** 将 QQ 原始作者信息映射为 Chat SDK Author。 */
48
+ function toAuthor(raw, isMe) {
49
+ return {
50
+ userId: String(raw.user_id),
51
+ userName: String(raw.user_id),
52
+ fullName: raw.sender.card || raw.sender.nickname || String(raw.user_id),
53
+ isBot: isMe,
54
+ isMe
55
+ };
56
+ }
57
+ /** 从 NapCat segment 列表提取纯文本;必要时回退 raw_message。 */
58
+ function extractText(raw) {
59
+ const text = raw.message.map(segmentToText).join("");
60
+ if (text.length > 0) return text;
61
+ return raw.raw_message ? (0, chat.toPlainText)((0, chat.parseMarkdown)(raw.raw_message)) : "";
62
+ }
63
+ /** 将 NapCat segment 中可识别的媒体映射为 Chat SDK attachments。 */
64
+ function toAttachments(message) {
65
+ return message.map((segment) => {
66
+ if (segment.type === "image") return {
67
+ type: "image",
68
+ url: "url" in segment.data ? segment.data.url : void 0
69
+ };
70
+ if (segment.type === "file") return {
71
+ type: "file",
72
+ name: segment.data.file
73
+ };
74
+ if (segment.type === "video") return {
75
+ type: "video",
76
+ url: segment.data.url,
77
+ name: segment.data.file
78
+ };
79
+ if (segment.type === "record") return {
80
+ type: "audio",
81
+ name: segment.data.file
82
+ };
83
+ return null;
84
+ }).filter((attachment) => attachment !== null);
85
+ }
86
+ /** 将单个 NapCat segment 转为可读文本片段。 */
87
+ function segmentToText(segment) {
88
+ if (segment.type === "text") return segment.data.text;
89
+ if (segment.type === "at") return segment.data.qq === "all" ? "@all" : `@${segment.data.qq}`;
90
+ if (segment.type === "face") return `[face:${segment.data.id}]`;
91
+ if (segment.type === "image") return "[image]";
92
+ if (segment.type === "file") return `[file:${segment.data.file}]`;
93
+ if (segment.type === "record") return "[audio]";
94
+ if (segment.type === "video") return "[video]";
95
+ if (segment.type === "markdown") return segment.data.content;
96
+ return "";
97
+ }
98
+ //#endregion
99
+ //#region src/adapter.ts
100
+ /**
101
+ * Chat SDK QQ 平台适配器(基于 NapCat WebSocket)。
102
+ *
103
+ * 设计说明:
104
+ * - 入口仅支持 WS 事件推送,不支持 HTTP webhook。
105
+ * - thread 模型采用“会话即 thread”:
106
+ * - 群:`qq:group:{group_id}`
107
+ * - 私聊:`qq:private:{user_id}`
108
+ */
109
+ var QQAdapter = class {
110
+ /** 适配器名称,作为 Chat SDK 的 adapter key。 */
111
+ name = "qq";
112
+ /** 机器人用户名(初始化后由登录信息填充)。 */
113
+ userName;
114
+ /** 机器人用户标识(初始化后由登录信息填充)。 */
115
+ botUserId;
116
+ config;
117
+ converter = new QQFormatConverter();
118
+ chat = null;
119
+ client;
120
+ selfId;
121
+ logger;
122
+ listenersBound = false;
123
+ initializing;
124
+ /** 创建 QQ 适配器实例(不发起连接)。 */
125
+ constructor(config) {
126
+ this.config = config;
127
+ this.userName = "";
128
+ this.logger = config.logger ?? {
129
+ child: () => this.logger,
130
+ debug: () => {},
131
+ info: () => {},
132
+ warn: () => {},
133
+ error: () => {}
134
+ };
135
+ }
136
+ /**
137
+ * 初始化适配器,建立 NapCat 连接并注册消息监听。
138
+ * 该方法具有幂等性,多次调用会复用同一初始化流程。
139
+ */
140
+ async initialize(chat$1) {
141
+ if (this.initializing) return this.initializing;
142
+ this.initializing = this.doInitialize(chat$1).catch((error) => {
143
+ this.initializing = void 0;
144
+ throw error;
145
+ });
146
+ return this.initializing;
147
+ }
148
+ async doInitialize(chat$2) {
149
+ this.chat = chat$2;
150
+ this.logger = this.config.logger ?? chat$2.getLogger(this.name);
151
+ if (!this.client) this.client = new node_napcat_ts.NCWebsocket(this.config.napcat, this.config.debug ?? false);
152
+ this.bindListeners();
153
+ await this.client.connect();
154
+ const login = await this.client.get_login_info();
155
+ this.logger.info("login with", login);
156
+ this.selfId = String(login.user_id);
157
+ this.botUserId = login.nickname;
158
+ this.userName = login.nickname;
159
+ }
160
+ /** QQ 适配器为 WS-only 模式,HTTP webhook 入口固定返回 501。 */
161
+ async handleWebhook(_request, _options) {
162
+ return new Response("QQ adapter uses NapCat WebSocket ingress only.", { status: 501 });
163
+ }
164
+ /** 编码 thread ID:`qq:{chatType}:{peerId}`。 */
165
+ encodeThreadId(data) {
166
+ if (data.chatType !== "group" && data.chatType !== "private") throw new _chat_adapter_shared.ValidationError("qq", `Unsupported QQ chat type: ${data.chatType}`);
167
+ if (!data.peerId) throw new _chat_adapter_shared.ValidationError("qq", "QQ thread peerId is required");
168
+ return `qq:${data.chatType}:${data.peerId}`;
169
+ }
170
+ /** 解码 thread ID 并进行格式校验。 */
171
+ decodeThreadId(threadId) {
172
+ const parts = threadId.split(":");
173
+ if (parts.length !== 3 || parts[0] !== "qq") throw new _chat_adapter_shared.ValidationError("qq", `Invalid QQ thread ID: ${threadId}`);
174
+ const chatType = parts[1];
175
+ if (chatType !== "group" && chatType !== "private") throw new _chat_adapter_shared.ValidationError("qq", `Invalid QQ thread type in thread ID: ${threadId}`);
176
+ const peerId = parts[2];
177
+ if (!peerId) throw new _chat_adapter_shared.ValidationError("qq", `Invalid QQ thread peer ID in thread ID: ${threadId}`);
178
+ return {
179
+ chatType,
180
+ peerId
181
+ };
182
+ }
183
+ /** 当前模型下 channelId 与 threadId 一致。 */
184
+ channelIdFromThreadId(threadId) {
185
+ this.decodeThreadId(threadId);
186
+ return threadId;
187
+ }
188
+ /** 判断当前 thread 是否为私聊会话。 */
189
+ isDM(threadId) {
190
+ return this.decodeThreadId(threadId).chatType === "private";
191
+ }
192
+ /** 向群或私聊发送消息。 */
193
+ async postMessage(threadId, message) {
194
+ const client = this.requireClient();
195
+ const parsed = this.decodeThreadId(threadId);
196
+ const peerId = toNumberId(parsed.peerId, "peerId");
197
+ const text = this.converter.renderPostable(message);
198
+ const outgoingText = text.length > 0 ? text : " ";
199
+ const segments = [node_napcat_ts.Structs.text(outgoingText)];
200
+ this.logger.debug("post message", parsed.chatType, parsed.peerId, segments);
201
+ const sent = parsed.chatType === "group" ? await client.send_group_msg({
202
+ group_id: peerId,
203
+ message: segments
204
+ }) : await client.send_private_msg({
205
+ user_id: peerId,
206
+ message: segments
207
+ });
208
+ return {
209
+ id: String(sent.message_id),
210
+ raw: {
211
+ ...sent,
212
+ threadId,
213
+ chatType: parsed.chatType
214
+ },
215
+ threadId
216
+ };
217
+ }
218
+ /** QQ 暂不支持编辑消息。 */
219
+ async editMessage(_threadId, _messageId, _message) {
220
+ throw new chat.NotImplementedError("QQ adapter does not support editMessage yet", "editMessage");
221
+ }
222
+ /** 删除指定消息。 */
223
+ async deleteMessage(_threadId, messageId) {
224
+ await this.requireClient().delete_msg({ message_id: toNumberId(messageId, "messageId") });
225
+ }
226
+ /** QQ 暂不支持反应能力(添加)。 */
227
+ async addReaction(_threadId, _messageId, _emoji) {
228
+ throw new chat.NotImplementedError("QQ adapter does not support addReaction yet", "addReaction");
229
+ }
230
+ /** QQ 暂不支持反应能力(移除)。 */
231
+ async removeReaction(_threadId, _messageId, _emoji) {
232
+ throw new chat.NotImplementedError("QQ adapter does not support removeReaction yet", "removeReaction");
233
+ }
234
+ /** QQ 暂不支持历史消息分页拉取。 */
235
+ async fetchMessages(_threadId, _options) {
236
+ throw new chat.NotImplementedError("QQ adapter does not support fetchMessages yet", "fetchMessages");
237
+ }
238
+ /** 获取 thread 基础信息。 */
239
+ async fetchThread(threadId) {
240
+ const parsed = this.decodeThreadId(threadId);
241
+ return {
242
+ id: threadId,
243
+ channelId: threadId,
244
+ isDM: parsed.chatType === "private",
245
+ metadata: {
246
+ chatType: parsed.chatType,
247
+ peerId: parsed.peerId
248
+ }
249
+ };
250
+ }
251
+ /** 当前实现不发送真实 typing,仅作为 no-op。 */
252
+ startTyping(_threadId, _status) {
253
+ return Promise.resolve();
254
+ }
255
+ /** 渲染格式化内容到 QQ 文本。 */
256
+ renderFormatted(content) {
257
+ return this.converter.fromAst(content);
258
+ }
259
+ /** 将 NapCat 原始消息转换为 Chat SDK 标准 Message。 */
260
+ parseMessage(raw) {
261
+ const threadId = this.encodeThreadId(toThreadId(raw));
262
+ const text = extractText(raw);
263
+ const isMe = this.selfId !== void 0 && String(raw.user_id) === this.selfId;
264
+ return new chat.Message({
265
+ id: String(raw.message_id),
266
+ threadId,
267
+ text,
268
+ formatted: this.converter.toAst(text),
269
+ author: toAuthor(raw, isMe),
270
+ metadata: {
271
+ dateSent: /* @__PURE__ */ new Date(raw.time * 1e3),
272
+ edited: false
273
+ },
274
+ attachments: toAttachments(raw.message),
275
+ raw
276
+ });
277
+ }
278
+ bindListeners() {
279
+ if (!this.client || this.listenersBound) return;
280
+ this.client.on("message.group", this.onGroupMessage);
281
+ this.client.on("message.private", this.onPrivateMessage);
282
+ this.listenersBound = true;
283
+ }
284
+ onGroupMessage = (raw) => {
285
+ this.dispatchIncomingMessage(raw);
286
+ };
287
+ onPrivateMessage = (raw) => {
288
+ this.dispatchIncomingMessage(raw);
289
+ };
290
+ /**
291
+ * 统一处理入站消息:
292
+ * - 过滤 bot 自己发送的消息
293
+ * - 计算 threadId 与 mention
294
+ * - 交给 Chat SDK `processMessage` 进入标准事件流
295
+ */
296
+ dispatchIncomingMessage(raw) {
297
+ if (!this.chat) return;
298
+ if (isSelfMessage(raw, this.selfId)) return;
299
+ const threadId = this.encodeThreadId(toThreadId(raw));
300
+ const mention = isMention(raw, this.selfId);
301
+ this.chat.processMessage(this, threadId, async () => {
302
+ const message = this.parseMessage(raw);
303
+ message.isMention = mention;
304
+ return message;
305
+ });
306
+ }
307
+ /** 获取已初始化的 NapCat 客户端,否则抛出配置错误。 */
308
+ requireClient() {
309
+ if (!this.client) throw new _chat_adapter_shared.ValidationError("qq", "QQ adapter is not initialized. Attach it to Chat and call chat.initialize() first.");
310
+ return this.client;
311
+ }
312
+ };
313
+ //#endregion
314
+ //#region src/factory.ts
315
+ /**
316
+ * 创建 QQ 适配器实例。
317
+ * 仅支持显式配置,不读取环境变量。
318
+ *
319
+ * @param config QQ 适配器配置
320
+ * @returns 已创建但未初始化的 QQAdapter 实例
321
+ */
322
+ function createQQAdapter(config) {
323
+ if (!config || !config.napcat) throw new _chat_adapter_shared.ValidationError("qq", "QQ NapCat config is required. Pass { napcat: NCWebsocketOptions } to createQQAdapter(config).");
324
+ return new QQAdapter(config);
5
325
  }
6
326
  //#endregion
7
- exports.hello = hello;
327
+ exports.QQAdapter = QQAdapter;
328
+ exports.createQQAdapter = createQQAdapter;
329
+
330
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["BaseFormatConverter","ValidationError","chat","NCWebsocket","ValidationError","Structs","NotImplementedError","Message","ValidationError"],"sources":["../src/format-converter.ts","../src/utils.ts","../src/adapter.ts","../src/factory.ts"],"sourcesContent":["import { BaseFormatConverter, parseMarkdown, stringifyMarkdown, type Root } from 'chat';\n\n/**\n * @todo\n */\nexport class QQFormatConverter extends BaseFormatConverter {\n toAst(platformText: string): Root {\n return parseMarkdown(platformText);\n }\n\n fromAst(ast: Root): string {\n return stringifyMarkdown(ast);\n }\n}\n","import type { Receive } from 'node-napcat-ts';\n\nimport { ValidationError } from '@chat-adapter/shared';\nimport { parseMarkdown, toPlainText, type Attachment, type Author } from 'chat';\n\nimport type { QQRawMessage, QQThreadId } from './types.js';\n\n/** NapCat 入站 message segment 的联合类型。 */\nexport type QQMessageSegment = Receive[keyof Receive];\n\n/** 将字符串 ID 转为正整数,失败时抛出 ValidationError。 */\nexport function toNumberId(value: string, fieldName: string): number {\n const num = Number(value);\n if (!Number.isInteger(num) || num <= 0) {\n throw new ValidationError('qq', `QQ ${fieldName} must be a positive integer: ${value}`);\n }\n return num;\n}\n\n/** 判断消息是否由 bot 自己发送(用于避免回环处理)。 */\nexport function isSelfMessage(raw: QQRawMessage, selfId?: string): boolean {\n if (!selfId) {\n return false;\n }\n\n return String(raw.user_id) === selfId;\n}\n\n/** 判断消息是否命中 mention 语义。 */\nexport function isMention(raw: QQRawMessage, selfId?: string): boolean {\n if (raw.message_type === 'private') {\n return true;\n }\n\n if (!selfId) {\n return false;\n }\n\n return raw.message.some(\n (segment) =>\n segment.type === 'at' && segment.data.qq !== 'all' && String(segment.data.qq) === selfId\n );\n}\n\n/** 将 QQ 原始消息映射为统一的 QQ thread ID 结构。 */\nexport function toThreadId(raw: QQRawMessage): QQThreadId {\n if (raw.message_type === 'group') {\n return {\n chatType: 'group',\n peerId: String(raw.group_id)\n };\n }\n\n return {\n chatType: 'private',\n peerId: String(raw.user_id)\n };\n}\n\n/** 将 QQ 原始作者信息映射为 Chat SDK Author。 */\nexport function toAuthor(raw: QQRawMessage, isMe: boolean): Author {\n return {\n userId: String(raw.user_id),\n userName: String(raw.user_id),\n fullName: raw.sender.card || raw.sender.nickname || String(raw.user_id),\n isBot: isMe,\n isMe\n };\n}\n\n/** 从 NapCat segment 列表提取纯文本;必要时回退 raw_message。 */\nexport function extractText(raw: QQRawMessage): string {\n const text = raw.message.map(segmentToText).join('');\n if (text.length > 0) {\n return text;\n }\n\n return raw.raw_message ? toPlainText(parseMarkdown(raw.raw_message)) : '';\n}\n\n/** 将 NapCat segment 中可识别的媒体映射为 Chat SDK attachments。 */\nexport function toAttachments(message: QQMessageSegment[]): Attachment[] {\n return message\n .map((segment) => {\n if (segment.type === 'image') {\n return {\n type: 'image' as const,\n url: 'url' in segment.data ? segment.data.url : undefined\n };\n }\n\n if (segment.type === 'file') {\n return {\n type: 'file' as const,\n name: segment.data.file\n };\n }\n\n if (segment.type === 'video') {\n return {\n type: 'video' as const,\n url: segment.data.url,\n name: segment.data.file\n };\n }\n\n if (segment.type === 'record') {\n return {\n type: 'audio' as const,\n name: segment.data.file\n };\n }\n\n return null;\n })\n .filter((attachment): attachment is NonNullable<typeof attachment> => attachment !== null);\n}\n\n/** 将单个 NapCat segment 转为可读文本片段。 */\nexport function segmentToText(segment: QQMessageSegment): string {\n if (segment.type === 'text') {\n return segment.data.text;\n }\n\n if (segment.type === 'at') {\n return segment.data.qq === 'all' ? '@all' : `@${segment.data.qq}`;\n }\n\n if (segment.type === 'face') {\n return `[face:${segment.data.id}]`;\n }\n\n if (segment.type === 'image') {\n return '[image]';\n }\n\n if (segment.type === 'file') {\n return `[file:${segment.data.file}]`;\n }\n\n if (segment.type === 'record') {\n return '[audio]';\n }\n\n if (segment.type === 'video') {\n return '[video]';\n }\n\n if (segment.type === 'markdown') {\n return segment.data.content;\n }\n\n return '';\n}\n","import {\n type Adapter,\n type AdapterPostableMessage,\n type ChatInstance,\n type EmojiValue,\n type FetchOptions,\n type FetchResult,\n type FormattedContent,\n type Logger,\n type RawMessage,\n type ThreadInfo,\n type WebhookOptions,\n NotImplementedError,\n Message\n} from 'chat';\nimport { ValidationError } from '@chat-adapter/shared';\nimport { NCWebsocket, Structs, type SendMessageSegment } from 'node-napcat-ts';\n\nimport type {\n QQAdapterConfig,\n QQGroupMessage,\n QQNapcatClient,\n QQPrivateMessage,\n QQRawMessage,\n QQThreadId\n} from './types.js';\n\nimport { QQFormatConverter } from './format-converter.js';\nimport {\n extractText,\n isMention,\n isSelfMessage,\n toAttachments,\n toAuthor,\n toNumberId,\n toThreadId\n} from './utils.js';\n\n/**\n * Chat SDK QQ 平台适配器(基于 NapCat WebSocket)。\n *\n * 设计说明:\n * - 入口仅支持 WS 事件推送,不支持 HTTP webhook。\n * - thread 模型采用“会话即 thread”:\n * - 群:`qq:group:{group_id}`\n * - 私聊:`qq:private:{user_id}`\n */\nexport class QQAdapter implements Adapter<QQThreadId, QQRawMessage> {\n /** 适配器名称,作为 Chat SDK 的 adapter key。 */\n readonly name = 'qq';\n\n /** 机器人用户名(初始化后由登录信息填充)。 */\n userName: string;\n\n /** 机器人用户标识(初始化后由登录信息填充)。 */\n botUserId?: string;\n\n // ---\n\n private readonly config: QQAdapterConfig;\n\n private readonly converter = new QQFormatConverter();\n\n private chat: ChatInstance | null = null;\n\n private client?: QQNapcatClient;\n\n private selfId?: string;\n\n private logger: Logger;\n\n private listenersBound = false;\n\n private initializing?: Promise<void>;\n\n /** 创建 QQ 适配器实例(不发起连接)。 */\n public constructor(config: QQAdapterConfig) {\n this.config = config;\n this.userName = '';\n this.logger = config.logger ?? {\n child: () => this.logger,\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {}\n };\n }\n\n /**\n * 初始化适配器,建立 NapCat 连接并注册消息监听。\n * 该方法具有幂等性,多次调用会复用同一初始化流程。\n */\n public async initialize(chat: ChatInstance): Promise<void> {\n if (this.initializing) {\n return this.initializing;\n }\n\n this.initializing = this.doInitialize(chat).catch((error) => {\n this.initializing = undefined;\n throw error;\n });\n\n return this.initializing;\n }\n\n private async doInitialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n this.logger = this.config.logger ?? chat.getLogger(this.name);\n\n if (!this.client) {\n this.client = new NCWebsocket(this.config.napcat, this.config.debug ?? false);\n }\n\n this.bindListeners();\n\n await this.client.connect();\n\n const login = await this.client.get_login_info();\n this.logger.info('login with', login);\n\n this.selfId = String(login.user_id);\n this.botUserId = login.nickname;\n this.userName = login.nickname;\n }\n\n /** QQ 适配器为 WS-only 模式,HTTP webhook 入口固定返回 501。 */\n public async handleWebhook(_request: Request, _options?: WebhookOptions): Promise<Response> {\n return new Response('QQ adapter uses NapCat WebSocket ingress only.', {\n status: 501\n });\n }\n\n /** 编码 thread ID:`qq:{chatType}:{peerId}`。 */\n public encodeThreadId(data: QQThreadId): string {\n if (data.chatType !== 'group' && data.chatType !== 'private') {\n throw new ValidationError('qq', `Unsupported QQ chat type: ${data.chatType}`);\n }\n\n if (!data.peerId) {\n throw new ValidationError('qq', 'QQ thread peerId is required');\n }\n\n return `qq:${data.chatType}:${data.peerId}`;\n }\n\n /** 解码 thread ID 并进行格式校验。 */\n public decodeThreadId(threadId: string): QQThreadId {\n const parts = threadId.split(':');\n if (parts.length !== 3 || parts[0] !== 'qq') {\n throw new ValidationError('qq', `Invalid QQ thread ID: ${threadId}`);\n }\n\n const chatType = parts[1];\n if (chatType !== 'group' && chatType !== 'private') {\n throw new ValidationError('qq', `Invalid QQ thread type in thread ID: ${threadId}`);\n }\n\n const peerId = parts[2];\n if (!peerId) {\n throw new ValidationError('qq', `Invalid QQ thread peer ID in thread ID: ${threadId}`);\n }\n\n return {\n chatType,\n peerId\n };\n }\n\n /** 当前模型下 channelId 与 threadId 一致。 */\n public channelIdFromThreadId(threadId: string): string {\n this.decodeThreadId(threadId);\n return threadId;\n }\n\n /** 判断当前 thread 是否为私聊会话。 */\n public isDM(threadId: string): boolean {\n return this.decodeThreadId(threadId).chatType === 'private';\n }\n\n /** 向群或私聊发送消息。 */\n public async postMessage(\n threadId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<QQRawMessage>> {\n const client = this.requireClient();\n const parsed = this.decodeThreadId(threadId);\n const peerId = toNumberId(parsed.peerId, 'peerId');\n const text = this.converter.renderPostable(message);\n const outgoingText = text.length > 0 ? text : ' ';\n const segments: SendMessageSegment[] = [Structs.text(outgoingText)];\n\n this.logger.debug('post message', parsed.chatType, parsed.peerId, segments);\n\n const sent =\n parsed.chatType === 'group'\n ? await client.send_group_msg({\n group_id: peerId,\n message: segments\n })\n : await client.send_private_msg({\n user_id: peerId,\n message: segments\n });\n\n return {\n id: String(sent.message_id),\n raw: {\n ...sent,\n threadId,\n chatType: parsed.chatType\n } as unknown as QQRawMessage,\n threadId\n };\n }\n\n /** QQ 暂不支持编辑消息。 */\n public async editMessage(\n _threadId: string,\n _messageId: string,\n _message: AdapterPostableMessage\n ): Promise<RawMessage<QQRawMessage>> {\n throw new NotImplementedError('QQ adapter does not support editMessage yet', 'editMessage');\n }\n\n /** 删除指定消息。 */\n public async deleteMessage(_threadId: string, messageId: string): Promise<void> {\n const client = this.requireClient();\n await client.delete_msg({ message_id: toNumberId(messageId, 'messageId') });\n }\n\n /** QQ 暂不支持反应能力(添加)。 */\n public async addReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string\n ): Promise<void> {\n throw new NotImplementedError('QQ adapter does not support addReaction yet', 'addReaction');\n }\n\n /** QQ 暂不支持反应能力(移除)。 */\n public async removeReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string\n ): Promise<void> {\n throw new NotImplementedError(\n 'QQ adapter does not support removeReaction yet',\n 'removeReaction'\n );\n }\n\n /** QQ 暂不支持历史消息分页拉取。 */\n public async fetchMessages(\n _threadId: string,\n _options?: FetchOptions\n ): Promise<FetchResult<QQRawMessage>> {\n throw new NotImplementedError('QQ adapter does not support fetchMessages yet', 'fetchMessages');\n }\n\n /** 获取 thread 基础信息。 */\n public async fetchThread(threadId: string): Promise<ThreadInfo> {\n const parsed = this.decodeThreadId(threadId);\n\n return {\n id: threadId,\n channelId: threadId,\n isDM: parsed.chatType === 'private',\n metadata: {\n chatType: parsed.chatType,\n peerId: parsed.peerId\n }\n };\n }\n\n /** 当前实现不发送真实 typing,仅作为 no-op。 */\n public startTyping(_threadId: string, _status?: string): Promise<void> {\n return Promise.resolve();\n }\n\n /** 渲染格式化内容到 QQ 文本。 */\n public renderFormatted(content: FormattedContent): string {\n return this.converter.fromAst(content);\n }\n\n /** 将 NapCat 原始消息转换为 Chat SDK 标准 Message。 */\n public parseMessage(raw: QQRawMessage): Message<QQRawMessage> {\n const threadId = this.encodeThreadId(toThreadId(raw));\n const text = extractText(raw);\n const isMe = this.selfId !== undefined && String(raw.user_id) === this.selfId;\n\n return new Message<QQRawMessage>({\n id: String(raw.message_id),\n threadId,\n text,\n formatted: this.converter.toAst(text),\n author: toAuthor(raw, isMe),\n metadata: {\n dateSent: new Date(raw.time * 1000),\n edited: false\n },\n attachments: toAttachments(raw.message),\n raw\n });\n }\n\n private bindListeners(): void {\n if (!this.client || this.listenersBound) {\n return;\n }\n\n this.client.on('message.group', this.onGroupMessage);\n this.client.on('message.private', this.onPrivateMessage);\n this.listenersBound = true;\n }\n\n private readonly onGroupMessage = (raw: QQGroupMessage): void => {\n this.dispatchIncomingMessage(raw);\n };\n\n private readonly onPrivateMessage = (raw: QQPrivateMessage): void => {\n this.dispatchIncomingMessage(raw);\n };\n\n /**\n * 统一处理入站消息:\n * - 过滤 bot 自己发送的消息\n * - 计算 threadId 与 mention\n * - 交给 Chat SDK `processMessage` 进入标准事件流\n */\n private dispatchIncomingMessage(raw: QQRawMessage): void {\n if (!this.chat) {\n return;\n }\n\n if (isSelfMessage(raw, this.selfId)) {\n return;\n }\n\n const threadId = this.encodeThreadId(toThreadId(raw));\n const mention = isMention(raw, this.selfId);\n\n this.chat.processMessage(this, threadId, async () => {\n const message = this.parseMessage(raw);\n message.isMention = mention;\n return message;\n });\n }\n\n /** 获取已初始化的 NapCat 客户端,否则抛出配置错误。 */\n private requireClient(): QQNapcatClient {\n if (!this.client) {\n throw new ValidationError(\n 'qq',\n 'QQ adapter is not initialized. Attach it to Chat and call chat.initialize() first.'\n );\n }\n\n return this.client;\n }\n}\n","import { ValidationError } from '@chat-adapter/shared';\n\nimport type { QQAdapterConfig } from './types.js';\n\nimport { QQAdapter } from './adapter.js';\n\n/**\n * 创建 QQ 适配器实例。\n * 仅支持显式配置,不读取环境变量。\n *\n * @param config QQ 适配器配置\n * @returns 已创建但未初始化的 QQAdapter 实例\n */\nexport function createQQAdapter(config: QQAdapterConfig): QQAdapter {\n if (!config || !config.napcat) {\n throw new ValidationError(\n 'qq',\n 'QQ NapCat config is required. Pass { napcat: NCWebsocketOptions } to createQQAdapter(config).'\n );\n }\n\n return new QQAdapter(config);\n}\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -1,4 +1,127 @@
1
- //#region src/index.d.ts
2
- declare function hello(): string;
1
+ import { Adapter, AdapterPostableMessage, ChatInstance, EmojiValue, FetchOptions, FetchResult, FormattedContent, Logger, Message, RawMessage, ThreadInfo, WebhookOptions } from "chat";
2
+ import { MessageHandler, NCWebsocket, NCWebsocketOptions } from "node-napcat-ts";
3
+
4
+ //#region src/types.d.ts
5
+ /**
6
+ * QQ 会话类型。
7
+ * - `group`: 群会话
8
+ * - `private`: 私聊会话
9
+ */
10
+ type QQChatType = 'group' | 'private';
11
+ /**
12
+ * QQ thread ID 的解码结构。
13
+ * 对应编码格式:`qq:{chatType}:{peerId}`。
14
+ */
15
+ interface QQThreadId {
16
+ /** 会话类型(群/私聊) */
17
+ chatType: QQChatType;
18
+ /** 对端 ID:群号或用户 QQ 号(字符串形式) */
19
+ peerId: string;
20
+ }
21
+ /** NapCat WebSocket 客户端类型别名。 */
22
+ type QQNapcatClient = NCWebsocket;
23
+ /** QQ 适配器统一使用的原始消息类型(message union)。 */
24
+ type QQRawMessage = MessageHandler['message'];
25
+ /** QQ 群消息原始类型。 */
26
+ type QQGroupMessage = MessageHandler['message.group'];
27
+ /** QQ 私聊消息原始类型。 */
28
+ type QQPrivateMessage = MessageHandler['message.private'];
29
+ /** QQ 适配器配置。 */
30
+ interface QQAdapterConfig {
31
+ /** NapCat 连接配置(必填)。 */
32
+ napcat: NCWebsocketOptions;
33
+ /** 是否启用 NapCat SDK 的 debug 输出。 */
34
+ debug?: boolean;
35
+ /** 自定义 logger;未传时使用 Chat SDK 提供的 logger。 */
36
+ logger?: Logger;
37
+ }
3
38
  //#endregion
4
- export { hello };
39
+ //#region src/adapter.d.ts
40
+ /**
41
+ * Chat SDK QQ 平台适配器(基于 NapCat WebSocket)。
42
+ *
43
+ * 设计说明:
44
+ * - 入口仅支持 WS 事件推送,不支持 HTTP webhook。
45
+ * - thread 模型采用“会话即 thread”:
46
+ * - 群:`qq:group:{group_id}`
47
+ * - 私聊:`qq:private:{user_id}`
48
+ */
49
+ declare class QQAdapter implements Adapter<QQThreadId, QQRawMessage> {
50
+ /** 适配器名称,作为 Chat SDK 的 adapter key。 */
51
+ readonly name = "qq";
52
+ /** 机器人用户名(初始化后由登录信息填充)。 */
53
+ userName: string;
54
+ /** 机器人用户标识(初始化后由登录信息填充)。 */
55
+ botUserId?: string;
56
+ private readonly config;
57
+ private readonly converter;
58
+ private chat;
59
+ private client?;
60
+ private selfId?;
61
+ private logger;
62
+ private listenersBound;
63
+ private initializing?;
64
+ /** 创建 QQ 适配器实例(不发起连接)。 */
65
+ constructor(config: QQAdapterConfig);
66
+ /**
67
+ * 初始化适配器,建立 NapCat 连接并注册消息监听。
68
+ * 该方法具有幂等性,多次调用会复用同一初始化流程。
69
+ */
70
+ initialize(chat: ChatInstance): Promise<void>;
71
+ private doInitialize;
72
+ /** QQ 适配器为 WS-only 模式,HTTP webhook 入口固定返回 501。 */
73
+ handleWebhook(_request: Request, _options?: WebhookOptions): Promise<Response>;
74
+ /** 编码 thread ID:`qq:{chatType}:{peerId}`。 */
75
+ encodeThreadId(data: QQThreadId): string;
76
+ /** 解码 thread ID 并进行格式校验。 */
77
+ decodeThreadId(threadId: string): QQThreadId;
78
+ /** 当前模型下 channelId 与 threadId 一致。 */
79
+ channelIdFromThreadId(threadId: string): string;
80
+ /** 判断当前 thread 是否为私聊会话。 */
81
+ isDM(threadId: string): boolean;
82
+ /** 向群或私聊发送消息。 */
83
+ postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage<QQRawMessage>>;
84
+ /** QQ 暂不支持编辑消息。 */
85
+ editMessage(_threadId: string, _messageId: string, _message: AdapterPostableMessage): Promise<RawMessage<QQRawMessage>>;
86
+ /** 删除指定消息。 */
87
+ deleteMessage(_threadId: string, messageId: string): Promise<void>;
88
+ /** QQ 暂不支持反应能力(添加)。 */
89
+ addReaction(_threadId: string, _messageId: string, _emoji: EmojiValue | string): Promise<void>;
90
+ /** QQ 暂不支持反应能力(移除)。 */
91
+ removeReaction(_threadId: string, _messageId: string, _emoji: EmojiValue | string): Promise<void>;
92
+ /** QQ 暂不支持历史消息分页拉取。 */
93
+ fetchMessages(_threadId: string, _options?: FetchOptions): Promise<FetchResult<QQRawMessage>>;
94
+ /** 获取 thread 基础信息。 */
95
+ fetchThread(threadId: string): Promise<ThreadInfo>;
96
+ /** 当前实现不发送真实 typing,仅作为 no-op。 */
97
+ startTyping(_threadId: string, _status?: string): Promise<void>;
98
+ /** 渲染格式化内容到 QQ 文本。 */
99
+ renderFormatted(content: FormattedContent): string;
100
+ /** 将 NapCat 原始消息转换为 Chat SDK 标准 Message。 */
101
+ parseMessage(raw: QQRawMessage): Message<QQRawMessage>;
102
+ private bindListeners;
103
+ private readonly onGroupMessage;
104
+ private readonly onPrivateMessage;
105
+ /**
106
+ * 统一处理入站消息:
107
+ * - 过滤 bot 自己发送的消息
108
+ * - 计算 threadId 与 mention
109
+ * - 交给 Chat SDK `processMessage` 进入标准事件流
110
+ */
111
+ private dispatchIncomingMessage;
112
+ /** 获取已初始化的 NapCat 客户端,否则抛出配置错误。 */
113
+ private requireClient;
114
+ }
115
+ //#endregion
116
+ //#region src/factory.d.ts
117
+ /**
118
+ * 创建 QQ 适配器实例。
119
+ * 仅支持显式配置,不读取环境变量。
120
+ *
121
+ * @param config QQ 适配器配置
122
+ * @returns 已创建但未初始化的 QQAdapter 实例
123
+ */
124
+ declare function createQQAdapter(config: QQAdapterConfig): QQAdapter;
125
+ //#endregion
126
+ export { QQAdapter, type QQAdapterConfig, type QQChatType, type QQGroupMessage, type QQNapcatClient, type QQPrivateMessage, type QQRawMessage, type QQThreadId, createQQAdapter };
127
+ //# sourceMappingURL=index.d.cts.map
package/dist/index.d.mts CHANGED
@@ -1,4 +1,127 @@
1
- //#region src/index.d.ts
2
- declare function hello(): string;
1
+ import { Adapter, AdapterPostableMessage, ChatInstance, EmojiValue, FetchOptions, FetchResult, FormattedContent, Logger, Message, RawMessage, ThreadInfo, WebhookOptions } from "chat";
2
+ import { MessageHandler, NCWebsocket, NCWebsocketOptions } from "node-napcat-ts";
3
+
4
+ //#region src/types.d.ts
5
+ /**
6
+ * QQ 会话类型。
7
+ * - `group`: 群会话
8
+ * - `private`: 私聊会话
9
+ */
10
+ type QQChatType = 'group' | 'private';
11
+ /**
12
+ * QQ thread ID 的解码结构。
13
+ * 对应编码格式:`qq:{chatType}:{peerId}`。
14
+ */
15
+ interface QQThreadId {
16
+ /** 会话类型(群/私聊) */
17
+ chatType: QQChatType;
18
+ /** 对端 ID:群号或用户 QQ 号(字符串形式) */
19
+ peerId: string;
20
+ }
21
+ /** NapCat WebSocket 客户端类型别名。 */
22
+ type QQNapcatClient = NCWebsocket;
23
+ /** QQ 适配器统一使用的原始消息类型(message union)。 */
24
+ type QQRawMessage = MessageHandler['message'];
25
+ /** QQ 群消息原始类型。 */
26
+ type QQGroupMessage = MessageHandler['message.group'];
27
+ /** QQ 私聊消息原始类型。 */
28
+ type QQPrivateMessage = MessageHandler['message.private'];
29
+ /** QQ 适配器配置。 */
30
+ interface QQAdapterConfig {
31
+ /** NapCat 连接配置(必填)。 */
32
+ napcat: NCWebsocketOptions;
33
+ /** 是否启用 NapCat SDK 的 debug 输出。 */
34
+ debug?: boolean;
35
+ /** 自定义 logger;未传时使用 Chat SDK 提供的 logger。 */
36
+ logger?: Logger;
37
+ }
3
38
  //#endregion
4
- export { hello };
39
+ //#region src/adapter.d.ts
40
+ /**
41
+ * Chat SDK QQ 平台适配器(基于 NapCat WebSocket)。
42
+ *
43
+ * 设计说明:
44
+ * - 入口仅支持 WS 事件推送,不支持 HTTP webhook。
45
+ * - thread 模型采用“会话即 thread”:
46
+ * - 群:`qq:group:{group_id}`
47
+ * - 私聊:`qq:private:{user_id}`
48
+ */
49
+ declare class QQAdapter implements Adapter<QQThreadId, QQRawMessage> {
50
+ /** 适配器名称,作为 Chat SDK 的 adapter key。 */
51
+ readonly name = "qq";
52
+ /** 机器人用户名(初始化后由登录信息填充)。 */
53
+ userName: string;
54
+ /** 机器人用户标识(初始化后由登录信息填充)。 */
55
+ botUserId?: string;
56
+ private readonly config;
57
+ private readonly converter;
58
+ private chat;
59
+ private client?;
60
+ private selfId?;
61
+ private logger;
62
+ private listenersBound;
63
+ private initializing?;
64
+ /** 创建 QQ 适配器实例(不发起连接)。 */
65
+ constructor(config: QQAdapterConfig);
66
+ /**
67
+ * 初始化适配器,建立 NapCat 连接并注册消息监听。
68
+ * 该方法具有幂等性,多次调用会复用同一初始化流程。
69
+ */
70
+ initialize(chat: ChatInstance): Promise<void>;
71
+ private doInitialize;
72
+ /** QQ 适配器为 WS-only 模式,HTTP webhook 入口固定返回 501。 */
73
+ handleWebhook(_request: Request, _options?: WebhookOptions): Promise<Response>;
74
+ /** 编码 thread ID:`qq:{chatType}:{peerId}`。 */
75
+ encodeThreadId(data: QQThreadId): string;
76
+ /** 解码 thread ID 并进行格式校验。 */
77
+ decodeThreadId(threadId: string): QQThreadId;
78
+ /** 当前模型下 channelId 与 threadId 一致。 */
79
+ channelIdFromThreadId(threadId: string): string;
80
+ /** 判断当前 thread 是否为私聊会话。 */
81
+ isDM(threadId: string): boolean;
82
+ /** 向群或私聊发送消息。 */
83
+ postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage<QQRawMessage>>;
84
+ /** QQ 暂不支持编辑消息。 */
85
+ editMessage(_threadId: string, _messageId: string, _message: AdapterPostableMessage): Promise<RawMessage<QQRawMessage>>;
86
+ /** 删除指定消息。 */
87
+ deleteMessage(_threadId: string, messageId: string): Promise<void>;
88
+ /** QQ 暂不支持反应能力(添加)。 */
89
+ addReaction(_threadId: string, _messageId: string, _emoji: EmojiValue | string): Promise<void>;
90
+ /** QQ 暂不支持反应能力(移除)。 */
91
+ removeReaction(_threadId: string, _messageId: string, _emoji: EmojiValue | string): Promise<void>;
92
+ /** QQ 暂不支持历史消息分页拉取。 */
93
+ fetchMessages(_threadId: string, _options?: FetchOptions): Promise<FetchResult<QQRawMessage>>;
94
+ /** 获取 thread 基础信息。 */
95
+ fetchThread(threadId: string): Promise<ThreadInfo>;
96
+ /** 当前实现不发送真实 typing,仅作为 no-op。 */
97
+ startTyping(_threadId: string, _status?: string): Promise<void>;
98
+ /** 渲染格式化内容到 QQ 文本。 */
99
+ renderFormatted(content: FormattedContent): string;
100
+ /** 将 NapCat 原始消息转换为 Chat SDK 标准 Message。 */
101
+ parseMessage(raw: QQRawMessage): Message<QQRawMessage>;
102
+ private bindListeners;
103
+ private readonly onGroupMessage;
104
+ private readonly onPrivateMessage;
105
+ /**
106
+ * 统一处理入站消息:
107
+ * - 过滤 bot 自己发送的消息
108
+ * - 计算 threadId 与 mention
109
+ * - 交给 Chat SDK `processMessage` 进入标准事件流
110
+ */
111
+ private dispatchIncomingMessage;
112
+ /** 获取已初始化的 NapCat 客户端,否则抛出配置错误。 */
113
+ private requireClient;
114
+ }
115
+ //#endregion
116
+ //#region src/factory.d.ts
117
+ /**
118
+ * 创建 QQ 适配器实例。
119
+ * 仅支持显式配置,不读取环境变量。
120
+ *
121
+ * @param config QQ 适配器配置
122
+ * @returns 已创建但未初始化的 QQAdapter 实例
123
+ */
124
+ declare function createQQAdapter(config: QQAdapterConfig): QQAdapter;
125
+ //#endregion
126
+ export { QQAdapter, type QQAdapterConfig, type QQChatType, type QQGroupMessage, type QQNapcatClient, type QQPrivateMessage, type QQRawMessage, type QQThreadId, createQQAdapter };
127
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,6 +1,328 @@
1
- //#region src/index.ts
2
- function hello() {
3
- return "world";
1
+ import { BaseFormatConverter, Message, NotImplementedError, parseMarkdown, stringifyMarkdown, toPlainText } from "chat";
2
+ import { ValidationError } from "@chat-adapter/shared";
3
+ import { NCWebsocket, Structs } from "node-napcat-ts";
4
+ //#region src/format-converter.ts
5
+ /**
6
+ * @todo
7
+ */
8
+ var QQFormatConverter = class extends BaseFormatConverter {
9
+ toAst(platformText) {
10
+ return parseMarkdown(platformText);
11
+ }
12
+ fromAst(ast) {
13
+ return stringifyMarkdown(ast);
14
+ }
15
+ };
16
+ //#endregion
17
+ //#region src/utils.ts
18
+ /** 将字符串 ID 转为正整数,失败时抛出 ValidationError。 */
19
+ function toNumberId(value, fieldName) {
20
+ const num = Number(value);
21
+ if (!Number.isInteger(num) || num <= 0) throw new ValidationError("qq", `QQ ${fieldName} must be a positive integer: ${value}`);
22
+ return num;
23
+ }
24
+ /** 判断消息是否由 bot 自己发送(用于避免回环处理)。 */
25
+ function isSelfMessage(raw, selfId) {
26
+ if (!selfId) return false;
27
+ return String(raw.user_id) === selfId;
28
+ }
29
+ /** 判断消息是否命中 mention 语义。 */
30
+ function isMention(raw, selfId) {
31
+ if (raw.message_type === "private") return true;
32
+ if (!selfId) return false;
33
+ return raw.message.some((segment) => segment.type === "at" && segment.data.qq !== "all" && String(segment.data.qq) === selfId);
34
+ }
35
+ /** 将 QQ 原始消息映射为统一的 QQ thread ID 结构。 */
36
+ function toThreadId(raw) {
37
+ if (raw.message_type === "group") return {
38
+ chatType: "group",
39
+ peerId: String(raw.group_id)
40
+ };
41
+ return {
42
+ chatType: "private",
43
+ peerId: String(raw.user_id)
44
+ };
45
+ }
46
+ /** 将 QQ 原始作者信息映射为 Chat SDK Author。 */
47
+ function toAuthor(raw, isMe) {
48
+ return {
49
+ userId: String(raw.user_id),
50
+ userName: String(raw.user_id),
51
+ fullName: raw.sender.card || raw.sender.nickname || String(raw.user_id),
52
+ isBot: isMe,
53
+ isMe
54
+ };
55
+ }
56
+ /** 从 NapCat segment 列表提取纯文本;必要时回退 raw_message。 */
57
+ function extractText(raw) {
58
+ const text = raw.message.map(segmentToText).join("");
59
+ if (text.length > 0) return text;
60
+ return raw.raw_message ? toPlainText(parseMarkdown(raw.raw_message)) : "";
61
+ }
62
+ /** 将 NapCat segment 中可识别的媒体映射为 Chat SDK attachments。 */
63
+ function toAttachments(message) {
64
+ return message.map((segment) => {
65
+ if (segment.type === "image") return {
66
+ type: "image",
67
+ url: "url" in segment.data ? segment.data.url : void 0
68
+ };
69
+ if (segment.type === "file") return {
70
+ type: "file",
71
+ name: segment.data.file
72
+ };
73
+ if (segment.type === "video") return {
74
+ type: "video",
75
+ url: segment.data.url,
76
+ name: segment.data.file
77
+ };
78
+ if (segment.type === "record") return {
79
+ type: "audio",
80
+ name: segment.data.file
81
+ };
82
+ return null;
83
+ }).filter((attachment) => attachment !== null);
84
+ }
85
+ /** 将单个 NapCat segment 转为可读文本片段。 */
86
+ function segmentToText(segment) {
87
+ if (segment.type === "text") return segment.data.text;
88
+ if (segment.type === "at") return segment.data.qq === "all" ? "@all" : `@${segment.data.qq}`;
89
+ if (segment.type === "face") return `[face:${segment.data.id}]`;
90
+ if (segment.type === "image") return "[image]";
91
+ if (segment.type === "file") return `[file:${segment.data.file}]`;
92
+ if (segment.type === "record") return "[audio]";
93
+ if (segment.type === "video") return "[video]";
94
+ if (segment.type === "markdown") return segment.data.content;
95
+ return "";
96
+ }
97
+ //#endregion
98
+ //#region src/adapter.ts
99
+ /**
100
+ * Chat SDK QQ 平台适配器(基于 NapCat WebSocket)。
101
+ *
102
+ * 设计说明:
103
+ * - 入口仅支持 WS 事件推送,不支持 HTTP webhook。
104
+ * - thread 模型采用“会话即 thread”:
105
+ * - 群:`qq:group:{group_id}`
106
+ * - 私聊:`qq:private:{user_id}`
107
+ */
108
+ var QQAdapter = class {
109
+ /** 适配器名称,作为 Chat SDK 的 adapter key。 */
110
+ name = "qq";
111
+ /** 机器人用户名(初始化后由登录信息填充)。 */
112
+ userName;
113
+ /** 机器人用户标识(初始化后由登录信息填充)。 */
114
+ botUserId;
115
+ config;
116
+ converter = new QQFormatConverter();
117
+ chat = null;
118
+ client;
119
+ selfId;
120
+ logger;
121
+ listenersBound = false;
122
+ initializing;
123
+ /** 创建 QQ 适配器实例(不发起连接)。 */
124
+ constructor(config) {
125
+ this.config = config;
126
+ this.userName = "";
127
+ this.logger = config.logger ?? {
128
+ child: () => this.logger,
129
+ debug: () => {},
130
+ info: () => {},
131
+ warn: () => {},
132
+ error: () => {}
133
+ };
134
+ }
135
+ /**
136
+ * 初始化适配器,建立 NapCat 连接并注册消息监听。
137
+ * 该方法具有幂等性,多次调用会复用同一初始化流程。
138
+ */
139
+ async initialize(chat) {
140
+ if (this.initializing) return this.initializing;
141
+ this.initializing = this.doInitialize(chat).catch((error) => {
142
+ this.initializing = void 0;
143
+ throw error;
144
+ });
145
+ return this.initializing;
146
+ }
147
+ async doInitialize(chat) {
148
+ this.chat = chat;
149
+ this.logger = this.config.logger ?? chat.getLogger(this.name);
150
+ if (!this.client) this.client = new NCWebsocket(this.config.napcat, this.config.debug ?? false);
151
+ this.bindListeners();
152
+ await this.client.connect();
153
+ const login = await this.client.get_login_info();
154
+ this.logger.info("login with", login);
155
+ this.selfId = String(login.user_id);
156
+ this.botUserId = login.nickname;
157
+ this.userName = login.nickname;
158
+ }
159
+ /** QQ 适配器为 WS-only 模式,HTTP webhook 入口固定返回 501。 */
160
+ async handleWebhook(_request, _options) {
161
+ return new Response("QQ adapter uses NapCat WebSocket ingress only.", { status: 501 });
162
+ }
163
+ /** 编码 thread ID:`qq:{chatType}:{peerId}`。 */
164
+ encodeThreadId(data) {
165
+ if (data.chatType !== "group" && data.chatType !== "private") throw new ValidationError("qq", `Unsupported QQ chat type: ${data.chatType}`);
166
+ if (!data.peerId) throw new ValidationError("qq", "QQ thread peerId is required");
167
+ return `qq:${data.chatType}:${data.peerId}`;
168
+ }
169
+ /** 解码 thread ID 并进行格式校验。 */
170
+ decodeThreadId(threadId) {
171
+ const parts = threadId.split(":");
172
+ if (parts.length !== 3 || parts[0] !== "qq") throw new ValidationError("qq", `Invalid QQ thread ID: ${threadId}`);
173
+ const chatType = parts[1];
174
+ if (chatType !== "group" && chatType !== "private") throw new ValidationError("qq", `Invalid QQ thread type in thread ID: ${threadId}`);
175
+ const peerId = parts[2];
176
+ if (!peerId) throw new ValidationError("qq", `Invalid QQ thread peer ID in thread ID: ${threadId}`);
177
+ return {
178
+ chatType,
179
+ peerId
180
+ };
181
+ }
182
+ /** 当前模型下 channelId 与 threadId 一致。 */
183
+ channelIdFromThreadId(threadId) {
184
+ this.decodeThreadId(threadId);
185
+ return threadId;
186
+ }
187
+ /** 判断当前 thread 是否为私聊会话。 */
188
+ isDM(threadId) {
189
+ return this.decodeThreadId(threadId).chatType === "private";
190
+ }
191
+ /** 向群或私聊发送消息。 */
192
+ async postMessage(threadId, message) {
193
+ const client = this.requireClient();
194
+ const parsed = this.decodeThreadId(threadId);
195
+ const peerId = toNumberId(parsed.peerId, "peerId");
196
+ const text = this.converter.renderPostable(message);
197
+ const outgoingText = text.length > 0 ? text : " ";
198
+ const segments = [Structs.text(outgoingText)];
199
+ this.logger.debug("post message", parsed.chatType, parsed.peerId, segments);
200
+ const sent = parsed.chatType === "group" ? await client.send_group_msg({
201
+ group_id: peerId,
202
+ message: segments
203
+ }) : await client.send_private_msg({
204
+ user_id: peerId,
205
+ message: segments
206
+ });
207
+ return {
208
+ id: String(sent.message_id),
209
+ raw: {
210
+ ...sent,
211
+ threadId,
212
+ chatType: parsed.chatType
213
+ },
214
+ threadId
215
+ };
216
+ }
217
+ /** QQ 暂不支持编辑消息。 */
218
+ async editMessage(_threadId, _messageId, _message) {
219
+ throw new NotImplementedError("QQ adapter does not support editMessage yet", "editMessage");
220
+ }
221
+ /** 删除指定消息。 */
222
+ async deleteMessage(_threadId, messageId) {
223
+ await this.requireClient().delete_msg({ message_id: toNumberId(messageId, "messageId") });
224
+ }
225
+ /** QQ 暂不支持反应能力(添加)。 */
226
+ async addReaction(_threadId, _messageId, _emoji) {
227
+ throw new NotImplementedError("QQ adapter does not support addReaction yet", "addReaction");
228
+ }
229
+ /** QQ 暂不支持反应能力(移除)。 */
230
+ async removeReaction(_threadId, _messageId, _emoji) {
231
+ throw new NotImplementedError("QQ adapter does not support removeReaction yet", "removeReaction");
232
+ }
233
+ /** QQ 暂不支持历史消息分页拉取。 */
234
+ async fetchMessages(_threadId, _options) {
235
+ throw new NotImplementedError("QQ adapter does not support fetchMessages yet", "fetchMessages");
236
+ }
237
+ /** 获取 thread 基础信息。 */
238
+ async fetchThread(threadId) {
239
+ const parsed = this.decodeThreadId(threadId);
240
+ return {
241
+ id: threadId,
242
+ channelId: threadId,
243
+ isDM: parsed.chatType === "private",
244
+ metadata: {
245
+ chatType: parsed.chatType,
246
+ peerId: parsed.peerId
247
+ }
248
+ };
249
+ }
250
+ /** 当前实现不发送真实 typing,仅作为 no-op。 */
251
+ startTyping(_threadId, _status) {
252
+ return Promise.resolve();
253
+ }
254
+ /** 渲染格式化内容到 QQ 文本。 */
255
+ renderFormatted(content) {
256
+ return this.converter.fromAst(content);
257
+ }
258
+ /** 将 NapCat 原始消息转换为 Chat SDK 标准 Message。 */
259
+ parseMessage(raw) {
260
+ const threadId = this.encodeThreadId(toThreadId(raw));
261
+ const text = extractText(raw);
262
+ const isMe = this.selfId !== void 0 && String(raw.user_id) === this.selfId;
263
+ return new Message({
264
+ id: String(raw.message_id),
265
+ threadId,
266
+ text,
267
+ formatted: this.converter.toAst(text),
268
+ author: toAuthor(raw, isMe),
269
+ metadata: {
270
+ dateSent: /* @__PURE__ */ new Date(raw.time * 1e3),
271
+ edited: false
272
+ },
273
+ attachments: toAttachments(raw.message),
274
+ raw
275
+ });
276
+ }
277
+ bindListeners() {
278
+ if (!this.client || this.listenersBound) return;
279
+ this.client.on("message.group", this.onGroupMessage);
280
+ this.client.on("message.private", this.onPrivateMessage);
281
+ this.listenersBound = true;
282
+ }
283
+ onGroupMessage = (raw) => {
284
+ this.dispatchIncomingMessage(raw);
285
+ };
286
+ onPrivateMessage = (raw) => {
287
+ this.dispatchIncomingMessage(raw);
288
+ };
289
+ /**
290
+ * 统一处理入站消息:
291
+ * - 过滤 bot 自己发送的消息
292
+ * - 计算 threadId 与 mention
293
+ * - 交给 Chat SDK `processMessage` 进入标准事件流
294
+ */
295
+ dispatchIncomingMessage(raw) {
296
+ if (!this.chat) return;
297
+ if (isSelfMessage(raw, this.selfId)) return;
298
+ const threadId = this.encodeThreadId(toThreadId(raw));
299
+ const mention = isMention(raw, this.selfId);
300
+ this.chat.processMessage(this, threadId, async () => {
301
+ const message = this.parseMessage(raw);
302
+ message.isMention = mention;
303
+ return message;
304
+ });
305
+ }
306
+ /** 获取已初始化的 NapCat 客户端,否则抛出配置错误。 */
307
+ requireClient() {
308
+ if (!this.client) throw new ValidationError("qq", "QQ adapter is not initialized. Attach it to Chat and call chat.initialize() first.");
309
+ return this.client;
310
+ }
311
+ };
312
+ //#endregion
313
+ //#region src/factory.ts
314
+ /**
315
+ * 创建 QQ 适配器实例。
316
+ * 仅支持显式配置,不读取环境变量。
317
+ *
318
+ * @param config QQ 适配器配置
319
+ * @returns 已创建但未初始化的 QQAdapter 实例
320
+ */
321
+ function createQQAdapter(config) {
322
+ if (!config || !config.napcat) throw new ValidationError("qq", "QQ NapCat config is required. Pass { napcat: NCWebsocketOptions } to createQQAdapter(config).");
323
+ return new QQAdapter(config);
4
324
  }
5
325
  //#endregion
6
- export { hello };
326
+ export { QQAdapter, createQQAdapter };
327
+
328
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/format-converter.ts","../src/utils.ts","../src/adapter.ts","../src/factory.ts"],"sourcesContent":["import { BaseFormatConverter, parseMarkdown, stringifyMarkdown, type Root } from 'chat';\n\n/**\n * @todo\n */\nexport class QQFormatConverter extends BaseFormatConverter {\n toAst(platformText: string): Root {\n return parseMarkdown(platformText);\n }\n\n fromAst(ast: Root): string {\n return stringifyMarkdown(ast);\n }\n}\n","import type { Receive } from 'node-napcat-ts';\n\nimport { ValidationError } from '@chat-adapter/shared';\nimport { parseMarkdown, toPlainText, type Attachment, type Author } from 'chat';\n\nimport type { QQRawMessage, QQThreadId } from './types.js';\n\n/** NapCat 入站 message segment 的联合类型。 */\nexport type QQMessageSegment = Receive[keyof Receive];\n\n/** 将字符串 ID 转为正整数,失败时抛出 ValidationError。 */\nexport function toNumberId(value: string, fieldName: string): number {\n const num = Number(value);\n if (!Number.isInteger(num) || num <= 0) {\n throw new ValidationError('qq', `QQ ${fieldName} must be a positive integer: ${value}`);\n }\n return num;\n}\n\n/** 判断消息是否由 bot 自己发送(用于避免回环处理)。 */\nexport function isSelfMessage(raw: QQRawMessage, selfId?: string): boolean {\n if (!selfId) {\n return false;\n }\n\n return String(raw.user_id) === selfId;\n}\n\n/** 判断消息是否命中 mention 语义。 */\nexport function isMention(raw: QQRawMessage, selfId?: string): boolean {\n if (raw.message_type === 'private') {\n return true;\n }\n\n if (!selfId) {\n return false;\n }\n\n return raw.message.some(\n (segment) =>\n segment.type === 'at' && segment.data.qq !== 'all' && String(segment.data.qq) === selfId\n );\n}\n\n/** 将 QQ 原始消息映射为统一的 QQ thread ID 结构。 */\nexport function toThreadId(raw: QQRawMessage): QQThreadId {\n if (raw.message_type === 'group') {\n return {\n chatType: 'group',\n peerId: String(raw.group_id)\n };\n }\n\n return {\n chatType: 'private',\n peerId: String(raw.user_id)\n };\n}\n\n/** 将 QQ 原始作者信息映射为 Chat SDK Author。 */\nexport function toAuthor(raw: QQRawMessage, isMe: boolean): Author {\n return {\n userId: String(raw.user_id),\n userName: String(raw.user_id),\n fullName: raw.sender.card || raw.sender.nickname || String(raw.user_id),\n isBot: isMe,\n isMe\n };\n}\n\n/** 从 NapCat segment 列表提取纯文本;必要时回退 raw_message。 */\nexport function extractText(raw: QQRawMessage): string {\n const text = raw.message.map(segmentToText).join('');\n if (text.length > 0) {\n return text;\n }\n\n return raw.raw_message ? toPlainText(parseMarkdown(raw.raw_message)) : '';\n}\n\n/** 将 NapCat segment 中可识别的媒体映射为 Chat SDK attachments。 */\nexport function toAttachments(message: QQMessageSegment[]): Attachment[] {\n return message\n .map((segment) => {\n if (segment.type === 'image') {\n return {\n type: 'image' as const,\n url: 'url' in segment.data ? segment.data.url : undefined\n };\n }\n\n if (segment.type === 'file') {\n return {\n type: 'file' as const,\n name: segment.data.file\n };\n }\n\n if (segment.type === 'video') {\n return {\n type: 'video' as const,\n url: segment.data.url,\n name: segment.data.file\n };\n }\n\n if (segment.type === 'record') {\n return {\n type: 'audio' as const,\n name: segment.data.file\n };\n }\n\n return null;\n })\n .filter((attachment): attachment is NonNullable<typeof attachment> => attachment !== null);\n}\n\n/** 将单个 NapCat segment 转为可读文本片段。 */\nexport function segmentToText(segment: QQMessageSegment): string {\n if (segment.type === 'text') {\n return segment.data.text;\n }\n\n if (segment.type === 'at') {\n return segment.data.qq === 'all' ? '@all' : `@${segment.data.qq}`;\n }\n\n if (segment.type === 'face') {\n return `[face:${segment.data.id}]`;\n }\n\n if (segment.type === 'image') {\n return '[image]';\n }\n\n if (segment.type === 'file') {\n return `[file:${segment.data.file}]`;\n }\n\n if (segment.type === 'record') {\n return '[audio]';\n }\n\n if (segment.type === 'video') {\n return '[video]';\n }\n\n if (segment.type === 'markdown') {\n return segment.data.content;\n }\n\n return '';\n}\n","import {\n type Adapter,\n type AdapterPostableMessage,\n type ChatInstance,\n type EmojiValue,\n type FetchOptions,\n type FetchResult,\n type FormattedContent,\n type Logger,\n type RawMessage,\n type ThreadInfo,\n type WebhookOptions,\n NotImplementedError,\n Message\n} from 'chat';\nimport { ValidationError } from '@chat-adapter/shared';\nimport { NCWebsocket, Structs, type SendMessageSegment } from 'node-napcat-ts';\n\nimport type {\n QQAdapterConfig,\n QQGroupMessage,\n QQNapcatClient,\n QQPrivateMessage,\n QQRawMessage,\n QQThreadId\n} from './types.js';\n\nimport { QQFormatConverter } from './format-converter.js';\nimport {\n extractText,\n isMention,\n isSelfMessage,\n toAttachments,\n toAuthor,\n toNumberId,\n toThreadId\n} from './utils.js';\n\n/**\n * Chat SDK QQ 平台适配器(基于 NapCat WebSocket)。\n *\n * 设计说明:\n * - 入口仅支持 WS 事件推送,不支持 HTTP webhook。\n * - thread 模型采用“会话即 thread”:\n * - 群:`qq:group:{group_id}`\n * - 私聊:`qq:private:{user_id}`\n */\nexport class QQAdapter implements Adapter<QQThreadId, QQRawMessage> {\n /** 适配器名称,作为 Chat SDK 的 adapter key。 */\n readonly name = 'qq';\n\n /** 机器人用户名(初始化后由登录信息填充)。 */\n userName: string;\n\n /** 机器人用户标识(初始化后由登录信息填充)。 */\n botUserId?: string;\n\n // ---\n\n private readonly config: QQAdapterConfig;\n\n private readonly converter = new QQFormatConverter();\n\n private chat: ChatInstance | null = null;\n\n private client?: QQNapcatClient;\n\n private selfId?: string;\n\n private logger: Logger;\n\n private listenersBound = false;\n\n private initializing?: Promise<void>;\n\n /** 创建 QQ 适配器实例(不发起连接)。 */\n public constructor(config: QQAdapterConfig) {\n this.config = config;\n this.userName = '';\n this.logger = config.logger ?? {\n child: () => this.logger,\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {}\n };\n }\n\n /**\n * 初始化适配器,建立 NapCat 连接并注册消息监听。\n * 该方法具有幂等性,多次调用会复用同一初始化流程。\n */\n public async initialize(chat: ChatInstance): Promise<void> {\n if (this.initializing) {\n return this.initializing;\n }\n\n this.initializing = this.doInitialize(chat).catch((error) => {\n this.initializing = undefined;\n throw error;\n });\n\n return this.initializing;\n }\n\n private async doInitialize(chat: ChatInstance): Promise<void> {\n this.chat = chat;\n this.logger = this.config.logger ?? chat.getLogger(this.name);\n\n if (!this.client) {\n this.client = new NCWebsocket(this.config.napcat, this.config.debug ?? false);\n }\n\n this.bindListeners();\n\n await this.client.connect();\n\n const login = await this.client.get_login_info();\n this.logger.info('login with', login);\n\n this.selfId = String(login.user_id);\n this.botUserId = login.nickname;\n this.userName = login.nickname;\n }\n\n /** QQ 适配器为 WS-only 模式,HTTP webhook 入口固定返回 501。 */\n public async handleWebhook(_request: Request, _options?: WebhookOptions): Promise<Response> {\n return new Response('QQ adapter uses NapCat WebSocket ingress only.', {\n status: 501\n });\n }\n\n /** 编码 thread ID:`qq:{chatType}:{peerId}`。 */\n public encodeThreadId(data: QQThreadId): string {\n if (data.chatType !== 'group' && data.chatType !== 'private') {\n throw new ValidationError('qq', `Unsupported QQ chat type: ${data.chatType}`);\n }\n\n if (!data.peerId) {\n throw new ValidationError('qq', 'QQ thread peerId is required');\n }\n\n return `qq:${data.chatType}:${data.peerId}`;\n }\n\n /** 解码 thread ID 并进行格式校验。 */\n public decodeThreadId(threadId: string): QQThreadId {\n const parts = threadId.split(':');\n if (parts.length !== 3 || parts[0] !== 'qq') {\n throw new ValidationError('qq', `Invalid QQ thread ID: ${threadId}`);\n }\n\n const chatType = parts[1];\n if (chatType !== 'group' && chatType !== 'private') {\n throw new ValidationError('qq', `Invalid QQ thread type in thread ID: ${threadId}`);\n }\n\n const peerId = parts[2];\n if (!peerId) {\n throw new ValidationError('qq', `Invalid QQ thread peer ID in thread ID: ${threadId}`);\n }\n\n return {\n chatType,\n peerId\n };\n }\n\n /** 当前模型下 channelId 与 threadId 一致。 */\n public channelIdFromThreadId(threadId: string): string {\n this.decodeThreadId(threadId);\n return threadId;\n }\n\n /** 判断当前 thread 是否为私聊会话。 */\n public isDM(threadId: string): boolean {\n return this.decodeThreadId(threadId).chatType === 'private';\n }\n\n /** 向群或私聊发送消息。 */\n public async postMessage(\n threadId: string,\n message: AdapterPostableMessage\n ): Promise<RawMessage<QQRawMessage>> {\n const client = this.requireClient();\n const parsed = this.decodeThreadId(threadId);\n const peerId = toNumberId(parsed.peerId, 'peerId');\n const text = this.converter.renderPostable(message);\n const outgoingText = text.length > 0 ? text : ' ';\n const segments: SendMessageSegment[] = [Structs.text(outgoingText)];\n\n this.logger.debug('post message', parsed.chatType, parsed.peerId, segments);\n\n const sent =\n parsed.chatType === 'group'\n ? await client.send_group_msg({\n group_id: peerId,\n message: segments\n })\n : await client.send_private_msg({\n user_id: peerId,\n message: segments\n });\n\n return {\n id: String(sent.message_id),\n raw: {\n ...sent,\n threadId,\n chatType: parsed.chatType\n } as unknown as QQRawMessage,\n threadId\n };\n }\n\n /** QQ 暂不支持编辑消息。 */\n public async editMessage(\n _threadId: string,\n _messageId: string,\n _message: AdapterPostableMessage\n ): Promise<RawMessage<QQRawMessage>> {\n throw new NotImplementedError('QQ adapter does not support editMessage yet', 'editMessage');\n }\n\n /** 删除指定消息。 */\n public async deleteMessage(_threadId: string, messageId: string): Promise<void> {\n const client = this.requireClient();\n await client.delete_msg({ message_id: toNumberId(messageId, 'messageId') });\n }\n\n /** QQ 暂不支持反应能力(添加)。 */\n public async addReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string\n ): Promise<void> {\n throw new NotImplementedError('QQ adapter does not support addReaction yet', 'addReaction');\n }\n\n /** QQ 暂不支持反应能力(移除)。 */\n public async removeReaction(\n _threadId: string,\n _messageId: string,\n _emoji: EmojiValue | string\n ): Promise<void> {\n throw new NotImplementedError(\n 'QQ adapter does not support removeReaction yet',\n 'removeReaction'\n );\n }\n\n /** QQ 暂不支持历史消息分页拉取。 */\n public async fetchMessages(\n _threadId: string,\n _options?: FetchOptions\n ): Promise<FetchResult<QQRawMessage>> {\n throw new NotImplementedError('QQ adapter does not support fetchMessages yet', 'fetchMessages');\n }\n\n /** 获取 thread 基础信息。 */\n public async fetchThread(threadId: string): Promise<ThreadInfo> {\n const parsed = this.decodeThreadId(threadId);\n\n return {\n id: threadId,\n channelId: threadId,\n isDM: parsed.chatType === 'private',\n metadata: {\n chatType: parsed.chatType,\n peerId: parsed.peerId\n }\n };\n }\n\n /** 当前实现不发送真实 typing,仅作为 no-op。 */\n public startTyping(_threadId: string, _status?: string): Promise<void> {\n return Promise.resolve();\n }\n\n /** 渲染格式化内容到 QQ 文本。 */\n public renderFormatted(content: FormattedContent): string {\n return this.converter.fromAst(content);\n }\n\n /** 将 NapCat 原始消息转换为 Chat SDK 标准 Message。 */\n public parseMessage(raw: QQRawMessage): Message<QQRawMessage> {\n const threadId = this.encodeThreadId(toThreadId(raw));\n const text = extractText(raw);\n const isMe = this.selfId !== undefined && String(raw.user_id) === this.selfId;\n\n return new Message<QQRawMessage>({\n id: String(raw.message_id),\n threadId,\n text,\n formatted: this.converter.toAst(text),\n author: toAuthor(raw, isMe),\n metadata: {\n dateSent: new Date(raw.time * 1000),\n edited: false\n },\n attachments: toAttachments(raw.message),\n raw\n });\n }\n\n private bindListeners(): void {\n if (!this.client || this.listenersBound) {\n return;\n }\n\n this.client.on('message.group', this.onGroupMessage);\n this.client.on('message.private', this.onPrivateMessage);\n this.listenersBound = true;\n }\n\n private readonly onGroupMessage = (raw: QQGroupMessage): void => {\n this.dispatchIncomingMessage(raw);\n };\n\n private readonly onPrivateMessage = (raw: QQPrivateMessage): void => {\n this.dispatchIncomingMessage(raw);\n };\n\n /**\n * 统一处理入站消息:\n * - 过滤 bot 自己发送的消息\n * - 计算 threadId 与 mention\n * - 交给 Chat SDK `processMessage` 进入标准事件流\n */\n private dispatchIncomingMessage(raw: QQRawMessage): void {\n if (!this.chat) {\n return;\n }\n\n if (isSelfMessage(raw, this.selfId)) {\n return;\n }\n\n const threadId = this.encodeThreadId(toThreadId(raw));\n const mention = isMention(raw, this.selfId);\n\n this.chat.processMessage(this, threadId, async () => {\n const message = this.parseMessage(raw);\n message.isMention = mention;\n return message;\n });\n }\n\n /** 获取已初始化的 NapCat 客户端,否则抛出配置错误。 */\n private requireClient(): QQNapcatClient {\n if (!this.client) {\n throw new ValidationError(\n 'qq',\n 'QQ adapter is not initialized. Attach it to Chat and call chat.initialize() first.'\n );\n }\n\n return this.client;\n }\n}\n","import { ValidationError } from '@chat-adapter/shared';\n\nimport type { QQAdapterConfig } from './types.js';\n\nimport { QQAdapter } from './adapter.js';\n\n/**\n * 创建 QQ 适配器实例。\n * 仅支持显式配置,不读取环境变量。\n *\n * @param config QQ 适配器配置\n * @returns 已创建但未初始化的 QQAdapter 实例\n */\nexport function createQQAdapter(config: QQAdapterConfig): QQAdapter {\n if (!config || !config.napcat) {\n throw new ValidationError(\n 'qq',\n 'QQ NapCat config is required. Pass { napcat: NCWebsocketOptions } to createQQAdapter(config).'\n );\n }\n\n return new QQAdapter(config);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chat-adapter-qq",
3
- "version": "0.0.0",
3
+ "version": "0.0.1",
4
4
  "description": "Community chat adapter for QQ based on NapCat",
5
5
  "keywords": [
6
6
  "qq",
@@ -8,7 +8,8 @@
8
8
  "onebot",
9
9
  "bot",
10
10
  "chat",
11
- "adapter"
11
+ "chat-sdk",
12
+ "chat-adapter"
12
13
  ],
13
14
  "homepage": "https://github.com/yjl9903/openana#readme",
14
15
  "bugs": {
@@ -37,10 +38,12 @@
37
38
  "dist"
38
39
  ],
39
40
  "dependencies": {
40
- "@chat-adapter/shared": "^4.0.0"
41
+ "@chat-adapter/shared": "^4.0.0",
42
+ "node-napcat-ts": "^0.4.21"
41
43
  },
42
44
  "devDependencies": {
43
- "chat": "^4.0.0"
45
+ "@chat-adapter/state-memory": "^4.18.0",
46
+ "chat": "^4.18.0"
44
47
  },
45
48
  "peerDependencies": {
46
49
  "chat": "^4.0.0"