@zhin.js/adapter-milky 6.0.14 → 7.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/CHANGELOG.md +37 -0
- package/README.md +24 -3
- package/adapters/milky.js +1 -11
- package/adapters/milky.ts +1 -11
- package/lib/client.d.ts +21 -0
- package/lib/client.js +33 -0
- package/lib/endpoint.d.ts +1 -1
- package/lib/endpoint.js +1 -1
- package/lib/index.d.ts +5 -2
- package/lib/index.js +3 -2
- package/lib/milky-auth.d.ts +0 -1
- package/lib/milky-auth.js +0 -14
- package/lib/milky-endpoint-commands.d.ts +1 -1
- package/lib/protocol.d.ts +3 -3
- package/lib/protocol.js +2 -5
- package/lib/sse-endpoint.d.ts +6 -23
- package/lib/sse-endpoint.js +25 -87
- package/lib/webhook-endpoint.d.ts +6 -23
- package/lib/webhook-endpoint.js +29 -96
- package/lib/ws-endpoint.d.ts +6 -24
- package/lib/ws-endpoint.js +36 -116
- package/lib/wss-endpoint.d.ts +9 -23
- package/lib/wss-endpoint.js +65 -148
- package/package.json +16 -13
- package/src/client.ts +80 -0
- package/src/endpoint.ts +1 -1
- package/src/index.ts +17 -7
- package/src/milky-auth.ts +0 -15
- package/src/protocol.ts +4 -7
- package/src/sse-endpoint.ts +30 -102
- package/src/webhook-endpoint.ts +37 -113
- package/src/ws-endpoint.ts +46 -128
- package/src/wss-endpoint.ts +73 -161
- package/lib/milky-agent-deps.d.ts +0 -24
- package/lib/milky-agent-deps.js +0 -30
- package/src/milky-agent-deps.ts +0 -53
|
@@ -1,39 +1,22 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
1
|
+
import { ClientEndpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
3
2
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
4
3
|
import type { CapabilityId } from 'zhin.js';
|
|
5
|
-
import {
|
|
4
|
+
import { type MilkyClient } from './client.js';
|
|
5
|
+
import { callApi, type MilkyWebhookConfig } from './protocol.js';
|
|
6
6
|
export interface MilkyWebhookEndpointOptions {
|
|
7
7
|
readonly id: CapabilityId;
|
|
8
|
-
readonly gateway: MessageGateway;
|
|
9
|
-
readonly sideEvents?: SideEventGateway;
|
|
10
8
|
readonly http: HttpHost;
|
|
11
9
|
readonly config: MilkyWebhookConfig;
|
|
12
10
|
readonly callApi?: typeof callApi;
|
|
13
11
|
}
|
|
14
|
-
export declare class MilkyWebhookEndpoint
|
|
12
|
+
export declare class MilkyWebhookEndpoint extends ClientEndpoint<MilkyClient> {
|
|
15
13
|
#private;
|
|
14
|
+
readonly client: MilkyClient;
|
|
16
15
|
readonly management: EndpointManagement;
|
|
16
|
+
readonly control: EndpointControl;
|
|
17
17
|
constructor(options: MilkyWebhookEndpointOptions);
|
|
18
18
|
start(): Promise<void>;
|
|
19
|
-
open(): void;
|
|
20
|
-
close(): void;
|
|
21
19
|
stop(): Promise<void>;
|
|
22
20
|
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
23
|
-
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
24
21
|
recallMessage(id: string): Promise<void>;
|
|
25
|
-
kickMember(groupId: number, userId: number, rejectAddRequest?: boolean): Promise<boolean>;
|
|
26
|
-
muteMember(groupId: number, userId: number, duration?: number): Promise<boolean>;
|
|
27
|
-
muteAll(groupId: number, enable?: boolean): Promise<boolean>;
|
|
28
|
-
setAdmin(groupId: number, userId: number, enable?: boolean): Promise<boolean>;
|
|
29
|
-
setCard(groupId: number, userId: number, card: string): Promise<boolean>;
|
|
30
|
-
setTitle(groupId: number, userId: number, title: string): Promise<boolean>;
|
|
31
|
-
setGroupName(groupId: number, name: string): Promise<boolean>;
|
|
32
|
-
getMemberList(groupId: number): Promise<unknown[]>;
|
|
33
|
-
getGroupInfo(groupId: number): Promise<unknown>;
|
|
34
|
-
admit(event: MilkyEvent): void;
|
|
35
|
-
apiOptions(): {
|
|
36
|
-
baseUrl: string;
|
|
37
|
-
access_token?: string;
|
|
38
|
-
};
|
|
39
22
|
}
|
package/lib/webhook-endpoint.js
CHANGED
|
@@ -1,27 +1,36 @@
|
|
|
1
|
+
import { ClientEndpoint, createRecallEndpointControl, } from 'zhin.js/adapter';
|
|
1
2
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
2
|
-
import {
|
|
3
|
+
import { verifyMilkyAccessToken } from './milky-auth.js';
|
|
3
4
|
import { createMilkyEndpointManagement } from './endpoint-management.js';
|
|
4
|
-
import {
|
|
5
|
+
import { callMilkyClient, createMilkyEndpointClient, forwardMilkyClientEvents } from './client.js';
|
|
5
6
|
import { buildSendAction, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundSegments, formatOutboundMessageId, formatOutboundSegments, isMentioned, milkyInboundConversation, parseMessageReceiveData, parseMilkyMessageId, senderNickname, } from './protocol.js';
|
|
6
|
-
export class MilkyWebhookEndpoint {
|
|
7
|
+
export class MilkyWebhookEndpoint extends ClientEndpoint {
|
|
8
|
+
client;
|
|
7
9
|
#logger;
|
|
8
10
|
#options;
|
|
9
11
|
#callApi;
|
|
10
|
-
management
|
|
12
|
+
management;
|
|
13
|
+
control = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
11
14
|
#routeReleases = [];
|
|
12
|
-
#open = false;
|
|
13
15
|
#started = false;
|
|
14
|
-
#unregisterAgent;
|
|
15
16
|
constructor(options) {
|
|
17
|
+
super();
|
|
16
18
|
this.#logger = getAdapterLogger('milky', options.config.id);
|
|
17
19
|
this.#options = options;
|
|
18
20
|
this.#callApi = options.callApi ?? callApi;
|
|
21
|
+
this.client = createMilkyEndpointClient(options.config, this.#callApi);
|
|
22
|
+
this.management = createMilkyEndpointManagement({
|
|
23
|
+
callApi: (action, params) => callMilkyClient(this.client, action, params),
|
|
24
|
+
});
|
|
25
|
+
this.bindClientEvents((receive) => forwardMilkyClientEvents(this.client, receive), (name, payload) => {
|
|
26
|
+
if (name === 'event')
|
|
27
|
+
this.#admitRaw(payload);
|
|
28
|
+
}, (_name, error) => this.#warnPlatformEvent(error));
|
|
19
29
|
}
|
|
20
30
|
async start() {
|
|
21
31
|
if (this.#started)
|
|
22
32
|
return;
|
|
23
33
|
this.#started = true;
|
|
24
|
-
this.#unregisterAgent = registerMilkyAgentEndpoint(this.#options.config.id, this);
|
|
25
34
|
this.#setupRoutes();
|
|
26
35
|
this.#logger.info(formatCompact({
|
|
27
36
|
op: 'listen',
|
|
@@ -30,25 +39,17 @@ export class MilkyWebhookEndpoint {
|
|
|
30
39
|
path: this.#options.config.path,
|
|
31
40
|
}));
|
|
32
41
|
}
|
|
33
|
-
open() {
|
|
34
|
-
this.#open = true;
|
|
35
|
-
}
|
|
36
|
-
close() {
|
|
37
|
-
this.#open = false;
|
|
38
|
-
}
|
|
39
42
|
async stop() {
|
|
40
|
-
this
|
|
43
|
+
this.close();
|
|
41
44
|
for (const release of this.#routeReleases.splice(0))
|
|
42
45
|
release();
|
|
43
|
-
this.#unregisterAgent?.();
|
|
44
|
-
this.#unregisterAgent = undefined;
|
|
45
46
|
this.#started = false;
|
|
46
47
|
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
47
48
|
}
|
|
48
49
|
async send({ conversation, payload }) {
|
|
49
50
|
const message = formatOutboundSegments(payload);
|
|
50
51
|
const { action, params } = buildSendAction(conversation, message);
|
|
51
|
-
const data = await this.
|
|
52
|
+
const data = await callMilkyClient(this.client, action, params);
|
|
52
53
|
const messageId = formatOutboundMessageId(conversation, data?.message_seq);
|
|
53
54
|
this.#logger.debug(formatCompact({
|
|
54
55
|
op: 'milky_send',
|
|
@@ -58,91 +59,31 @@ export class MilkyWebhookEndpoint {
|
|
|
58
59
|
}));
|
|
59
60
|
return messageId;
|
|
60
61
|
}
|
|
61
|
-
callApi(action, params = {}) {
|
|
62
|
-
return this.#callApi(this.apiOptions(), action, params);
|
|
63
|
-
}
|
|
64
62
|
async recallMessage(id) {
|
|
65
63
|
const parsed = parseMilkyMessageId(id);
|
|
66
64
|
if (!parsed)
|
|
67
65
|
throw new Error(`Invalid message id: ${id}`);
|
|
68
66
|
if (parsed.message_scene === 'group') {
|
|
69
|
-
await this.
|
|
67
|
+
await callMilkyClient(this.client, 'recall_group_message', {
|
|
70
68
|
group_id: parsed.peer_id,
|
|
71
69
|
message_seq: parsed.message_seq,
|
|
72
70
|
});
|
|
73
71
|
}
|
|
74
72
|
else {
|
|
75
|
-
await this.
|
|
73
|
+
await callMilkyClient(this.client, 'recall_private_message', {
|
|
76
74
|
user_id: parsed.peer_id,
|
|
77
75
|
message_seq: parsed.message_seq,
|
|
78
76
|
});
|
|
79
77
|
}
|
|
80
78
|
}
|
|
81
|
-
|
|
82
|
-
await this.callApi('kick_group_member', {
|
|
83
|
-
group_id: groupId,
|
|
84
|
-
user_id: userId,
|
|
85
|
-
reject_add_request: rejectAddRequest,
|
|
86
|
-
});
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
async muteMember(groupId, userId, duration = 600) {
|
|
90
|
-
await this.callApi('set_group_member_mute', {
|
|
91
|
-
group_id: groupId,
|
|
92
|
-
user_id: userId,
|
|
93
|
-
duration,
|
|
94
|
-
});
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
|
-
async muteAll(groupId, enable = true) {
|
|
98
|
-
await this.callApi('set_group_whole_mute', { group_id: groupId, is_mute: enable });
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
async setAdmin(groupId, userId, enable = true) {
|
|
102
|
-
await this.callApi('set_group_member_admin', {
|
|
103
|
-
group_id: groupId,
|
|
104
|
-
user_id: userId,
|
|
105
|
-
is_set: enable,
|
|
106
|
-
});
|
|
107
|
-
return true;
|
|
108
|
-
}
|
|
109
|
-
async setCard(groupId, userId, card) {
|
|
110
|
-
await this.callApi('set_group_member_card', {
|
|
111
|
-
group_id: groupId,
|
|
112
|
-
user_id: userId,
|
|
113
|
-
card,
|
|
114
|
-
});
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
async setTitle(groupId, userId, title) {
|
|
118
|
-
await this.callApi('set_group_member_special_title', {
|
|
119
|
-
group_id: groupId,
|
|
120
|
-
user_id: userId,
|
|
121
|
-
special_title: title,
|
|
122
|
-
});
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
async setGroupName(groupId, name) {
|
|
126
|
-
await this.callApi('set_group_name', { group_id: groupId, new_group_name: name });
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
async getMemberList(groupId) {
|
|
130
|
-
return this.callApi('get_group_member_list', { group_id: groupId });
|
|
131
|
-
}
|
|
132
|
-
async getGroupInfo(groupId) {
|
|
133
|
-
return this.callApi('get_group_info', { group_id: groupId });
|
|
134
|
-
}
|
|
135
|
-
admit(event) {
|
|
79
|
+
#admitRaw(event) {
|
|
136
80
|
const data = parseMessageReceiveData(event);
|
|
137
|
-
if (!
|
|
81
|
+
if (!data)
|
|
138
82
|
return;
|
|
139
83
|
this.#admitMessage(data, event);
|
|
140
84
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
baseUrl: this.#options.config.baseUrl,
|
|
144
|
-
access_token: this.#options.config.access_token,
|
|
145
|
-
};
|
|
85
|
+
#warnPlatformEvent(error) {
|
|
86
|
+
this.#logger.warn(formatCompact({ op: 'milky_platform_event_failed', error: error instanceof Error ? error.message : String(error) }));
|
|
146
87
|
}
|
|
147
88
|
#admitMessage(data, event) {
|
|
148
89
|
const conversation = milkyInboundConversation(String(this.#options.id), data);
|
|
@@ -152,7 +93,7 @@ export class MilkyWebhookEndpoint {
|
|
|
152
93
|
const audioUrl = extractInboundAudioUrl(data);
|
|
153
94
|
const nickname = senderNickname(data);
|
|
154
95
|
const mentioned = isMentioned(data, event.self_id);
|
|
155
|
-
void this
|
|
96
|
+
void this.emit('message.receive', {
|
|
156
97
|
conversation,
|
|
157
98
|
message: { conversation, id: formatInboundMessageId(data) },
|
|
158
99
|
content,
|
|
@@ -190,20 +131,12 @@ export class MilkyWebhookEndpoint {
|
|
|
190
131
|
response.end(JSON.stringify({ message: 'Unauthorized' }));
|
|
191
132
|
return;
|
|
192
133
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
event = JSON.parse(raw);
|
|
197
|
-
}
|
|
198
|
-
catch {
|
|
199
|
-
response.writeHead(400, { 'Content-Type': 'application/json' });
|
|
200
|
-
response.end(JSON.stringify({ message: 'Invalid JSON' }));
|
|
134
|
+
if (!this.clientEventsOpen) {
|
|
135
|
+
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
136
|
+
response.end(JSON.stringify({ status: 'ok' }));
|
|
201
137
|
return;
|
|
202
138
|
}
|
|
203
|
-
|
|
204
|
-
this.admit(event);
|
|
205
|
-
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
206
|
-
response.end(JSON.stringify({ status: 'ok' }));
|
|
139
|
+
await this.client.acceptHttp(request, response);
|
|
207
140
|
}
|
|
208
141
|
catch (error) {
|
|
209
142
|
this.#logger.error('Milky webhook error:', error);
|
package/lib/ws-endpoint.d.ts
CHANGED
|
@@ -1,41 +1,23 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
1
|
+
import { ClientEndpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
3
2
|
import type { CapabilityId } from 'zhin.js';
|
|
4
|
-
import {
|
|
3
|
+
import { type MilkyClient } from './client.js';
|
|
4
|
+
import { callApi, type MilkyWsConfig } from './protocol.js';
|
|
5
5
|
import type { MilkyWsCreateOptions, MilkyWsSocket } from './ws-types.js';
|
|
6
6
|
export interface MilkyWsEndpointOptions {
|
|
7
7
|
readonly id: CapabilityId;
|
|
8
|
-
readonly gateway: MessageGateway;
|
|
9
|
-
readonly sideEvents?: SideEventGateway;
|
|
10
8
|
readonly config: MilkyWsConfig;
|
|
11
9
|
readonly createWebSocket?: (url: string, options: MilkyWsCreateOptions) => MilkyWsSocket;
|
|
12
10
|
readonly callApi?: typeof callApi;
|
|
13
11
|
}
|
|
14
|
-
export declare class MilkyWsEndpoint
|
|
12
|
+
export declare class MilkyWsEndpoint extends ClientEndpoint<MilkyClient> {
|
|
15
13
|
#private;
|
|
14
|
+
readonly client: MilkyClient;
|
|
16
15
|
readonly management: EndpointManagement;
|
|
16
|
+
readonly control: EndpointControl;
|
|
17
17
|
constructor(options: MilkyWsEndpointOptions);
|
|
18
18
|
start(): Promise<void>;
|
|
19
|
-
open(): void;
|
|
20
19
|
close(): void;
|
|
21
20
|
stop(): Promise<void>;
|
|
22
21
|
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
23
|
-
/** Public API for agent tools / callers. */
|
|
24
|
-
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
25
22
|
recallMessage(id: string): Promise<void>;
|
|
26
|
-
kickMember(groupId: number, userId: number, rejectAddRequest?: boolean): Promise<boolean>;
|
|
27
|
-
muteMember(groupId: number, userId: number, duration?: number): Promise<boolean>;
|
|
28
|
-
muteAll(groupId: number, enable?: boolean): Promise<boolean>;
|
|
29
|
-
setAdmin(groupId: number, userId: number, enable?: boolean): Promise<boolean>;
|
|
30
|
-
setCard(groupId: number, userId: number, card: string): Promise<boolean>;
|
|
31
|
-
setTitle(groupId: number, userId: number, title: string): Promise<boolean>;
|
|
32
|
-
setGroupName(groupId: number, name: string): Promise<boolean>;
|
|
33
|
-
getMemberList(groupId: number): Promise<unknown[]>;
|
|
34
|
-
getGroupInfo(groupId: number): Promise<unknown>;
|
|
35
|
-
/** Test / internal: admit a parsed event when the endpoint is open. */
|
|
36
|
-
admit(event: MilkyEvent): void;
|
|
37
|
-
apiOptions(): {
|
|
38
|
-
baseUrl: string;
|
|
39
|
-
access_token?: string;
|
|
40
|
-
};
|
|
41
23
|
}
|
package/lib/ws-endpoint.js
CHANGED
|
@@ -2,25 +2,35 @@
|
|
|
2
2
|
* Milky WS client endpoint — outbound connect to Milky protocol server.
|
|
3
3
|
*/
|
|
4
4
|
import WebSocket from 'ws';
|
|
5
|
-
import { createEndpointLifecycle, } from 'zhin.js/adapter';
|
|
5
|
+
import { ClientEndpoint, createRecallEndpointControl, createEndpointLifecycle, } from 'zhin.js/adapter';
|
|
6
6
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
7
7
|
import { createMilkyEndpointManagement } from './endpoint-management.js';
|
|
8
|
-
import {
|
|
8
|
+
import { callMilkyClient, createMilkyEndpointClient, forwardMilkyClientEvents, } from './client.js';
|
|
9
9
|
import { buildSendAction, buildWsConnectOptions, callApi, extractInboundAudioUrl, formatInboundContent, formatInboundMessageId, formatInboundSegments, formatOutboundMessageId, formatOutboundSegments, isMentioned, milkyInboundConversation, parseMessageReceiveData, parseMilkyMessageId, senderNickname, } from './protocol.js';
|
|
10
10
|
const WS_OPEN = 1;
|
|
11
|
-
export class MilkyWsEndpoint {
|
|
11
|
+
export class MilkyWsEndpoint extends ClientEndpoint {
|
|
12
|
+
client;
|
|
12
13
|
#logger;
|
|
13
14
|
#options;
|
|
14
15
|
#callApi;
|
|
15
|
-
management
|
|
16
|
+
management;
|
|
17
|
+
control = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
16
18
|
#lifecycle;
|
|
17
19
|
#ws;
|
|
18
|
-
#
|
|
19
|
-
#unregisterAgent;
|
|
20
|
+
#clientSocketRelease;
|
|
20
21
|
constructor(options) {
|
|
22
|
+
super();
|
|
21
23
|
this.#logger = getAdapterLogger('milky', options.config.id);
|
|
22
24
|
this.#options = options;
|
|
23
25
|
this.#callApi = options.callApi ?? callApi;
|
|
26
|
+
this.client = createMilkyEndpointClient(options.config, this.#callApi);
|
|
27
|
+
this.management = createMilkyEndpointManagement({
|
|
28
|
+
callApi: (action, params) => callMilkyClient(this.client, action, params),
|
|
29
|
+
});
|
|
30
|
+
this.bindClientEvents((receive) => forwardMilkyClientEvents(this.client, receive), (name, payload) => {
|
|
31
|
+
if (name === 'event')
|
|
32
|
+
this.#admitRaw(payload);
|
|
33
|
+
}, (_name, error) => this.#warnPlatformEvent(error));
|
|
24
34
|
this.#lifecycle = createEndpointLifecycle({
|
|
25
35
|
name: options.config.id,
|
|
26
36
|
// reconnect_interval 旧语义为固定间隔:multiplier 1 + 无 jitter + 不封顶
|
|
@@ -35,28 +45,16 @@ export class MilkyWsEndpoint {
|
|
|
35
45
|
async start() {
|
|
36
46
|
if (this.#lifecycle.started)
|
|
37
47
|
return;
|
|
38
|
-
this.#
|
|
39
|
-
try {
|
|
40
|
-
await this.#lifecycle.start((handle) => this.#connect(handle));
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
// start 失败复位由基座保证;agent 注册/反注册是适配器专有依赖,留在适配器侧
|
|
44
|
-
this.#unregisterAgent?.();
|
|
45
|
-
this.#unregisterAgent = undefined;
|
|
46
|
-
throw err;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
open() {
|
|
50
|
-
this.#open = true;
|
|
48
|
+
await this.#lifecycle.start((handle) => this.#connect(handle));
|
|
51
49
|
}
|
|
52
50
|
close() {
|
|
53
|
-
|
|
51
|
+
super.close();
|
|
52
|
+
this.#clientSocketRelease?.();
|
|
53
|
+
this.#clientSocketRelease = undefined;
|
|
54
54
|
}
|
|
55
55
|
async stop() {
|
|
56
|
-
this
|
|
56
|
+
this.close();
|
|
57
57
|
await this.#lifecycle.stop();
|
|
58
|
-
this.#unregisterAgent?.();
|
|
59
|
-
this.#unregisterAgent = undefined;
|
|
60
58
|
if (this.#ws) {
|
|
61
59
|
try {
|
|
62
60
|
this.#ws.close();
|
|
@@ -70,7 +68,7 @@ export class MilkyWsEndpoint {
|
|
|
70
68
|
async send({ conversation, payload }) {
|
|
71
69
|
const message = formatOutboundSegments(payload);
|
|
72
70
|
const { action, params } = buildSendAction(conversation, message);
|
|
73
|
-
const data = await this.
|
|
71
|
+
const data = await callMilkyClient(this.client, action, params);
|
|
74
72
|
const messageId = formatOutboundMessageId(conversation, data?.message_seq);
|
|
75
73
|
this.#logger.debug(formatCompact({
|
|
76
74
|
op: 'milky_send',
|
|
@@ -80,93 +78,34 @@ export class MilkyWsEndpoint {
|
|
|
80
78
|
}));
|
|
81
79
|
return messageId;
|
|
82
80
|
}
|
|
83
|
-
/** Public API for agent tools / callers. */
|
|
84
|
-
callApi(action, params = {}) {
|
|
85
|
-
return this.#callApi(this.apiOptions(), action, params);
|
|
86
|
-
}
|
|
87
81
|
async recallMessage(id) {
|
|
88
82
|
const parsed = parseMilkyMessageId(id);
|
|
89
83
|
if (!parsed)
|
|
90
84
|
throw new Error(`Invalid message id: ${id}`);
|
|
91
85
|
if (parsed.message_scene === 'group') {
|
|
92
|
-
await this.
|
|
86
|
+
await callMilkyClient(this.client, 'recall_group_message', {
|
|
93
87
|
group_id: parsed.peer_id,
|
|
94
88
|
message_seq: parsed.message_seq,
|
|
95
89
|
});
|
|
96
90
|
}
|
|
97
91
|
else {
|
|
98
|
-
await this.
|
|
92
|
+
await callMilkyClient(this.client, 'recall_private_message', {
|
|
99
93
|
user_id: parsed.peer_id,
|
|
100
94
|
message_seq: parsed.message_seq,
|
|
101
95
|
});
|
|
102
96
|
}
|
|
103
97
|
}
|
|
104
|
-
|
|
105
|
-
await this.callApi('kick_group_member', {
|
|
106
|
-
group_id: groupId,
|
|
107
|
-
user_id: userId,
|
|
108
|
-
reject_add_request: rejectAddRequest,
|
|
109
|
-
});
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
async muteMember(groupId, userId, duration = 600) {
|
|
113
|
-
await this.callApi('set_group_member_mute', {
|
|
114
|
-
group_id: groupId,
|
|
115
|
-
user_id: userId,
|
|
116
|
-
duration,
|
|
117
|
-
});
|
|
118
|
-
return true;
|
|
119
|
-
}
|
|
120
|
-
async muteAll(groupId, enable = true) {
|
|
121
|
-
await this.callApi('set_group_whole_mute', { group_id: groupId, is_mute: enable });
|
|
122
|
-
return true;
|
|
123
|
-
}
|
|
124
|
-
async setAdmin(groupId, userId, enable = true) {
|
|
125
|
-
await this.callApi('set_group_member_admin', {
|
|
126
|
-
group_id: groupId,
|
|
127
|
-
user_id: userId,
|
|
128
|
-
is_set: enable,
|
|
129
|
-
});
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
async setCard(groupId, userId, card) {
|
|
133
|
-
await this.callApi('set_group_member_card', {
|
|
134
|
-
group_id: groupId,
|
|
135
|
-
user_id: userId,
|
|
136
|
-
card,
|
|
137
|
-
});
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
async setTitle(groupId, userId, title) {
|
|
141
|
-
await this.callApi('set_group_member_special_title', {
|
|
142
|
-
group_id: groupId,
|
|
143
|
-
user_id: userId,
|
|
144
|
-
special_title: title,
|
|
145
|
-
});
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
async setGroupName(groupId, name) {
|
|
149
|
-
await this.callApi('set_group_name', { group_id: groupId, new_group_name: name });
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
async getMemberList(groupId) {
|
|
153
|
-
return this.callApi('get_group_member_list', { group_id: groupId });
|
|
154
|
-
}
|
|
155
|
-
async getGroupInfo(groupId) {
|
|
156
|
-
return this.callApi('get_group_info', { group_id: groupId });
|
|
157
|
-
}
|
|
158
|
-
/** Test / internal: admit a parsed event when the endpoint is open. */
|
|
159
|
-
admit(event) {
|
|
98
|
+
#admitRaw(event) {
|
|
160
99
|
const data = parseMessageReceiveData(event);
|
|
161
|
-
if (!
|
|
100
|
+
if (!data)
|
|
162
101
|
return;
|
|
163
102
|
this.#admitMessage(data, event);
|
|
164
103
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
};
|
|
104
|
+
#warnPlatformEvent(error) {
|
|
105
|
+
this.#logger.warn(formatCompact({
|
|
106
|
+
op: 'milky_platform_event_failed',
|
|
107
|
+
error: error instanceof Error ? error.message : String(error),
|
|
108
|
+
}));
|
|
170
109
|
}
|
|
171
110
|
#admitMessage(data, event) {
|
|
172
111
|
const conversation = milkyInboundConversation(String(this.#options.id), data);
|
|
@@ -176,7 +115,7 @@ export class MilkyWsEndpoint {
|
|
|
176
115
|
const audioUrl = extractInboundAudioUrl(data);
|
|
177
116
|
const nickname = senderNickname(data);
|
|
178
117
|
const mentioned = isMentioned(data, event.self_id);
|
|
179
|
-
void this
|
|
118
|
+
void this.emit('message.receive', {
|
|
180
119
|
conversation,
|
|
181
120
|
message: { conversation, id: formatInboundMessageId(data) },
|
|
182
121
|
content,
|
|
@@ -247,10 +186,11 @@ export class MilkyWsEndpoint {
|
|
|
247
186
|
}
|
|
248
187
|
resolve();
|
|
249
188
|
});
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
});
|
|
189
|
+
this.#clientSocketRelease?.();
|
|
190
|
+
this.#clientSocketRelease = this.client.acceptWebSocket(ws);
|
|
253
191
|
ws.on('close', (code, reason) => {
|
|
192
|
+
this.#clientSocketRelease?.();
|
|
193
|
+
this.#clientSocketRelease = undefined;
|
|
254
194
|
const reasonStr = typeof reason === 'string'
|
|
255
195
|
? reason
|
|
256
196
|
: Buffer.isBuffer(reason)
|
|
@@ -290,24 +230,4 @@ export class MilkyWsEndpoint {
|
|
|
290
230
|
});
|
|
291
231
|
});
|
|
292
232
|
}
|
|
293
|
-
#onMessage(data) {
|
|
294
|
-
try {
|
|
295
|
-
const raw = typeof data === 'string'
|
|
296
|
-
? data
|
|
297
|
-
: Buffer.isBuffer(data)
|
|
298
|
-
? data.toString()
|
|
299
|
-
: data instanceof ArrayBuffer
|
|
300
|
-
? new TextDecoder().decode(data)
|
|
301
|
-
: String(data ?? '');
|
|
302
|
-
const event = JSON.parse(raw);
|
|
303
|
-
this.admit(event);
|
|
304
|
-
}
|
|
305
|
-
catch (error) {
|
|
306
|
-
this.#logger.warn(formatCompact({
|
|
307
|
-
op: 'milky_parse_failed',
|
|
308
|
-
endpoint: this.#options.config.id,
|
|
309
|
-
error: error instanceof Error ? error.message : String(error),
|
|
310
|
-
}));
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
233
|
}
|
package/lib/wss-endpoint.d.ts
CHANGED
|
@@ -1,39 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Milky reverse WSS endpoint — httpHostToken WS upgrade inbound + baseUrl HTTP API outbound.
|
|
3
|
+
*/
|
|
4
|
+
import { ClientEndpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
3
5
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
4
6
|
import type { CapabilityId } from 'zhin.js';
|
|
5
|
-
import {
|
|
7
|
+
import { type MilkyClient } from './client.js';
|
|
8
|
+
import { callApi, type MilkyWssConfig } from './protocol.js';
|
|
6
9
|
export interface MilkyWssEndpointOptions {
|
|
7
10
|
readonly id: CapabilityId;
|
|
8
|
-
readonly gateway: MessageGateway;
|
|
9
|
-
readonly sideEvents?: SideEventGateway;
|
|
10
11
|
readonly http: HttpHost;
|
|
11
12
|
readonly config: MilkyWssConfig;
|
|
12
13
|
readonly callApi?: typeof callApi;
|
|
13
14
|
}
|
|
14
|
-
export declare class MilkyWssEndpoint
|
|
15
|
+
export declare class MilkyWssEndpoint extends ClientEndpoint<MilkyClient> {
|
|
15
16
|
#private;
|
|
17
|
+
readonly client: MilkyClient;
|
|
16
18
|
readonly management: EndpointManagement;
|
|
19
|
+
readonly control: EndpointControl;
|
|
17
20
|
constructor(options: MilkyWssEndpointOptions);
|
|
18
21
|
start(): Promise<void>;
|
|
19
|
-
open(): void;
|
|
20
|
-
close(): void;
|
|
21
22
|
stop(): Promise<void>;
|
|
22
23
|
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
23
|
-
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
24
24
|
recallMessage(id: string): Promise<void>;
|
|
25
|
-
kickMember(groupId: number, userId: number, rejectAddRequest?: boolean): Promise<boolean>;
|
|
26
|
-
muteMember(groupId: number, userId: number, duration?: number): Promise<boolean>;
|
|
27
|
-
muteAll(groupId: number, enable?: boolean): Promise<boolean>;
|
|
28
|
-
setAdmin(groupId: number, userId: number, enable?: boolean): Promise<boolean>;
|
|
29
|
-
setCard(groupId: number, userId: number, card: string): Promise<boolean>;
|
|
30
|
-
setTitle(groupId: number, userId: number, title: string): Promise<boolean>;
|
|
31
|
-
setGroupName(groupId: number, name: string): Promise<boolean>;
|
|
32
|
-
getMemberList(groupId: number): Promise<unknown[]>;
|
|
33
|
-
getGroupInfo(groupId: number): Promise<unknown>;
|
|
34
|
-
admit(event: MilkyEvent): void;
|
|
35
|
-
apiOptions(): {
|
|
36
|
-
baseUrl: string;
|
|
37
|
-
access_token?: string;
|
|
38
|
-
};
|
|
39
25
|
}
|