@zhin.js/adapter-qq 7.0.1 → 7.0.3
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/CHANGELOG.md +24 -0
- package/lib/endpoint.d.ts +3 -9
- package/lib/endpoint.js +26 -32
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/protocol.d.ts +6 -7
- package/lib/protocol.js +17 -14
- package/package.json +15 -14
- package/src/endpoint.ts +28 -35
- package/src/index.ts +0 -3
- package/src/protocol.ts +17 -19
- /package/commands/endpoint/add/{[name:string=].js → [[name]].js} +0 -0
- /package/commands/endpoint/add/{[name:string=].ts → [[name]].ts} +0 -0
- /package/commands/endpoint/remove/{[name:string].js → [name].js} +0 -0
- /package/commands/endpoint/remove/{[name:string].ts → [name].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @zhin.js/adapter-qq
|
|
2
2
|
|
|
3
|
+
## 7.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [afc0e66]
|
|
8
|
+
- Updated dependencies [2e41ad5]
|
|
9
|
+
- Updated dependencies [9f57124]
|
|
10
|
+
- @zhin.js/core@1.5.2
|
|
11
|
+
- @zhin.js/adapter@1.1.5
|
|
12
|
+
- @zhin.js/im-contract@1.0.1
|
|
13
|
+
- @zhin.js/plugin-runtime@1.1.3
|
|
14
|
+
- @zhin.js/command@1.0.7
|
|
15
|
+
- @zhin.js/host-http@1.0.6
|
|
16
|
+
- @zhin.js/agent@1.1.3
|
|
17
|
+
- zhin.js@6.0.2
|
|
18
|
+
|
|
19
|
+
## 7.0.2
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Updated dependencies [696ab1b]
|
|
24
|
+
- @zhin.js/agent@1.1.2
|
|
25
|
+
- zhin.js@6.0.1
|
|
26
|
+
|
|
3
27
|
## 7.0.1
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* QQ endpoints — lifecycle, outbound, admit, agent tool surface.
|
|
3
3
|
*/
|
|
4
|
-
import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
4
|
+
import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
|
|
5
5
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
6
6
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
7
7
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
@@ -26,10 +26,7 @@ export declare class QqWebsocketEndpoint implements EndpointInstance {
|
|
|
26
26
|
open(): void;
|
|
27
27
|
close(): void;
|
|
28
28
|
stop(): Promise<void>;
|
|
29
|
-
send({
|
|
30
|
-
readonly target: string;
|
|
31
|
-
readonly payload: unknown;
|
|
32
|
-
}): Promise<string>;
|
|
29
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
33
30
|
recallMessage(messageId: string): Promise<void>;
|
|
34
31
|
/** Test / internal: admit a message when open. */
|
|
35
32
|
admit(msg: QqInboundMessage): void;
|
|
@@ -60,10 +57,7 @@ export declare class QqHttpEndpoint implements EndpointInstance {
|
|
|
60
57
|
open(): void;
|
|
61
58
|
close(): void;
|
|
62
59
|
stop(): Promise<void>;
|
|
63
|
-
send({
|
|
64
|
-
readonly target: string;
|
|
65
|
-
readonly payload: unknown;
|
|
66
|
-
}): Promise<string>;
|
|
60
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
67
61
|
recallMessage(messageId: string): Promise<void>;
|
|
68
62
|
admit(msg: QqInboundMessage): void;
|
|
69
63
|
getGuilds(): Promise<unknown[]>;
|
package/lib/endpoint.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
2
2
|
import { formatOutbound } from './outbound.js';
|
|
3
3
|
import { registerQqAgentEndpoint } from './qq-agent-deps.js';
|
|
4
|
-
import { formatInboundContent,
|
|
4
|
+
import { formatInboundContent, parseCompoundMessageId, qqInboundConversation, qqOutboundKind, resolveOutboundMessageId, senderDisplayName, } from './protocol.js';
|
|
5
5
|
import { defaultCreateHttpBot, registerQqWebhookRoutes, } from './webhook.js';
|
|
6
6
|
import { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
|
|
7
7
|
const logger = getLogger('qq');
|
|
@@ -67,34 +67,32 @@ export class QqWebsocketEndpoint {
|
|
|
67
67
|
this.#started = false;
|
|
68
68
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
69
69
|
}
|
|
70
|
-
async send({
|
|
70
|
+
async send({ conversation, payload }) {
|
|
71
71
|
const body = formatOutbound(payload);
|
|
72
|
-
const
|
|
72
|
+
const kind = qqOutboundKind(conversation);
|
|
73
73
|
const bot = this.#requireBot();
|
|
74
74
|
let result;
|
|
75
|
-
switch (
|
|
75
|
+
switch (kind) {
|
|
76
76
|
case 'private':
|
|
77
|
-
result = await bot.sendPrivateMessage(
|
|
77
|
+
result = await bot.sendPrivateMessage(conversation.id, body);
|
|
78
78
|
break;
|
|
79
79
|
case 'group':
|
|
80
|
-
result = await bot.sendGroupMessage(
|
|
80
|
+
result = await bot.sendGroupMessage(conversation.id, body);
|
|
81
81
|
break;
|
|
82
82
|
case 'channel':
|
|
83
|
-
result = await bot.sendGuildMessage(
|
|
83
|
+
result = await bot.sendGuildMessage(conversation.id, body);
|
|
84
84
|
break;
|
|
85
85
|
case 'direct':
|
|
86
86
|
if (!bot.sendDirectMessage)
|
|
87
87
|
throw new Error('QQ direct message not supported by transport');
|
|
88
|
-
result = await bot.sendDirectMessage(
|
|
88
|
+
result = await bot.sendDirectMessage(conversation.id, body);
|
|
89
89
|
break;
|
|
90
|
-
default:
|
|
91
|
-
throw new Error(`unsupported QQ target kind: ${String(parsed.kind)}`);
|
|
92
90
|
}
|
|
93
|
-
const messageId = `${
|
|
91
|
+
const messageId = `${kind}-${conversation.id}:${resolveOutboundMessageId(result)}`;
|
|
94
92
|
logger.debug(formatCompact({
|
|
95
93
|
op: 'qq_send',
|
|
96
94
|
endpoint: this.#options.config.name,
|
|
97
|
-
target
|
|
95
|
+
target: `${kind}:${conversation.id}`,
|
|
98
96
|
messageId,
|
|
99
97
|
}));
|
|
100
98
|
return messageId;
|
|
@@ -127,14 +125,13 @@ export class QqWebsocketEndpoint {
|
|
|
127
125
|
admit(msg) {
|
|
128
126
|
if (!this.#open)
|
|
129
127
|
return;
|
|
130
|
-
const
|
|
128
|
+
const conversation = qqInboundConversation(String(this.#options.id), msg);
|
|
131
129
|
void this.#options.gateway.receive({
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
conversation,
|
|
131
|
+
message: { conversation, id: msg.id },
|
|
134
132
|
content: formatInboundContent(msg),
|
|
135
133
|
...(msg.segments?.length ? { segments: msg.segments } : {}),
|
|
136
134
|
sender: senderDisplayName(msg),
|
|
137
|
-
id: msg.id,
|
|
138
135
|
metadata: Object.freeze({
|
|
139
136
|
endpoint: this.#options.config.name,
|
|
140
137
|
channelKind: msg.channelKind,
|
|
@@ -147,7 +144,7 @@ export class QqWebsocketEndpoint {
|
|
|
147
144
|
}).catch((err) => {
|
|
148
145
|
logger.warn(formatCompact({
|
|
149
146
|
op: 'qq_gateway_receive_failed',
|
|
150
|
-
target
|
|
147
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
151
148
|
error: err instanceof Error ? err.message : String(err),
|
|
152
149
|
}));
|
|
153
150
|
});
|
|
@@ -255,30 +252,28 @@ export class QqHttpEndpoint {
|
|
|
255
252
|
this.#started = false;
|
|
256
253
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
257
254
|
}
|
|
258
|
-
async send({
|
|
255
|
+
async send({ conversation, payload }) {
|
|
259
256
|
const body = formatOutbound(payload);
|
|
260
|
-
const
|
|
257
|
+
const kind = qqOutboundKind(conversation);
|
|
261
258
|
const bot = this.#requireBot();
|
|
262
259
|
let result;
|
|
263
|
-
switch (
|
|
260
|
+
switch (kind) {
|
|
264
261
|
case 'private':
|
|
265
|
-
result = await bot.sendPrivateMessage(
|
|
262
|
+
result = await bot.sendPrivateMessage(conversation.id, body);
|
|
266
263
|
break;
|
|
267
264
|
case 'group':
|
|
268
|
-
result = await bot.sendGroupMessage(
|
|
265
|
+
result = await bot.sendGroupMessage(conversation.id, body);
|
|
269
266
|
break;
|
|
270
267
|
case 'channel':
|
|
271
|
-
result = await bot.sendGuildMessage(
|
|
268
|
+
result = await bot.sendGuildMessage(conversation.id, body);
|
|
272
269
|
break;
|
|
273
270
|
case 'direct':
|
|
274
271
|
if (!bot.sendDirectMessage)
|
|
275
272
|
throw new Error('QQ direct message not supported by transport');
|
|
276
|
-
result = await bot.sendDirectMessage(
|
|
273
|
+
result = await bot.sendDirectMessage(conversation.id, body);
|
|
277
274
|
break;
|
|
278
|
-
default:
|
|
279
|
-
throw new Error(`unsupported QQ target kind: ${String(parsed.kind)}`);
|
|
280
275
|
}
|
|
281
|
-
return `${
|
|
276
|
+
return `${kind}-${conversation.id}:${resolveOutboundMessageId(result)}`;
|
|
282
277
|
}
|
|
283
278
|
async recallMessage(messageId) {
|
|
284
279
|
const { kind, channelId, qqMsgId } = parseCompoundMessageId(messageId);
|
|
@@ -307,14 +302,13 @@ export class QqHttpEndpoint {
|
|
|
307
302
|
admit(msg) {
|
|
308
303
|
if (!this.#open)
|
|
309
304
|
return;
|
|
310
|
-
const
|
|
305
|
+
const conversation = qqInboundConversation(String(this.#options.id), msg);
|
|
311
306
|
void this.#options.gateway.receive({
|
|
312
|
-
|
|
313
|
-
|
|
307
|
+
conversation,
|
|
308
|
+
message: { conversation, id: msg.id },
|
|
314
309
|
content: formatInboundContent(msg),
|
|
315
310
|
...(msg.segments?.length ? { segments: msg.segments } : {}),
|
|
316
311
|
sender: senderDisplayName(msg),
|
|
317
|
-
id: msg.id,
|
|
318
312
|
metadata: Object.freeze({
|
|
319
313
|
endpoint: this.#options.config.name,
|
|
320
314
|
channelKind: msg.channelKind,
|
|
@@ -327,7 +321,7 @@ export class QqHttpEndpoint {
|
|
|
327
321
|
}).catch((err) => {
|
|
328
322
|
logger.warn(formatCompact({
|
|
329
323
|
op: 'qq_gateway_receive_failed',
|
|
330
|
-
target
|
|
324
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
331
325
|
error: err instanceof Error ? err.message : String(err),
|
|
332
326
|
}));
|
|
333
327
|
});
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { formatInboundContent,
|
|
1
|
+
export { formatInboundContent, formatOutboundText, resolveOutboundMessageId, resolveQqConfig, senderDisplayName, type QqAdapterConfig, type QqChannelKind, type QqInboundMessage, type QqWireSegment, type ResolvedQqConfig, type ResolvedQqHttpConfig, type ResolvedQqWebsocketConfig, } from './protocol.js';
|
|
2
2
|
export { formatOutbound, resolveMediaFile, type QqOutboundElem, type QqOutboundMessage, } from './outbound.js';
|
|
3
3
|
export { QqHttpEndpoint, QqWebsocketEndpoint, type CreateQqBot, type CreateQqHttpBot, type QqBotTransport, type QqEndpointOptions, type QqHttpBotTransport, type QqHttpEndpointOptions, } from './endpoint.js';
|
|
4
4
|
export { registerQqWebhookRoutes, handleQqWebhookRequest, readRequestBodyText, defaultCreateHttpBot, type QqWebhookHandler, } from './webhook.js';
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { formatInboundContent,
|
|
1
|
+
export { formatInboundContent, formatOutboundText, resolveOutboundMessageId, resolveQqConfig, senderDisplayName, } from './protocol.js';
|
|
2
2
|
export { formatOutbound, resolveMediaFile, } from './outbound.js';
|
|
3
3
|
export { QqHttpEndpoint, QqWebsocketEndpoint, } from './endpoint.js';
|
|
4
4
|
export { registerQqWebhookRoutes, handleQqWebhookRequest, readRequestBodyText, defaultCreateHttpBot, } from './webhook.js';
|
package/lib/protocol.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* QQ Official Bot protocol helpers — no legacy Adapter/Endpoint / segment-mapper.
|
|
3
3
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
*/
|
|
5
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
5
6
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
6
7
|
export interface QqAdapterConfig {
|
|
7
8
|
readonly name?: string;
|
|
@@ -79,16 +80,14 @@ export interface QqWireSegment {
|
|
|
79
80
|
readonly type: string;
|
|
80
81
|
readonly data?: Record<string, unknown>;
|
|
81
82
|
}
|
|
82
|
-
export interface ParsedSendTarget {
|
|
83
|
-
readonly kind: QqChannelKind;
|
|
84
|
-
readonly id: string;
|
|
85
|
-
}
|
|
86
83
|
export declare function resolveQqConfig(config?: QqAdapterConfig): ResolvedQqConfig;
|
|
87
84
|
/**
|
|
88
|
-
*
|
|
85
|
+
* 入站归一化 → ConversationRef:`direct`(频道私信)映射为 guild 容器内的
|
|
86
|
+
* private 会话;频道消息的所属 guild 进 `parent`。
|
|
89
87
|
*/
|
|
90
|
-
export declare function
|
|
91
|
-
|
|
88
|
+
export declare function qqInboundConversation(endpointId: string, msg: QqInboundMessage): ConversationRef;
|
|
89
|
+
/** 出站:guild 容器内的 private 会话即 QQ `direct`(频道私信)。 */
|
|
90
|
+
export declare function qqOutboundKind(conversation: ConversationRef): QqChannelKind;
|
|
92
91
|
export declare function senderDisplayName(msg: QqInboundMessage): string;
|
|
93
92
|
export declare function formatInboundContent(msg: QqInboundMessage): string;
|
|
94
93
|
/**
|
package/lib/protocol.js
CHANGED
|
@@ -43,22 +43,25 @@ export function resolveQqConfig(config = {}) {
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
46
|
+
* 入站归一化 → ConversationRef:`direct`(频道私信)映射为 guild 容器内的
|
|
47
|
+
* private 会话;频道消息的所属 guild 进 `parent`。
|
|
47
48
|
*/
|
|
48
|
-
export function
|
|
49
|
-
|
|
49
|
+
export function qqInboundConversation(endpointId, msg) {
|
|
50
|
+
const kind = msg.channelKind === 'direct' ? 'private' : msg.channelKind;
|
|
51
|
+
return {
|
|
52
|
+
endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
|
|
53
|
+
kind,
|
|
54
|
+
id: msg.channelId,
|
|
55
|
+
...(msg.guildId && (msg.channelKind === 'channel' || msg.channelKind === 'direct')
|
|
56
|
+
? { parent: { kind: 'channel', id: msg.guildId } }
|
|
57
|
+
: {}),
|
|
58
|
+
};
|
|
50
59
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
const head = target.slice(0, sep);
|
|
57
|
-
const rest = target.slice(sep + 1);
|
|
58
|
-
if (head === 'private' || head === 'group' || head === 'channel' || head === 'direct') {
|
|
59
|
-
return { kind: head, id: rest };
|
|
60
|
-
}
|
|
61
|
-
return { kind: 'private', id: target };
|
|
60
|
+
/** 出站:guild 容器内的 private 会话即 QQ `direct`(频道私信)。 */
|
|
61
|
+
export function qqOutboundKind(conversation) {
|
|
62
|
+
if (conversation.kind === 'private' && conversation.parent?.kind === 'channel')
|
|
63
|
+
return 'direct';
|
|
64
|
+
return conversation.kind;
|
|
62
65
|
}
|
|
63
66
|
export function senderDisplayName(msg) {
|
|
64
67
|
return msg.authorName || msg.authorId;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-qq",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.3",
|
|
4
4
|
"description": "Zhin.js QQ Official adapter for Plugin Runtime (WebSocket gateway)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -35,20 +35,21 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"qq-official-bot": "^1.2.3",
|
|
38
|
-
"@zhin.js/adapter": "1.1.
|
|
39
|
-
"@zhin.js/command": "1.0.
|
|
40
|
-
"@zhin.js/core": "1.5.
|
|
41
|
-
"@zhin.js/host-http": "1.0.
|
|
38
|
+
"@zhin.js/adapter": "1.1.5",
|
|
39
|
+
"@zhin.js/command": "1.0.7",
|
|
40
|
+
"@zhin.js/core": "1.5.2",
|
|
41
|
+
"@zhin.js/host-http": "1.0.6",
|
|
42
|
+
"@zhin.js/im-contract": "1.0.1",
|
|
42
43
|
"@zhin.js/logger": "1.0.75",
|
|
43
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
44
|
+
"@zhin.js/plugin-runtime": "1.1.3"
|
|
44
45
|
},
|
|
45
46
|
"peerDependencies": {
|
|
46
47
|
"zod": "^4.0.0",
|
|
47
|
-
"@zhin.js/adapter": "1.1.
|
|
48
|
-
"@zhin.js/agent": "1.1.
|
|
49
|
-
"@zhin.js/core": "1.5.
|
|
50
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
51
|
-
"zhin.js": "6.0.
|
|
48
|
+
"@zhin.js/adapter": "1.1.5",
|
|
49
|
+
"@zhin.js/agent": "1.1.3",
|
|
50
|
+
"@zhin.js/core": "1.5.2",
|
|
51
|
+
"@zhin.js/plugin-runtime": "1.1.3",
|
|
52
|
+
"zhin.js": "6.0.2"
|
|
52
53
|
},
|
|
53
54
|
"peerDependenciesMeta": {
|
|
54
55
|
"zhin.js": {
|
|
@@ -66,9 +67,9 @@
|
|
|
66
67
|
"typescript": "^6.0.3",
|
|
67
68
|
"vitest": "^4.1.10",
|
|
68
69
|
"zod": "^4.4.3",
|
|
69
|
-
"@zhin.js/agent": "1.1.
|
|
70
|
-
"@zhin.js/host-http": "1.0.
|
|
71
|
-
"zhin.js": "6.0.
|
|
70
|
+
"@zhin.js/agent": "1.1.3",
|
|
71
|
+
"@zhin.js/host-http": "1.0.6",
|
|
72
|
+
"zhin.js": "6.0.2"
|
|
72
73
|
},
|
|
73
74
|
"files": [
|
|
74
75
|
"adapters",
|
package/src/endpoint.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
EndpointChannel,
|
|
6
6
|
EndpointInstance,
|
|
7
7
|
EndpointManagement,
|
|
8
|
+
EndpointSendRequest,
|
|
8
9
|
} from '@zhin.js/adapter';
|
|
9
10
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
10
11
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
@@ -14,9 +15,9 @@ import { formatOutbound } from './outbound.js';
|
|
|
14
15
|
import { registerQqAgentEndpoint } from './qq-agent-deps.js';
|
|
15
16
|
import {
|
|
16
17
|
formatInboundContent,
|
|
17
|
-
formatInboundTarget,
|
|
18
18
|
parseCompoundMessageId,
|
|
19
|
-
|
|
19
|
+
qqInboundConversation,
|
|
20
|
+
qqOutboundKind,
|
|
20
21
|
resolveOutboundMessageId,
|
|
21
22
|
senderDisplayName,
|
|
22
23
|
type QqInboundMessage,
|
|
@@ -118,33 +119,31 @@ export class QqWebsocketEndpoint implements EndpointInstance {
|
|
|
118
119
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
async send({
|
|
122
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
122
123
|
const body = formatOutbound(payload);
|
|
123
|
-
const
|
|
124
|
+
const kind = qqOutboundKind(conversation);
|
|
124
125
|
const bot = this.#requireBot();
|
|
125
126
|
let result: unknown;
|
|
126
|
-
switch (
|
|
127
|
+
switch (kind) {
|
|
127
128
|
case 'private':
|
|
128
|
-
result = await bot.sendPrivateMessage(
|
|
129
|
+
result = await bot.sendPrivateMessage(conversation.id, body);
|
|
129
130
|
break;
|
|
130
131
|
case 'group':
|
|
131
|
-
result = await bot.sendGroupMessage(
|
|
132
|
+
result = await bot.sendGroupMessage(conversation.id, body);
|
|
132
133
|
break;
|
|
133
134
|
case 'channel':
|
|
134
|
-
result = await bot.sendGuildMessage(
|
|
135
|
+
result = await bot.sendGuildMessage(conversation.id, body);
|
|
135
136
|
break;
|
|
136
137
|
case 'direct':
|
|
137
138
|
if (!bot.sendDirectMessage) throw new Error('QQ direct message not supported by transport');
|
|
138
|
-
result = await bot.sendDirectMessage(
|
|
139
|
+
result = await bot.sendDirectMessage(conversation.id, body);
|
|
139
140
|
break;
|
|
140
|
-
default:
|
|
141
|
-
throw new Error(`unsupported QQ target kind: ${String((parsed as ParsedSendTarget).kind)}`);
|
|
142
141
|
}
|
|
143
|
-
const messageId = `${
|
|
142
|
+
const messageId = `${kind}-${conversation.id}:${resolveOutboundMessageId(result)}`;
|
|
144
143
|
logger.debug(formatCompact({
|
|
145
144
|
op: 'qq_send',
|
|
146
145
|
endpoint: this.#options.config.name,
|
|
147
|
-
target
|
|
146
|
+
target: `${kind}:${conversation.id}`,
|
|
148
147
|
messageId,
|
|
149
148
|
}));
|
|
150
149
|
return messageId;
|
|
@@ -178,14 +177,13 @@ export class QqWebsocketEndpoint implements EndpointInstance {
|
|
|
178
177
|
/** Test / internal: admit a message when open. */
|
|
179
178
|
admit(msg: QqInboundMessage): void {
|
|
180
179
|
if (!this.#open) return;
|
|
181
|
-
const
|
|
180
|
+
const conversation = qqInboundConversation(String(this.#options.id), msg);
|
|
182
181
|
void this.#options.gateway.receive({
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
conversation,
|
|
183
|
+
message: { conversation, id: msg.id },
|
|
185
184
|
content: formatInboundContent(msg),
|
|
186
185
|
...(msg.segments?.length ? { segments: msg.segments } : {}),
|
|
187
186
|
sender: senderDisplayName(msg),
|
|
188
|
-
id: msg.id,
|
|
189
187
|
metadata: Object.freeze({
|
|
190
188
|
endpoint: this.#options.config.name,
|
|
191
189
|
channelKind: msg.channelKind,
|
|
@@ -198,7 +196,7 @@ export class QqWebsocketEndpoint implements EndpointInstance {
|
|
|
198
196
|
}).catch((err) => {
|
|
199
197
|
logger.warn(formatCompact({
|
|
200
198
|
op: 'qq_gateway_receive_failed',
|
|
201
|
-
target
|
|
199
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
202
200
|
error: err instanceof Error ? err.message : String(err),
|
|
203
201
|
}));
|
|
204
202
|
});
|
|
@@ -249,8 +247,6 @@ export class QqWebsocketEndpoint implements EndpointInstance {
|
|
|
249
247
|
}
|
|
250
248
|
}
|
|
251
249
|
|
|
252
|
-
type ParsedSendTarget = ReturnType<typeof parseSendTarget>;
|
|
253
|
-
|
|
254
250
|
export interface QqHttpEndpointOptions {
|
|
255
251
|
readonly id: CapabilityId;
|
|
256
252
|
readonly gateway: MessageGateway;
|
|
@@ -331,29 +327,27 @@ export class QqHttpEndpoint implements EndpointInstance {
|
|
|
331
327
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
332
328
|
}
|
|
333
329
|
|
|
334
|
-
async send({
|
|
330
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
335
331
|
const body = formatOutbound(payload);
|
|
336
|
-
const
|
|
332
|
+
const kind = qqOutboundKind(conversation);
|
|
337
333
|
const bot = this.#requireBot();
|
|
338
334
|
let result: unknown;
|
|
339
|
-
switch (
|
|
335
|
+
switch (kind) {
|
|
340
336
|
case 'private':
|
|
341
|
-
result = await bot.sendPrivateMessage(
|
|
337
|
+
result = await bot.sendPrivateMessage(conversation.id, body);
|
|
342
338
|
break;
|
|
343
339
|
case 'group':
|
|
344
|
-
result = await bot.sendGroupMessage(
|
|
340
|
+
result = await bot.sendGroupMessage(conversation.id, body);
|
|
345
341
|
break;
|
|
346
342
|
case 'channel':
|
|
347
|
-
result = await bot.sendGuildMessage(
|
|
343
|
+
result = await bot.sendGuildMessage(conversation.id, body);
|
|
348
344
|
break;
|
|
349
345
|
case 'direct':
|
|
350
346
|
if (!bot.sendDirectMessage) throw new Error('QQ direct message not supported by transport');
|
|
351
|
-
result = await bot.sendDirectMessage(
|
|
347
|
+
result = await bot.sendDirectMessage(conversation.id, body);
|
|
352
348
|
break;
|
|
353
|
-
default:
|
|
354
|
-
throw new Error(`unsupported QQ target kind: ${String((parsed as ParsedSendTarget).kind)}`);
|
|
355
349
|
}
|
|
356
|
-
return `${
|
|
350
|
+
return `${kind}-${conversation.id}:${resolveOutboundMessageId(result)}`;
|
|
357
351
|
}
|
|
358
352
|
|
|
359
353
|
async recallMessage(messageId: string): Promise<void> {
|
|
@@ -383,14 +377,13 @@ export class QqHttpEndpoint implements EndpointInstance {
|
|
|
383
377
|
|
|
384
378
|
admit(msg: QqInboundMessage): void {
|
|
385
379
|
if (!this.#open) return;
|
|
386
|
-
const
|
|
380
|
+
const conversation = qqInboundConversation(String(this.#options.id), msg);
|
|
387
381
|
void this.#options.gateway.receive({
|
|
388
|
-
|
|
389
|
-
|
|
382
|
+
conversation,
|
|
383
|
+
message: { conversation, id: msg.id },
|
|
390
384
|
content: formatInboundContent(msg),
|
|
391
385
|
...(msg.segments?.length ? { segments: msg.segments } : {}),
|
|
392
386
|
sender: senderDisplayName(msg),
|
|
393
|
-
id: msg.id,
|
|
394
387
|
metadata: Object.freeze({
|
|
395
388
|
endpoint: this.#options.config.name,
|
|
396
389
|
channelKind: msg.channelKind,
|
|
@@ -403,7 +396,7 @@ export class QqHttpEndpoint implements EndpointInstance {
|
|
|
403
396
|
}).catch((err) => {
|
|
404
397
|
logger.warn(formatCompact({
|
|
405
398
|
op: 'qq_gateway_receive_failed',
|
|
406
|
-
target
|
|
399
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
407
400
|
error: err instanceof Error ? err.message : String(err),
|
|
408
401
|
}));
|
|
409
402
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
export {
|
|
2
2
|
formatInboundContent,
|
|
3
|
-
formatInboundTarget,
|
|
4
3
|
formatOutboundText,
|
|
5
|
-
parseSendTarget,
|
|
6
4
|
resolveOutboundMessageId,
|
|
7
5
|
resolveQqConfig,
|
|
8
6
|
senderDisplayName,
|
|
9
|
-
type ParsedSendTarget,
|
|
10
7
|
type QqAdapterConfig,
|
|
11
8
|
type QqChannelKind,
|
|
12
9
|
type QqInboundMessage,
|
package/src/protocol.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { pickCredential } from '@zhin.js/adapter';
|
|
7
|
+
import type { ConversationKind, ConversationRef } from '@zhin.js/im-contract';
|
|
7
8
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
8
9
|
|
|
9
10
|
const logger = getLogger('qq');
|
|
@@ -92,11 +93,6 @@ export interface QqWireSegment {
|
|
|
92
93
|
readonly data?: Record<string, unknown>;
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
export interface ParsedSendTarget {
|
|
96
|
-
readonly kind: QqChannelKind;
|
|
97
|
-
readonly id: string;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
96
|
export function resolveQqConfig(config: QqAdapterConfig = {}): ResolvedQqConfig {
|
|
101
97
|
const entry = config.endpoints?.find((item) => item.context === 'qq' || !item.context);
|
|
102
98
|
const appid = pickCredential(config.appid, entry?.appid, process.env.QQ_APPID, process.env.QQ_BOT_APPID);
|
|
@@ -140,23 +136,25 @@ export function resolveQqConfig(config: QqAdapterConfig = {}): ResolvedQqConfig
|
|
|
140
136
|
}
|
|
141
137
|
|
|
142
138
|
/**
|
|
143
|
-
*
|
|
139
|
+
* 入站归一化 → ConversationRef:`direct`(频道私信)映射为 guild 容器内的
|
|
140
|
+
* private 会话;频道消息的所属 guild 进 `parent`。
|
|
144
141
|
*/
|
|
145
|
-
export function
|
|
146
|
-
|
|
142
|
+
export function qqInboundConversation(endpointId: string, msg: QqInboundMessage): ConversationRef {
|
|
143
|
+
const kind: ConversationKind = msg.channelKind === 'direct' ? 'private' : msg.channelKind;
|
|
144
|
+
return {
|
|
145
|
+
endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
|
|
146
|
+
kind,
|
|
147
|
+
id: msg.channelId,
|
|
148
|
+
...(msg.guildId && (msg.channelKind === 'channel' || msg.channelKind === 'direct')
|
|
149
|
+
? { parent: { kind: 'channel' as const, id: msg.guildId } }
|
|
150
|
+
: {}),
|
|
151
|
+
};
|
|
147
152
|
}
|
|
148
153
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
const head = target.slice(0, sep);
|
|
155
|
-
const rest = target.slice(sep + 1);
|
|
156
|
-
if (head === 'private' || head === 'group' || head === 'channel' || head === 'direct') {
|
|
157
|
-
return { kind: head, id: rest };
|
|
158
|
-
}
|
|
159
|
-
return { kind: 'private', id: target };
|
|
154
|
+
/** 出站:guild 容器内的 private 会话即 QQ `direct`(频道私信)。 */
|
|
155
|
+
export function qqOutboundKind(conversation: ConversationRef): QqChannelKind {
|
|
156
|
+
if (conversation.kind === 'private' && conversation.parent?.kind === 'channel') return 'direct';
|
|
157
|
+
return conversation.kind;
|
|
160
158
|
}
|
|
161
159
|
|
|
162
160
|
export function senderDisplayName(msg: QqInboundMessage): string {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|