@zhin.js/adapter-milky 6.0.2 → 6.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 +16 -0
- package/README.md +3 -3
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/protocol.d.ts +12 -10
- package/lib/protocol.js +24 -27
- package/lib/sse-endpoint.d.ts +2 -5
- package/lib/sse-endpoint.js +9 -9
- package/lib/webhook-endpoint.d.ts +2 -5
- package/lib/webhook-endpoint.js +9 -9
- package/lib/ws-endpoint.d.ts +2 -5
- package/lib/ws-endpoint.js +9 -9
- package/lib/wss-endpoint.d.ts +2 -5
- package/lib/wss-endpoint.js +9 -9
- package/package.json +14 -13
- package/src/index.ts +1 -6
- package/src/protocol.ts +28 -31
- package/src/sse-endpoint.ts +10 -9
- package/src/webhook-endpoint.ts +14 -10
- package/src/ws-endpoint.ts +10 -9
- package/src/wss-endpoint.ts +14 -10
- /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,21 @@
|
|
|
1
1
|
# @zhin.js/adapter-milky
|
|
2
2
|
|
|
3
|
+
## 6.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
|
+
|
|
3
19
|
## 6.0.2
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Zhin.js [Milky](https://milky.ntqqrev.org/) 协议适配器(Plugin Runtime)
|
|
|
8
8
|
- 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
|
|
9
9
|
- **正向 WebSocket**(`connection: ws`):应用连协议端 `ws(s)://baseUrl/event`
|
|
10
10
|
- `access_token` 鉴权(Bearer + query)
|
|
11
|
-
- 入站经 `messageGatewayToken`;出站 `send({
|
|
11
|
+
- 入站经 `messageGatewayToken`;出站 `send({ conversation, payload })` → HTTP `send_*_message`
|
|
12
12
|
|
|
13
13
|
## 安装
|
|
14
14
|
|
|
@@ -23,8 +23,8 @@ pnpm add @zhin.js/adapter-milky
|
|
|
23
23
|
- `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
|
|
24
24
|
- 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
|
|
25
25
|
|
|
26
|
-
入站:`gateway.receive({
|
|
27
|
-
出站:`send({
|
|
26
|
+
入站:`gateway.receive({ conversation: ConversationRef(kind: "private"|"group", id), message, content, sender, metadata })`
|
|
27
|
+
出站:`send({ conversation, payload })` → HTTP `send_private_message` / `send_group_message`(payload 已由 gateway/core 渲染;无 segment-mapper)
|
|
28
28
|
|
|
29
29
|
## 最小配置
|
|
30
30
|
|
package/lib/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { buildSendAction, buildSseConnectOptions, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent,
|
|
1
|
+
export { buildSendAction, buildSseConnectOptions, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatOutboundSegments, isMentioned, isMessageReceiveEvent, milkyInboundConversation, parseMessageReceiveData, resolveMilkyConfig, senderNickname, type MilkyAdapterConfig, type MilkyApiClientOptions, type MilkyApiResponse, type MilkyConfigBase, type MilkyEndpointConfig, type MilkyEvent, type MilkyIncomingMessage, type MilkyIncomingSegment, type MilkyOutgoingSegment, type MilkySseConfig, type MilkyWebhookConfig, type MilkyWireSegment, type MilkyWsConfig, type MilkyWssConfig, type ResolvedMilkyConfig, } from './protocol.js';
|
|
2
2
|
export { MilkySseEndpoint, MilkyWebhookEndpoint, MilkyWssEndpoint, MilkyWsEndpoint, consumeSseBuffer, openSseStream, readRequestBody, verifyMilkyAccessToken, type CreateMilkySseStream, type MilkySseEndpointOptions, type MilkyWebhookEndpointOptions, type MilkyWssEndpointOptions, type MilkyWsCreateOptions, type MilkyWsEndpointOptions, type MilkyWsSocket, } from './endpoint.js';
|
|
3
3
|
export { getMilkyAgentDeps, registerMilkyAgentEndpoint, setMilkyAgentDeps, type MilkyAgentDeps, type MilkyAgentEndpoint, } from './milky-agent-deps.js';
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { buildSendAction, buildSseConnectOptions, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent,
|
|
1
|
+
export { buildSendAction, buildSseConnectOptions, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatOutboundSegments, isMentioned, isMessageReceiveEvent, milkyInboundConversation, parseMessageReceiveData, resolveMilkyConfig, senderNickname, } from './protocol.js';
|
|
2
2
|
export { MilkySseEndpoint, MilkyWebhookEndpoint, MilkyWssEndpoint, MilkyWsEndpoint, consumeSseBuffer, openSseStream, readRequestBody, verifyMilkyAccessToken, } from './endpoint.js';
|
|
3
3
|
export { getMilkyAgentDeps, registerMilkyAgentEndpoint, setMilkyAgentDeps, } from './milky-agent-deps.js';
|
package/lib/protocol.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Spec: https://milky.ntqqrev.org/
|
|
5
5
|
*/
|
|
6
6
|
import type { Segment } from '@zhin.js/core/runtime';
|
|
7
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
7
8
|
/** Transitional legacy endpoint row (`endpoints[]` with `context: milky`). */
|
|
8
9
|
export interface MilkyLegacyEndpointRow {
|
|
9
10
|
readonly context?: string;
|
|
@@ -103,10 +104,6 @@ export interface MilkyOutgoingSegment {
|
|
|
103
104
|
type: string;
|
|
104
105
|
data: Record<string, unknown>;
|
|
105
106
|
}
|
|
106
|
-
export interface ParsedSendTarget {
|
|
107
|
-
readonly message_type: 'private' | 'group';
|
|
108
|
-
readonly id: string;
|
|
109
|
-
}
|
|
110
107
|
export interface MilkyApiClientOptions {
|
|
111
108
|
readonly baseUrl: string;
|
|
112
109
|
readonly access_token?: string;
|
|
@@ -122,8 +119,11 @@ export declare function parseMessageReceiveData(event: MilkyEvent): MilkyIncomin
|
|
|
122
119
|
export declare function isMessageReceiveEvent(event: MilkyEvent): event is MilkyEvent & {
|
|
123
120
|
data: MilkyIncomingMessage;
|
|
124
121
|
};
|
|
125
|
-
/**
|
|
126
|
-
|
|
122
|
+
/**
|
|
123
|
+
* 入站归一化 → ConversationRef:`friend`(私聊)→ `private`;`group` → `group`;
|
|
124
|
+
* `temp`(群临时会话)→ 群容器内的 `private` 会话(parent = 来源群)。
|
|
125
|
+
*/
|
|
126
|
+
export declare function milkyInboundConversation(endpointId: string, data: MilkyIncomingMessage): ConversationRef;
|
|
127
127
|
export declare function formatInboundContent(data: MilkyIncomingMessage): string;
|
|
128
128
|
/**
|
|
129
129
|
* 入站消息段 → canonical Segment[](与 formatInboundContent 纯文本视图同源双轨)。
|
|
@@ -139,7 +139,6 @@ export declare function senderNickname(data: MilkyIncomingMessage): string | und
|
|
|
139
139
|
/** 消息段中含 @ 本机(mention 段 user_id 等于协议端上报的 self_id)。 */
|
|
140
140
|
export declare function isMentioned(data: MilkyIncomingMessage, selfId: number | undefined): boolean;
|
|
141
141
|
export declare function formatInboundMessageId(data: MilkyIncomingMessage): string;
|
|
142
|
-
export declare function parseSendTarget(target: string): ParsedSendTarget;
|
|
143
142
|
/**
|
|
144
143
|
* Wire-encode an already-rendered outbound payload into Milky outgoing segments.
|
|
145
144
|
* Segment canonicalization is intentionally not done here.
|
|
@@ -147,12 +146,15 @@ export declare function parseSendTarget(target: string): ParsedSendTarget;
|
|
|
147
146
|
* file 段 Milky 消息 API 无对应 wire 段(文件走 upload_* API),warn + 丢弃。
|
|
148
147
|
*/
|
|
149
148
|
export declare function formatOutboundSegments(payload: unknown): MilkyOutgoingSegment[];
|
|
150
|
-
export declare function buildSendAction(
|
|
149
|
+
export declare function buildSendAction(conversation: ConversationRef, message: MilkyOutgoingSegment[]): {
|
|
151
150
|
action: string;
|
|
152
151
|
params: Record<string, unknown>;
|
|
153
152
|
};
|
|
154
|
-
/**
|
|
155
|
-
|
|
153
|
+
/**
|
|
154
|
+
* message_seq result → gateway message id(`scene:peer:seq` 复合格式,
|
|
155
|
+
* 与 formatInboundMessageId 同源,仅供 recall 链在本端点边界内解析)。
|
|
156
|
+
*/
|
|
157
|
+
export declare function formatOutboundMessageId(conversation: ConversationRef, messageSeq: number | undefined): string;
|
|
156
158
|
export declare function parseMilkyMessageId(msgId: string): {
|
|
157
159
|
message_scene: 'friend' | 'group' | 'temp';
|
|
158
160
|
peer_id: number;
|
package/lib/protocol.js
CHANGED
|
@@ -125,10 +125,19 @@ export function parseMessageReceiveData(event) {
|
|
|
125
125
|
export function isMessageReceiveEvent(event) {
|
|
126
126
|
return parseMessageReceiveData(event) != null;
|
|
127
127
|
}
|
|
128
|
-
/**
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
/**
|
|
129
|
+
* 入站归一化 → ConversationRef:`friend`(私聊)→ `private`;`group` → `group`;
|
|
130
|
+
* `temp`(群临时会话)→ 群容器内的 `private` 会话(parent = 来源群)。
|
|
131
|
+
*/
|
|
132
|
+
export function milkyInboundConversation(endpointId, data) {
|
|
133
|
+
return {
|
|
134
|
+
endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
|
|
135
|
+
kind: data.message_scene === 'group' ? 'group' : 'private',
|
|
136
|
+
id: String(data.peer_id),
|
|
137
|
+
...(data.message_scene === 'temp' && data.group
|
|
138
|
+
? { parent: { kind: 'group', id: String(data.group.group_id) } }
|
|
139
|
+
: {}),
|
|
140
|
+
};
|
|
132
141
|
}
|
|
133
142
|
export function formatInboundContent(data) {
|
|
134
143
|
return data.segments.map((seg) => {
|
|
@@ -290,19 +299,6 @@ export function isMentioned(data, selfId) {
|
|
|
290
299
|
export function formatInboundMessageId(data) {
|
|
291
300
|
return `${data.message_scene}:${data.peer_id}:${data.message_seq}`;
|
|
292
301
|
}
|
|
293
|
-
export function parseSendTarget(target) {
|
|
294
|
-
const sep = target.indexOf(':');
|
|
295
|
-
if (sep <= 0) {
|
|
296
|
-
return { message_type: 'private', id: target };
|
|
297
|
-
}
|
|
298
|
-
const head = target.slice(0, sep);
|
|
299
|
-
const rest = target.slice(sep + 1);
|
|
300
|
-
if (head === 'group')
|
|
301
|
-
return { message_type: 'group', id: rest };
|
|
302
|
-
if (head === 'private')
|
|
303
|
-
return { message_type: 'private', id: rest };
|
|
304
|
-
return { message_type: 'private', id: target };
|
|
305
|
-
}
|
|
306
302
|
/**
|
|
307
303
|
* 解析 image/audio/video 段的投递 uri(canonical MediaRef 唯一来源):
|
|
308
304
|
* - kind=url → http(s):// 直发;
|
|
@@ -432,13 +428,12 @@ export function formatOutboundSegments(payload) {
|
|
|
432
428
|
}
|
|
433
429
|
return out.length ? out : [{ type: 'text', data: { text: '' } }];
|
|
434
430
|
}
|
|
435
|
-
export function buildSendAction(
|
|
436
|
-
|
|
437
|
-
if (parsed.message_type === 'group') {
|
|
431
|
+
export function buildSendAction(conversation, message) {
|
|
432
|
+
if (conversation.kind === 'group') {
|
|
438
433
|
return {
|
|
439
434
|
action: 'send_group_message',
|
|
440
435
|
params: {
|
|
441
|
-
group_id: parseInt(
|
|
436
|
+
group_id: parseInt(conversation.id, 10),
|
|
442
437
|
message,
|
|
443
438
|
},
|
|
444
439
|
};
|
|
@@ -446,18 +441,20 @@ export function buildSendAction(target, message) {
|
|
|
446
441
|
return {
|
|
447
442
|
action: 'send_private_message',
|
|
448
443
|
params: {
|
|
449
|
-
user_id: parseInt(
|
|
444
|
+
user_id: parseInt(conversation.id, 10),
|
|
450
445
|
message,
|
|
451
446
|
},
|
|
452
447
|
};
|
|
453
448
|
}
|
|
454
|
-
/**
|
|
455
|
-
|
|
449
|
+
/**
|
|
450
|
+
* message_seq result → gateway message id(`scene:peer:seq` 复合格式,
|
|
451
|
+
* 与 formatInboundMessageId 同源,仅供 recall 链在本端点边界内解析)。
|
|
452
|
+
*/
|
|
453
|
+
export function formatOutboundMessageId(conversation, messageSeq) {
|
|
456
454
|
if (messageSeq == null)
|
|
457
455
|
return '';
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
return `${scene}:${parsed.id}:${messageSeq}`;
|
|
456
|
+
const scene = conversation.kind === 'group' ? 'group' : 'friend';
|
|
457
|
+
return `${scene}:${conversation.id}:${messageSeq}`;
|
|
461
458
|
}
|
|
462
459
|
export function parseMilkyMessageId(msgId) {
|
|
463
460
|
const parts = msgId.split(':');
|
package/lib/sse-endpoint.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Milky SSE client endpoint — GET text/event-stream on /event.
|
|
3
3
|
*/
|
|
4
|
-
import { type EndpointInstance, type EndpointManagement } from '@zhin.js/adapter';
|
|
4
|
+
import { type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from '@zhin.js/adapter';
|
|
5
5
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
6
6
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
7
7
|
import { callApi, type MilkyEvent, type MilkySseConfig } from './protocol.js';
|
|
@@ -28,10 +28,7 @@ export declare class MilkySseEndpoint implements EndpointInstance {
|
|
|
28
28
|
open(): void;
|
|
29
29
|
close(): void;
|
|
30
30
|
stop(): Promise<void>;
|
|
31
|
-
send({
|
|
32
|
-
readonly target: string;
|
|
33
|
-
readonly payload: unknown;
|
|
34
|
-
}): Promise<string>;
|
|
31
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
35
32
|
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
36
33
|
recallMessage(id: string): Promise<void>;
|
|
37
34
|
kickMember(groupId: number, userId: number, rejectAddRequest?: boolean): Promise<boolean>;
|
package/lib/sse-endpoint.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createEndpointLifecycle, } from '@zhin.js/adapter';
|
|
|
5
5
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
6
6
|
import { createMilkyEndpointManagement } from './endpoint-management.js';
|
|
7
7
|
import { registerMilkyAgentEndpoint } from './milky-agent-deps.js';
|
|
8
|
-
import { buildSendAction, buildSseConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundSegments,
|
|
8
|
+
import { buildSendAction, buildSseConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundSegments, formatOutboundMessageId, formatOutboundSegments, isMentioned, milkyInboundConversation, parseMessageReceiveData, parseMilkyMessageId, senderNickname, } from './protocol.js';
|
|
9
9
|
import { openSseStream } from './sse-client.js';
|
|
10
10
|
const logger = getLogger('milky');
|
|
11
11
|
export class MilkySseEndpoint {
|
|
@@ -58,15 +58,15 @@ export class MilkySseEndpoint {
|
|
|
58
58
|
this.#stream?.close();
|
|
59
59
|
this.#stream = undefined;
|
|
60
60
|
}
|
|
61
|
-
async send({
|
|
61
|
+
async send({ conversation, payload }) {
|
|
62
62
|
const message = formatOutboundSegments(payload);
|
|
63
|
-
const { action, params } = buildSendAction(
|
|
63
|
+
const { action, params } = buildSendAction(conversation, message);
|
|
64
64
|
const data = await this.callApi(action, params);
|
|
65
|
-
const messageId = formatOutboundMessageId(
|
|
65
|
+
const messageId = formatOutboundMessageId(conversation, data?.message_seq);
|
|
66
66
|
logger.debug(formatCompact({
|
|
67
67
|
op: 'milky_send',
|
|
68
68
|
endpoint: this.#options.config.name,
|
|
69
|
-
target
|
|
69
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
70
70
|
messageId,
|
|
71
71
|
mode: 'sse',
|
|
72
72
|
}));
|
|
@@ -159,19 +159,19 @@ export class MilkySseEndpoint {
|
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
161
|
#admitMessage(data, event) {
|
|
162
|
-
const
|
|
162
|
+
const conversation = milkyInboundConversation(String(this.#options.id), data);
|
|
163
|
+
const target = `${conversation.kind}:${conversation.id}`;
|
|
163
164
|
const content = formatInboundContent(data);
|
|
164
165
|
const segments = formatInboundSegments(data);
|
|
165
166
|
const audioUrl = extractInboundAudioUrl(data);
|
|
166
167
|
const nickname = senderNickname(data);
|
|
167
168
|
const mentioned = isMentioned(data, event.self_id);
|
|
168
169
|
void this.#options.gateway.receive({
|
|
169
|
-
|
|
170
|
-
|
|
170
|
+
conversation,
|
|
171
|
+
message: { conversation, id: formatInboundMessageId(data) },
|
|
171
172
|
content,
|
|
172
173
|
segments,
|
|
173
174
|
sender: String(data.sender_id),
|
|
174
|
-
id: formatInboundMessageId(data),
|
|
175
175
|
metadata: Object.freeze({
|
|
176
176
|
message_scene: data.message_scene,
|
|
177
177
|
peer_id: String(data.peer_id),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
1
|
+
import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
|
|
2
2
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
4
4
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
@@ -18,10 +18,7 @@ export declare class MilkyWebhookEndpoint implements EndpointInstance {
|
|
|
18
18
|
open(): void;
|
|
19
19
|
close(): void;
|
|
20
20
|
stop(): Promise<void>;
|
|
21
|
-
send({
|
|
22
|
-
readonly target: string;
|
|
23
|
-
readonly payload: unknown;
|
|
24
|
-
}): Promise<string>;
|
|
21
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
25
22
|
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
26
23
|
recallMessage(id: string): Promise<void>;
|
|
27
24
|
kickMember(groupId: number, userId: number, rejectAddRequest?: boolean): Promise<boolean>;
|
package/lib/webhook-endpoint.js
CHANGED
|
@@ -2,7 +2,7 @@ import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
|
2
2
|
import { readRequestBody, verifyMilkyAccessToken } from './milky-auth.js';
|
|
3
3
|
import { createMilkyEndpointManagement } from './endpoint-management.js';
|
|
4
4
|
import { registerMilkyAgentEndpoint } from './milky-agent-deps.js';
|
|
5
|
-
import { buildSendAction, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundSegments,
|
|
5
|
+
import { buildSendAction, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundSegments, formatOutboundMessageId, formatOutboundSegments, isMentioned, milkyInboundConversation, parseMessageReceiveData, parseMilkyMessageId, senderNickname, } from './protocol.js';
|
|
6
6
|
const logger = getLogger('milky');
|
|
7
7
|
export class MilkyWebhookEndpoint {
|
|
8
8
|
#options;
|
|
@@ -44,15 +44,15 @@ export class MilkyWebhookEndpoint {
|
|
|
44
44
|
this.#started = false;
|
|
45
45
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
46
46
|
}
|
|
47
|
-
async send({
|
|
47
|
+
async send({ conversation, payload }) {
|
|
48
48
|
const message = formatOutboundSegments(payload);
|
|
49
|
-
const { action, params } = buildSendAction(
|
|
49
|
+
const { action, params } = buildSendAction(conversation, message);
|
|
50
50
|
const data = await this.callApi(action, params);
|
|
51
|
-
const messageId = formatOutboundMessageId(
|
|
51
|
+
const messageId = formatOutboundMessageId(conversation, data?.message_seq);
|
|
52
52
|
logger.debug(formatCompact({
|
|
53
53
|
op: 'milky_send',
|
|
54
54
|
endpoint: this.#options.config.name,
|
|
55
|
-
target
|
|
55
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
56
56
|
messageId,
|
|
57
57
|
}));
|
|
58
58
|
return messageId;
|
|
@@ -144,19 +144,19 @@ export class MilkyWebhookEndpoint {
|
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
146
|
#admitMessage(data, event) {
|
|
147
|
-
const
|
|
147
|
+
const conversation = milkyInboundConversation(String(this.#options.id), data);
|
|
148
|
+
const target = `${conversation.kind}:${conversation.id}`;
|
|
148
149
|
const content = formatInboundContent(data);
|
|
149
150
|
const segments = formatInboundSegments(data);
|
|
150
151
|
const audioUrl = extractInboundAudioUrl(data);
|
|
151
152
|
const nickname = senderNickname(data);
|
|
152
153
|
const mentioned = isMentioned(data, event.self_id);
|
|
153
154
|
void this.#options.gateway.receive({
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
conversation,
|
|
156
|
+
message: { conversation, id: formatInboundMessageId(data) },
|
|
156
157
|
content,
|
|
157
158
|
segments,
|
|
158
159
|
sender: String(data.sender_id),
|
|
159
|
-
id: formatInboundMessageId(data),
|
|
160
160
|
metadata: Object.freeze({
|
|
161
161
|
message_scene: data.message_scene,
|
|
162
162
|
peer_id: String(data.peer_id),
|
package/lib/ws-endpoint.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type EndpointInstance, type EndpointManagement } from '@zhin.js/adapter';
|
|
1
|
+
import { type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from '@zhin.js/adapter';
|
|
2
2
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
4
4
|
import { callApi, type MilkyEvent, type MilkyWsConfig } from './protocol.js';
|
|
@@ -18,10 +18,7 @@ export declare class MilkyWsEndpoint implements EndpointInstance {
|
|
|
18
18
|
open(): void;
|
|
19
19
|
close(): void;
|
|
20
20
|
stop(): Promise<void>;
|
|
21
|
-
send({
|
|
22
|
-
readonly target: string;
|
|
23
|
-
readonly payload: unknown;
|
|
24
|
-
}): Promise<string>;
|
|
21
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
25
22
|
/** Public API for agent tools / callers. */
|
|
26
23
|
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
27
24
|
recallMessage(id: string): Promise<void>;
|
package/lib/ws-endpoint.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createEndpointLifecycle, } from '@zhin.js/adapter';
|
|
|
6
6
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
7
7
|
import { createMilkyEndpointManagement } from './endpoint-management.js';
|
|
8
8
|
import { registerMilkyAgentEndpoint } from './milky-agent-deps.js';
|
|
9
|
-
import { buildSendAction, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundSegments,
|
|
9
|
+
import { buildSendAction, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundSegments, formatOutboundMessageId, formatOutboundSegments, isMentioned, milkyInboundConversation, parseMessageReceiveData, parseMilkyMessageId, senderNickname, } from './protocol.js';
|
|
10
10
|
const logger = getLogger('milky');
|
|
11
11
|
const WS_OPEN = 1;
|
|
12
12
|
export class MilkyWsEndpoint {
|
|
@@ -66,15 +66,15 @@ export class MilkyWsEndpoint {
|
|
|
66
66
|
this.#ws = undefined;
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
-
async send({
|
|
69
|
+
async send({ conversation, payload }) {
|
|
70
70
|
const message = formatOutboundSegments(payload);
|
|
71
|
-
const { action, params } = buildSendAction(
|
|
71
|
+
const { action, params } = buildSendAction(conversation, message);
|
|
72
72
|
const data = await this.callApi(action, params);
|
|
73
|
-
const messageId = formatOutboundMessageId(
|
|
73
|
+
const messageId = formatOutboundMessageId(conversation, data?.message_seq);
|
|
74
74
|
logger.debug(formatCompact({
|
|
75
75
|
op: 'milky_send',
|
|
76
76
|
endpoint: this.#options.config.name,
|
|
77
|
-
target
|
|
77
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
78
78
|
messageId,
|
|
79
79
|
}));
|
|
80
80
|
return messageId;
|
|
@@ -168,19 +168,19 @@ export class MilkyWsEndpoint {
|
|
|
168
168
|
};
|
|
169
169
|
}
|
|
170
170
|
#admitMessage(data, event) {
|
|
171
|
-
const
|
|
171
|
+
const conversation = milkyInboundConversation(String(this.#options.id), data);
|
|
172
|
+
const target = `${conversation.kind}:${conversation.id}`;
|
|
172
173
|
const content = formatInboundContent(data);
|
|
173
174
|
const segments = formatInboundSegments(data);
|
|
174
175
|
const audioUrl = extractInboundAudioUrl(data);
|
|
175
176
|
const nickname = senderNickname(data);
|
|
176
177
|
const mentioned = isMentioned(data, event.self_id);
|
|
177
178
|
void this.#options.gateway.receive({
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
conversation,
|
|
180
|
+
message: { conversation, id: formatInboundMessageId(data) },
|
|
180
181
|
content,
|
|
181
182
|
segments,
|
|
182
183
|
sender: String(data.sender_id),
|
|
183
|
-
id: formatInboundMessageId(data),
|
|
184
184
|
metadata: Object.freeze({
|
|
185
185
|
message_scene: data.message_scene,
|
|
186
186
|
peer_id: String(data.peer_id),
|
package/lib/wss-endpoint.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
1
|
+
import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
|
|
2
2
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
4
4
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
@@ -18,10 +18,7 @@ export declare class MilkyWssEndpoint implements EndpointInstance {
|
|
|
18
18
|
open(): void;
|
|
19
19
|
close(): void;
|
|
20
20
|
stop(): Promise<void>;
|
|
21
|
-
send({
|
|
22
|
-
readonly target: string;
|
|
23
|
-
readonly payload: unknown;
|
|
24
|
-
}): Promise<string>;
|
|
21
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
25
22
|
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
26
23
|
recallMessage(id: string): Promise<void>;
|
|
27
24
|
kickMember(groupId: number, userId: number, rejectAddRequest?: boolean): Promise<boolean>;
|
package/lib/wss-endpoint.js
CHANGED
|
@@ -6,7 +6,7 @@ import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
|
6
6
|
import { verifyMilkyAccessToken } from './milky-auth.js';
|
|
7
7
|
import { createMilkyEndpointManagement } from './endpoint-management.js';
|
|
8
8
|
import { registerMilkyAgentEndpoint } from './milky-agent-deps.js';
|
|
9
|
-
import { buildSendAction, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundSegments,
|
|
9
|
+
import { buildSendAction, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundSegments, formatOutboundMessageId, formatOutboundSegments, isMentioned, milkyInboundConversation, parseMessageReceiveData, parseMilkyMessageId, senderNickname, } from './protocol.js';
|
|
10
10
|
const logger = getLogger('milky');
|
|
11
11
|
const WS_OPEN = 1;
|
|
12
12
|
export class MilkyWssEndpoint {
|
|
@@ -66,15 +66,15 @@ export class MilkyWssEndpoint {
|
|
|
66
66
|
}
|
|
67
67
|
this.#started = false;
|
|
68
68
|
}
|
|
69
|
-
async send({
|
|
69
|
+
async send({ conversation, payload }) {
|
|
70
70
|
const message = formatOutboundSegments(payload);
|
|
71
|
-
const { action, params } = buildSendAction(
|
|
71
|
+
const { action, params } = buildSendAction(conversation, message);
|
|
72
72
|
const data = await this.callApi(action, params);
|
|
73
|
-
const messageId = formatOutboundMessageId(
|
|
73
|
+
const messageId = formatOutboundMessageId(conversation, data?.message_seq);
|
|
74
74
|
logger.debug(formatCompact({
|
|
75
75
|
op: 'milky_send',
|
|
76
76
|
endpoint: this.#options.config.name,
|
|
77
|
-
target
|
|
77
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
78
78
|
messageId,
|
|
79
79
|
}));
|
|
80
80
|
return messageId;
|
|
@@ -166,19 +166,19 @@ export class MilkyWssEndpoint {
|
|
|
166
166
|
};
|
|
167
167
|
}
|
|
168
168
|
#admitMessage(data, event) {
|
|
169
|
-
const
|
|
169
|
+
const conversation = milkyInboundConversation(String(this.#options.id), data);
|
|
170
|
+
const target = `${conversation.kind}:${conversation.id}`;
|
|
170
171
|
const content = formatInboundContent(data);
|
|
171
172
|
const segments = formatInboundSegments(data);
|
|
172
173
|
const audioUrl = extractInboundAudioUrl(data);
|
|
173
174
|
const nickname = senderNickname(data);
|
|
174
175
|
const mentioned = isMentioned(data, event.self_id);
|
|
175
176
|
void this.#options.gateway.receive({
|
|
176
|
-
|
|
177
|
-
|
|
177
|
+
conversation,
|
|
178
|
+
message: { conversation, id: formatInboundMessageId(data) },
|
|
178
179
|
content,
|
|
179
180
|
segments,
|
|
180
181
|
sender: String(data.sender_id),
|
|
181
|
-
id: formatInboundMessageId(data),
|
|
182
182
|
metadata: Object.freeze({
|
|
183
183
|
message_scene: data.message_scene,
|
|
184
184
|
peer_id: String(data.peer_id),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-milky",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.3",
|
|
4
4
|
"description": "Zhin.js Milky adapter for Plugin Runtime (WebSocket client)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -40,12 +40,13 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"ws": "^8.21.1",
|
|
43
|
-
"@zhin.js/adapter": "1.1.
|
|
44
|
-
"@zhin.js/command": "1.0.
|
|
45
|
-
"@zhin.js/core": "1.5.
|
|
46
|
-
"@zhin.js/host-http": "1.0.
|
|
43
|
+
"@zhin.js/adapter": "1.1.5",
|
|
44
|
+
"@zhin.js/command": "1.0.7",
|
|
45
|
+
"@zhin.js/core": "1.5.2",
|
|
46
|
+
"@zhin.js/host-http": "1.0.6",
|
|
47
|
+
"@zhin.js/im-contract": "1.0.1",
|
|
47
48
|
"@zhin.js/logger": "1.0.75",
|
|
48
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
49
|
+
"@zhin.js/plugin-runtime": "1.1.3"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@types/node": "^26.1.2",
|
|
@@ -53,16 +54,16 @@
|
|
|
53
54
|
"typescript": "^6.0.3",
|
|
54
55
|
"vitest": "^4.1.10",
|
|
55
56
|
"zod": "^4.4.3",
|
|
56
|
-
"@zhin.js/agent": "1.1.
|
|
57
|
-
"@zhin.js/host-http": "1.0.
|
|
57
|
+
"@zhin.js/agent": "1.1.3",
|
|
58
|
+
"@zhin.js/host-http": "1.0.6"
|
|
58
59
|
},
|
|
59
60
|
"peerDependencies": {
|
|
60
61
|
"zod": "^4.0.0",
|
|
61
|
-
"@zhin.js/adapter": "1.1.
|
|
62
|
-
"@zhin.js/agent": "1.1.
|
|
63
|
-
"@zhin.js/core": "1.5.
|
|
64
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
65
|
-
"zhin.js": "6.0.
|
|
62
|
+
"@zhin.js/adapter": "1.1.5",
|
|
63
|
+
"@zhin.js/agent": "1.1.3",
|
|
64
|
+
"@zhin.js/core": "1.5.2",
|
|
65
|
+
"@zhin.js/plugin-runtime": "1.1.3",
|
|
66
|
+
"zhin.js": "6.0.2"
|
|
66
67
|
},
|
|
67
68
|
"peerDependenciesMeta": {
|
|
68
69
|
"zhin.js": {
|
package/src/index.ts
CHANGED
|
@@ -5,15 +5,11 @@ export {
|
|
|
5
5
|
callApi,
|
|
6
6
|
extractInboundAudioUrl,
|
|
7
7
|
formatInboundContent,
|
|
8
|
-
formatInboundMessageId,
|
|
9
|
-
formatInboundTarget,
|
|
10
|
-
formatOutboundMessageId,
|
|
11
8
|
formatOutboundSegments,
|
|
12
9
|
isMentioned,
|
|
13
10
|
isMessageReceiveEvent,
|
|
11
|
+
milkyInboundConversation,
|
|
14
12
|
parseMessageReceiveData,
|
|
15
|
-
parseMilkyMessageId,
|
|
16
|
-
parseSendTarget,
|
|
17
13
|
resolveMilkyConfig,
|
|
18
14
|
senderNickname,
|
|
19
15
|
type MilkyAdapterConfig,
|
|
@@ -30,7 +26,6 @@ export {
|
|
|
30
26
|
type MilkyWireSegment,
|
|
31
27
|
type MilkyWsConfig,
|
|
32
28
|
type MilkyWssConfig,
|
|
33
|
-
type ParsedSendTarget,
|
|
34
29
|
type ResolvedMilkyConfig,
|
|
35
30
|
} from './protocol.js';
|
|
36
31
|
|
package/src/protocol.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { isMediaRef } from '@zhin.js/core';
|
|
8
8
|
import type { Segment } from '@zhin.js/core/runtime';
|
|
9
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
9
10
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
10
11
|
|
|
11
12
|
const logger = getLogger('milky');
|
|
@@ -116,11 +117,6 @@ export interface MilkyOutgoingSegment {
|
|
|
116
117
|
data: Record<string, unknown>;
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
export interface ParsedSendTarget {
|
|
120
|
-
readonly message_type: 'private' | 'group';
|
|
121
|
-
readonly id: string;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
120
|
export interface MilkyApiClientOptions {
|
|
125
121
|
readonly baseUrl: string;
|
|
126
122
|
readonly access_token?: string;
|
|
@@ -261,10 +257,22 @@ export function isMessageReceiveEvent(
|
|
|
261
257
|
return parseMessageReceiveData(event) != null;
|
|
262
258
|
}
|
|
263
259
|
|
|
264
|
-
/**
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
260
|
+
/**
|
|
261
|
+
* 入站归一化 → ConversationRef:`friend`(私聊)→ `private`;`group` → `group`;
|
|
262
|
+
* `temp`(群临时会话)→ 群容器内的 `private` 会话(parent = 来源群)。
|
|
263
|
+
*/
|
|
264
|
+
export function milkyInboundConversation(
|
|
265
|
+
endpointId: string,
|
|
266
|
+
data: MilkyIncomingMessage,
|
|
267
|
+
): ConversationRef {
|
|
268
|
+
return {
|
|
269
|
+
endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
|
|
270
|
+
kind: data.message_scene === 'group' ? 'group' : 'private',
|
|
271
|
+
id: String(data.peer_id),
|
|
272
|
+
...(data.message_scene === 'temp' && data.group
|
|
273
|
+
? { parent: { kind: 'group' as const, id: String(data.group.group_id) } }
|
|
274
|
+
: {}),
|
|
275
|
+
};
|
|
268
276
|
}
|
|
269
277
|
|
|
270
278
|
export function formatInboundContent(data: MilkyIncomingMessage): string {
|
|
@@ -427,18 +435,6 @@ export function formatInboundMessageId(data: MilkyIncomingMessage): string {
|
|
|
427
435
|
return `${data.message_scene}:${data.peer_id}:${data.message_seq}`;
|
|
428
436
|
}
|
|
429
437
|
|
|
430
|
-
export function parseSendTarget(target: string): ParsedSendTarget {
|
|
431
|
-
const sep = target.indexOf(':');
|
|
432
|
-
if (sep <= 0) {
|
|
433
|
-
return { message_type: 'private', id: target };
|
|
434
|
-
}
|
|
435
|
-
const head = target.slice(0, sep);
|
|
436
|
-
const rest = target.slice(sep + 1);
|
|
437
|
-
if (head === 'group') return { message_type: 'group', id: rest };
|
|
438
|
-
if (head === 'private') return { message_type: 'private', id: rest };
|
|
439
|
-
return { message_type: 'private', id: target };
|
|
440
|
-
}
|
|
441
|
-
|
|
442
438
|
/**
|
|
443
439
|
* 解析 image/audio/video 段的投递 uri(canonical MediaRef 唯一来源):
|
|
444
440
|
* - kind=url → http(s):// 直发;
|
|
@@ -570,15 +566,14 @@ export function formatOutboundSegments(payload: unknown): MilkyOutgoingSegment[]
|
|
|
570
566
|
}
|
|
571
567
|
|
|
572
568
|
export function buildSendAction(
|
|
573
|
-
|
|
569
|
+
conversation: ConversationRef,
|
|
574
570
|
message: MilkyOutgoingSegment[],
|
|
575
571
|
): { action: string; params: Record<string, unknown> } {
|
|
576
|
-
|
|
577
|
-
if (parsed.message_type === 'group') {
|
|
572
|
+
if (conversation.kind === 'group') {
|
|
578
573
|
return {
|
|
579
574
|
action: 'send_group_message',
|
|
580
575
|
params: {
|
|
581
|
-
group_id: parseInt(
|
|
576
|
+
group_id: parseInt(conversation.id, 10),
|
|
582
577
|
message,
|
|
583
578
|
},
|
|
584
579
|
};
|
|
@@ -586,21 +581,23 @@ export function buildSendAction(
|
|
|
586
581
|
return {
|
|
587
582
|
action: 'send_private_message',
|
|
588
583
|
params: {
|
|
589
|
-
user_id: parseInt(
|
|
584
|
+
user_id: parseInt(conversation.id, 10),
|
|
590
585
|
message,
|
|
591
586
|
},
|
|
592
587
|
};
|
|
593
588
|
}
|
|
594
589
|
|
|
595
|
-
/**
|
|
590
|
+
/**
|
|
591
|
+
* message_seq result → gateway message id(`scene:peer:seq` 复合格式,
|
|
592
|
+
* 与 formatInboundMessageId 同源,仅供 recall 链在本端点边界内解析)。
|
|
593
|
+
*/
|
|
596
594
|
export function formatOutboundMessageId(
|
|
597
|
-
|
|
595
|
+
conversation: ConversationRef,
|
|
598
596
|
messageSeq: number | undefined,
|
|
599
597
|
): string {
|
|
600
598
|
if (messageSeq == null) return '';
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
return `${scene}:${parsed.id}:${messageSeq}`;
|
|
599
|
+
const scene = conversation.kind === 'group' ? 'group' : 'friend';
|
|
600
|
+
return `${scene}:${conversation.id}:${messageSeq}`;
|
|
604
601
|
}
|
|
605
602
|
|
|
606
603
|
export function parseMilkyMessageId(
|
package/src/sse-endpoint.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type EndpointInstance,
|
|
8
8
|
type EndpointLifecycle,
|
|
9
9
|
type EndpointManagement,
|
|
10
|
+
type EndpointSendRequest,
|
|
10
11
|
} from '@zhin.js/adapter';
|
|
11
12
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
12
13
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
@@ -21,10 +22,10 @@ import {
|
|
|
21
22
|
formatInboundContent,
|
|
22
23
|
formatInboundMessageId,
|
|
23
24
|
formatInboundSegments,
|
|
24
|
-
formatInboundTarget,
|
|
25
25
|
formatOutboundMessageId,
|
|
26
26
|
formatOutboundSegments,
|
|
27
27
|
isMentioned,
|
|
28
|
+
milkyInboundConversation,
|
|
28
29
|
parseMessageReceiveData,
|
|
29
30
|
parseMilkyMessageId,
|
|
30
31
|
senderNickname,
|
|
@@ -106,15 +107,15 @@ export class MilkySseEndpoint implements EndpointInstance {
|
|
|
106
107
|
this.#stream = undefined;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
async send({
|
|
110
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
110
111
|
const message = formatOutboundSegments(payload);
|
|
111
|
-
const { action, params } = buildSendAction(
|
|
112
|
+
const { action, params } = buildSendAction(conversation, message);
|
|
112
113
|
const data = await this.callApi(action, params) as { message_seq?: number } | undefined;
|
|
113
|
-
const messageId = formatOutboundMessageId(
|
|
114
|
+
const messageId = formatOutboundMessageId(conversation, data?.message_seq);
|
|
114
115
|
logger.debug(formatCompact({
|
|
115
116
|
op: 'milky_send',
|
|
116
117
|
endpoint: this.#options.config.name,
|
|
117
|
-
target
|
|
118
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
118
119
|
messageId,
|
|
119
120
|
mode: 'sse',
|
|
120
121
|
}));
|
|
@@ -218,19 +219,19 @@ export class MilkySseEndpoint implements EndpointInstance {
|
|
|
218
219
|
}
|
|
219
220
|
|
|
220
221
|
#admitMessage(data: MilkyIncomingMessage, event: MilkyEvent): void {
|
|
221
|
-
const
|
|
222
|
+
const conversation = milkyInboundConversation(String(this.#options.id), data);
|
|
223
|
+
const target = `${conversation.kind}:${conversation.id}`;
|
|
222
224
|
const content = formatInboundContent(data);
|
|
223
225
|
const segments = formatInboundSegments(data);
|
|
224
226
|
const audioUrl = extractInboundAudioUrl(data);
|
|
225
227
|
const nickname = senderNickname(data);
|
|
226
228
|
const mentioned = isMentioned(data, event.self_id);
|
|
227
229
|
void this.#options.gateway.receive({
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
conversation,
|
|
231
|
+
message: { conversation, id: formatInboundMessageId(data) },
|
|
230
232
|
content,
|
|
231
233
|
segments,
|
|
232
234
|
sender: String(data.sender_id),
|
|
233
|
-
id: formatInboundMessageId(data),
|
|
234
235
|
metadata: Object.freeze({
|
|
235
236
|
message_scene: data.message_scene,
|
|
236
237
|
peer_id: String(data.peer_id),
|
package/src/webhook-endpoint.ts
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
* Milky webhook endpoint — httpHostToken POST inbound + baseUrl HTTP API outbound.
|
|
3
3
|
*/
|
|
4
4
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
EndpointInstance,
|
|
7
|
+
EndpointManagement,
|
|
8
|
+
EndpointSendRequest,
|
|
9
|
+
} from '@zhin.js/adapter';
|
|
6
10
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
7
11
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
8
12
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
@@ -17,10 +21,10 @@ import {
|
|
|
17
21
|
formatInboundContent,
|
|
18
22
|
formatInboundMessageId,
|
|
19
23
|
formatInboundSegments,
|
|
20
|
-
formatInboundTarget,
|
|
21
24
|
formatOutboundMessageId,
|
|
22
25
|
formatOutboundSegments,
|
|
23
26
|
isMentioned,
|
|
27
|
+
milkyInboundConversation,
|
|
24
28
|
parseMessageReceiveData,
|
|
25
29
|
parseMilkyMessageId,
|
|
26
30
|
senderNickname,
|
|
@@ -83,15 +87,15 @@ export class MilkyWebhookEndpoint implements EndpointInstance {
|
|
|
83
87
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
async send({
|
|
90
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
87
91
|
const message = formatOutboundSegments(payload);
|
|
88
|
-
const { action, params } = buildSendAction(
|
|
92
|
+
const { action, params } = buildSendAction(conversation, message);
|
|
89
93
|
const data = await this.callApi(action, params) as { message_seq?: number } | undefined;
|
|
90
|
-
const messageId = formatOutboundMessageId(
|
|
94
|
+
const messageId = formatOutboundMessageId(conversation, data?.message_seq);
|
|
91
95
|
logger.debug(formatCompact({
|
|
92
96
|
op: 'milky_send',
|
|
93
97
|
endpoint: this.#options.config.name,
|
|
94
|
-
target
|
|
98
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
95
99
|
messageId,
|
|
96
100
|
}));
|
|
97
101
|
return messageId;
|
|
@@ -194,19 +198,19 @@ export class MilkyWebhookEndpoint implements EndpointInstance {
|
|
|
194
198
|
}
|
|
195
199
|
|
|
196
200
|
#admitMessage(data: MilkyIncomingMessage, event: MilkyEvent): void {
|
|
197
|
-
const
|
|
201
|
+
const conversation = milkyInboundConversation(String(this.#options.id), data);
|
|
202
|
+
const target = `${conversation.kind}:${conversation.id}`;
|
|
198
203
|
const content = formatInboundContent(data);
|
|
199
204
|
const segments = formatInboundSegments(data);
|
|
200
205
|
const audioUrl = extractInboundAudioUrl(data);
|
|
201
206
|
const nickname = senderNickname(data);
|
|
202
207
|
const mentioned = isMentioned(data, event.self_id);
|
|
203
208
|
void this.#options.gateway.receive({
|
|
204
|
-
|
|
205
|
-
|
|
209
|
+
conversation,
|
|
210
|
+
message: { conversation, id: formatInboundMessageId(data) },
|
|
206
211
|
content,
|
|
207
212
|
segments,
|
|
208
213
|
sender: String(data.sender_id),
|
|
209
|
-
id: formatInboundMessageId(data),
|
|
210
214
|
metadata: Object.freeze({
|
|
211
215
|
message_scene: data.message_scene,
|
|
212
216
|
peer_id: String(data.peer_id),
|
package/src/ws-endpoint.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type EndpointInstance,
|
|
9
9
|
type EndpointLifecycle,
|
|
10
10
|
type EndpointManagement,
|
|
11
|
+
type EndpointSendRequest,
|
|
11
12
|
} from '@zhin.js/adapter';
|
|
12
13
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
13
14
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
@@ -22,10 +23,10 @@ import {
|
|
|
22
23
|
formatInboundContent,
|
|
23
24
|
formatInboundMessageId,
|
|
24
25
|
formatInboundSegments,
|
|
25
|
-
formatInboundTarget,
|
|
26
26
|
formatOutboundMessageId,
|
|
27
27
|
formatOutboundSegments,
|
|
28
28
|
isMentioned,
|
|
29
|
+
milkyInboundConversation,
|
|
29
30
|
parseMessageReceiveData,
|
|
30
31
|
parseMilkyMessageId,
|
|
31
32
|
senderNickname,
|
|
@@ -109,15 +110,15 @@ export class MilkyWsEndpoint implements EndpointInstance {
|
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
|
|
112
|
-
async send({
|
|
113
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
113
114
|
const message = formatOutboundSegments(payload);
|
|
114
|
-
const { action, params } = buildSendAction(
|
|
115
|
+
const { action, params } = buildSendAction(conversation, message);
|
|
115
116
|
const data = await this.callApi(action, params) as { message_seq?: number } | undefined;
|
|
116
|
-
const messageId = formatOutboundMessageId(
|
|
117
|
+
const messageId = formatOutboundMessageId(conversation, data?.message_seq);
|
|
117
118
|
logger.debug(formatCompact({
|
|
118
119
|
op: 'milky_send',
|
|
119
120
|
endpoint: this.#options.config.name,
|
|
120
|
-
target
|
|
121
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
121
122
|
messageId,
|
|
122
123
|
}));
|
|
123
124
|
return messageId;
|
|
@@ -222,19 +223,19 @@ export class MilkyWsEndpoint implements EndpointInstance {
|
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
#admitMessage(data: MilkyIncomingMessage, event: MilkyEvent): void {
|
|
225
|
-
const
|
|
226
|
+
const conversation = milkyInboundConversation(String(this.#options.id), data);
|
|
227
|
+
const target = `${conversation.kind}:${conversation.id}`;
|
|
226
228
|
const content = formatInboundContent(data);
|
|
227
229
|
const segments = formatInboundSegments(data);
|
|
228
230
|
const audioUrl = extractInboundAudioUrl(data);
|
|
229
231
|
const nickname = senderNickname(data);
|
|
230
232
|
const mentioned = isMentioned(data, event.self_id);
|
|
231
233
|
void this.#options.gateway.receive({
|
|
232
|
-
|
|
233
|
-
|
|
234
|
+
conversation,
|
|
235
|
+
message: { conversation, id: formatInboundMessageId(data) },
|
|
234
236
|
content,
|
|
235
237
|
segments,
|
|
236
238
|
sender: String(data.sender_id),
|
|
237
|
-
id: formatInboundMessageId(data),
|
|
238
239
|
metadata: Object.freeze({
|
|
239
240
|
message_scene: data.message_scene,
|
|
240
241
|
peer_id: String(data.peer_id),
|
package/src/wss-endpoint.ts
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
* Milky reverse WSS endpoint — httpHostToken WS upgrade inbound + baseUrl HTTP API outbound.
|
|
3
3
|
*/
|
|
4
4
|
import { clearInterval } from 'node:timers';
|
|
5
|
-
import type {
|
|
5
|
+
import type {
|
|
6
|
+
EndpointInstance,
|
|
7
|
+
EndpointManagement,
|
|
8
|
+
EndpointSendRequest,
|
|
9
|
+
} from '@zhin.js/adapter';
|
|
6
10
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
7
11
|
import type { HttpHost, WsConnection } from '@zhin.js/host-http';
|
|
8
12
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
@@ -17,10 +21,10 @@ import {
|
|
|
17
21
|
formatInboundContent,
|
|
18
22
|
formatInboundMessageId,
|
|
19
23
|
formatInboundSegments,
|
|
20
|
-
formatInboundTarget,
|
|
21
24
|
formatOutboundMessageId,
|
|
22
25
|
formatOutboundSegments,
|
|
23
26
|
isMentioned,
|
|
27
|
+
milkyInboundConversation,
|
|
24
28
|
parseMessageReceiveData,
|
|
25
29
|
parseMilkyMessageId,
|
|
26
30
|
senderNickname,
|
|
@@ -102,15 +106,15 @@ export class MilkyWssEndpoint implements EndpointInstance {
|
|
|
102
106
|
this.#started = false;
|
|
103
107
|
}
|
|
104
108
|
|
|
105
|
-
async send({
|
|
109
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
106
110
|
const message = formatOutboundSegments(payload);
|
|
107
|
-
const { action, params } = buildSendAction(
|
|
111
|
+
const { action, params } = buildSendAction(conversation, message);
|
|
108
112
|
const data = await this.callApi(action, params) as { message_seq?: number } | undefined;
|
|
109
|
-
const messageId = formatOutboundMessageId(
|
|
113
|
+
const messageId = formatOutboundMessageId(conversation, data?.message_seq);
|
|
110
114
|
logger.debug(formatCompact({
|
|
111
115
|
op: 'milky_send',
|
|
112
116
|
endpoint: this.#options.config.name,
|
|
113
|
-
target
|
|
117
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
114
118
|
messageId,
|
|
115
119
|
}));
|
|
116
120
|
return messageId;
|
|
@@ -213,19 +217,19 @@ export class MilkyWssEndpoint implements EndpointInstance {
|
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
#admitMessage(data: MilkyIncomingMessage, event: MilkyEvent): void {
|
|
216
|
-
const
|
|
220
|
+
const conversation = milkyInboundConversation(String(this.#options.id), data);
|
|
221
|
+
const target = `${conversation.kind}:${conversation.id}`;
|
|
217
222
|
const content = formatInboundContent(data);
|
|
218
223
|
const segments = formatInboundSegments(data);
|
|
219
224
|
const audioUrl = extractInboundAudioUrl(data);
|
|
220
225
|
const nickname = senderNickname(data);
|
|
221
226
|
const mentioned = isMentioned(data, event.self_id);
|
|
222
227
|
void this.#options.gateway.receive({
|
|
223
|
-
|
|
224
|
-
|
|
228
|
+
conversation,
|
|
229
|
+
message: { conversation, id: formatInboundMessageId(data) },
|
|
225
230
|
content,
|
|
226
231
|
segments,
|
|
227
232
|
sender: String(data.sender_id),
|
|
228
|
-
id: formatInboundMessageId(data),
|
|
229
233
|
metadata: Object.freeze({
|
|
230
234
|
message_scene: data.message_scene,
|
|
231
235
|
peer_id: String(data.peer_id),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|