@zhin.js/adapter-milky 7.0.0 → 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 +23 -0
- package/README.md +24 -3
- package/adapters/milky.js +0 -11
- package/adapters/milky.ts +0 -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 +5 -23
- package/lib/sse-endpoint.js +24 -87
- package/lib/webhook-endpoint.d.ts +5 -23
- package/lib/webhook-endpoint.js +28 -97
- package/lib/ws-endpoint.d.ts +5 -24
- package/lib/ws-endpoint.js +35 -116
- package/lib/wss-endpoint.d.ts +8 -23
- package/lib/wss-endpoint.js +64 -149
- 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 +27 -102
- package/src/webhook-endpoint.ts +31 -110
- package/src/ws-endpoint.ts +43 -128
- package/src/wss-endpoint.ts +67 -158
- 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
package/src/ws-endpoint.ts
CHANGED
|
@@ -3,20 +3,24 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import WebSocket from 'ws';
|
|
5
5
|
import {
|
|
6
|
+
ClientEndpoint,
|
|
6
7
|
createRecallEndpointControl,
|
|
7
8
|
createEndpointLifecycle,
|
|
8
9
|
type EndpointConnectHandle,
|
|
9
10
|
type EndpointControl,
|
|
10
|
-
type EndpointInstance,
|
|
11
11
|
type EndpointLifecycle,
|
|
12
12
|
type EndpointManagement,
|
|
13
13
|
type EndpointSendRequest,
|
|
14
14
|
} from 'zhin.js/adapter';
|
|
15
|
-
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
16
15
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
17
16
|
import type { CapabilityId } from 'zhin.js';
|
|
18
17
|
import { createMilkyEndpointManagement } from './endpoint-management.js';
|
|
19
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
callMilkyClient,
|
|
20
|
+
createMilkyEndpointClient,
|
|
21
|
+
forwardMilkyClientEvents,
|
|
22
|
+
type MilkyClient,
|
|
23
|
+
} from './client.js';
|
|
20
24
|
import {
|
|
21
25
|
buildSendAction,
|
|
22
26
|
buildWsConnectOptions,
|
|
@@ -42,8 +46,6 @@ const WS_OPEN = 1;
|
|
|
42
46
|
|
|
43
47
|
export interface MilkyWsEndpointOptions {
|
|
44
48
|
readonly id: CapabilityId;
|
|
45
|
-
readonly gateway: MessageGateway;
|
|
46
|
-
readonly sideEvents?: SideEventGateway;
|
|
47
49
|
readonly config: MilkyWsConfig;
|
|
48
50
|
readonly createWebSocket?: (
|
|
49
51
|
url: string,
|
|
@@ -52,22 +54,34 @@ export interface MilkyWsEndpointOptions {
|
|
|
52
54
|
readonly callApi?: typeof callApi;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
export class MilkyWsEndpoint
|
|
57
|
+
export class MilkyWsEndpoint extends ClientEndpoint<MilkyClient> {
|
|
58
|
+
readonly client: MilkyClient;
|
|
56
59
|
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
57
60
|
|
|
58
61
|
readonly #options: MilkyWsEndpointOptions;
|
|
59
62
|
readonly #callApi: typeof callApi;
|
|
60
|
-
readonly management: EndpointManagement
|
|
63
|
+
readonly management: EndpointManagement;
|
|
61
64
|
readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
62
65
|
readonly #lifecycle: EndpointLifecycle;
|
|
63
66
|
#ws?: MilkyWsSocket;
|
|
64
|
-
#
|
|
65
|
-
#unregisterAgent?: () => void;
|
|
67
|
+
#clientSocketRelease?: () => void;
|
|
66
68
|
|
|
67
69
|
constructor(options: MilkyWsEndpointOptions) {
|
|
70
|
+
super();
|
|
68
71
|
this.#logger = getAdapterLogger('milky', options.config.id);
|
|
69
72
|
this.#options = options;
|
|
70
73
|
this.#callApi = options.callApi ?? callApi;
|
|
74
|
+
this.client = createMilkyEndpointClient(options.config, this.#callApi);
|
|
75
|
+
this.management = createMilkyEndpointManagement({
|
|
76
|
+
callApi: (action, params) => callMilkyClient(this.client, action, params),
|
|
77
|
+
});
|
|
78
|
+
this.bindClientEvents(
|
|
79
|
+
(receive) => forwardMilkyClientEvents(this.client, receive),
|
|
80
|
+
(name, payload) => {
|
|
81
|
+
if (name === 'event') this.#admitRaw(payload as MilkyEvent);
|
|
82
|
+
},
|
|
83
|
+
(_name, error) => this.#warnPlatformEvent(error),
|
|
84
|
+
);
|
|
71
85
|
this.#lifecycle = createEndpointLifecycle({
|
|
72
86
|
name: options.config.id,
|
|
73
87
|
// reconnect_interval 旧语义为固定间隔:multiplier 1 + 无 jitter + 不封顶
|
|
@@ -82,30 +96,18 @@ export class MilkyWsEndpoint implements EndpointInstance {
|
|
|
82
96
|
|
|
83
97
|
async start(): Promise<void> {
|
|
84
98
|
if (this.#lifecycle.started) return;
|
|
85
|
-
this.#
|
|
86
|
-
try {
|
|
87
|
-
await this.#lifecycle.start((handle) => this.#connect(handle));
|
|
88
|
-
} catch (err) {
|
|
89
|
-
// start 失败复位由基座保证;agent 注册/反注册是适配器专有依赖,留在适配器侧
|
|
90
|
-
this.#unregisterAgent?.();
|
|
91
|
-
this.#unregisterAgent = undefined;
|
|
92
|
-
throw err;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
open(): void {
|
|
97
|
-
this.#open = true;
|
|
99
|
+
await this.#lifecycle.start((handle) => this.#connect(handle));
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
close(): void {
|
|
101
|
-
|
|
103
|
+
super.close();
|
|
104
|
+
this.#clientSocketRelease?.();
|
|
105
|
+
this.#clientSocketRelease = undefined;
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
async stop(): Promise<void> {
|
|
105
|
-
this
|
|
109
|
+
this.close();
|
|
106
110
|
await this.#lifecycle.stop();
|
|
107
|
-
this.#unregisterAgent?.();
|
|
108
|
-
this.#unregisterAgent = undefined;
|
|
109
111
|
if (this.#ws) {
|
|
110
112
|
try {
|
|
111
113
|
this.#ws.close();
|
|
@@ -119,7 +121,7 @@ export class MilkyWsEndpoint implements EndpointInstance {
|
|
|
119
121
|
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
120
122
|
const message = formatOutboundSegments(payload);
|
|
121
123
|
const { action, params } = buildSendAction(conversation, message);
|
|
122
|
-
const data = await
|
|
124
|
+
const data = await callMilkyClient<{ message_seq?: number }>(this.client, action, params);
|
|
123
125
|
const messageId = formatOutboundMessageId(conversation, data?.message_seq);
|
|
124
126
|
this.#logger.debug(formatCompact({
|
|
125
127
|
op: 'milky_send',
|
|
@@ -130,102 +132,33 @@ export class MilkyWsEndpoint implements EndpointInstance {
|
|
|
130
132
|
return messageId;
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
/** Public API for agent tools / callers. */
|
|
134
|
-
callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
|
|
135
|
-
return this.#callApi(this.apiOptions(), action, params);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
135
|
async recallMessage(id: string): Promise<void> {
|
|
139
136
|
const parsed = parseMilkyMessageId(id);
|
|
140
137
|
if (!parsed) throw new Error(`Invalid message id: ${id}`);
|
|
141
138
|
if (parsed.message_scene === 'group') {
|
|
142
|
-
await this.
|
|
139
|
+
await callMilkyClient(this.client, 'recall_group_message', {
|
|
143
140
|
group_id: parsed.peer_id,
|
|
144
141
|
message_seq: parsed.message_seq,
|
|
145
142
|
});
|
|
146
143
|
} else {
|
|
147
|
-
await this.
|
|
144
|
+
await callMilkyClient(this.client, 'recall_private_message', {
|
|
148
145
|
user_id: parsed.peer_id,
|
|
149
146
|
message_seq: parsed.message_seq,
|
|
150
147
|
});
|
|
151
148
|
}
|
|
152
149
|
}
|
|
153
150
|
|
|
154
|
-
|
|
155
|
-
await this.callApi('kick_group_member', {
|
|
156
|
-
group_id: groupId,
|
|
157
|
-
user_id: userId,
|
|
158
|
-
reject_add_request: rejectAddRequest,
|
|
159
|
-
});
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async muteMember(groupId: number, userId: number, duration = 600): Promise<boolean> {
|
|
164
|
-
await this.callApi('set_group_member_mute', {
|
|
165
|
-
group_id: groupId,
|
|
166
|
-
user_id: userId,
|
|
167
|
-
duration,
|
|
168
|
-
});
|
|
169
|
-
return true;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async muteAll(groupId: number, enable = true): Promise<boolean> {
|
|
173
|
-
await this.callApi('set_group_whole_mute', { group_id: groupId, is_mute: enable });
|
|
174
|
-
return true;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async setAdmin(groupId: number, userId: number, enable = true): Promise<boolean> {
|
|
178
|
-
await this.callApi('set_group_member_admin', {
|
|
179
|
-
group_id: groupId,
|
|
180
|
-
user_id: userId,
|
|
181
|
-
is_set: enable,
|
|
182
|
-
});
|
|
183
|
-
return true;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async setCard(groupId: number, userId: number, card: string): Promise<boolean> {
|
|
187
|
-
await this.callApi('set_group_member_card', {
|
|
188
|
-
group_id: groupId,
|
|
189
|
-
user_id: userId,
|
|
190
|
-
card,
|
|
191
|
-
});
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async setTitle(groupId: number, userId: number, title: string): Promise<boolean> {
|
|
196
|
-
await this.callApi('set_group_member_special_title', {
|
|
197
|
-
group_id: groupId,
|
|
198
|
-
user_id: userId,
|
|
199
|
-
special_title: title,
|
|
200
|
-
});
|
|
201
|
-
return true;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async setGroupName(groupId: number, name: string): Promise<boolean> {
|
|
205
|
-
await this.callApi('set_group_name', { group_id: groupId, new_group_name: name });
|
|
206
|
-
return true;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async getMemberList(groupId: number): Promise<unknown[]> {
|
|
210
|
-
return this.callApi('get_group_member_list', { group_id: groupId }) as Promise<unknown[]>;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
async getGroupInfo(groupId: number): Promise<unknown> {
|
|
214
|
-
return this.callApi('get_group_info', { group_id: groupId });
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/** Test / internal: admit a parsed event when the endpoint is open. */
|
|
218
|
-
admit(event: MilkyEvent): void {
|
|
151
|
+
#admitRaw(event: MilkyEvent): void {
|
|
219
152
|
const data = parseMessageReceiveData(event);
|
|
220
|
-
if (!
|
|
153
|
+
if (!data) return;
|
|
221
154
|
this.#admitMessage(data, event);
|
|
222
155
|
}
|
|
223
156
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
};
|
|
157
|
+
#warnPlatformEvent(error: unknown): void {
|
|
158
|
+
this.#logger.warn(formatCompact({
|
|
159
|
+
op: 'milky_platform_event_failed',
|
|
160
|
+
error: error instanceof Error ? error.message : String(error),
|
|
161
|
+
}));
|
|
229
162
|
}
|
|
230
163
|
|
|
231
164
|
#admitMessage(data: MilkyIncomingMessage, event: MilkyEvent): void {
|
|
@@ -236,7 +169,7 @@ export class MilkyWsEndpoint implements EndpointInstance {
|
|
|
236
169
|
const audioUrl = extractInboundAudioUrl(data);
|
|
237
170
|
const nickname = senderNickname(data);
|
|
238
171
|
const mentioned = isMentioned(data, event.self_id);
|
|
239
|
-
void this
|
|
172
|
+
void this.emit('message.receive', {
|
|
240
173
|
conversation,
|
|
241
174
|
message: { conversation, id: formatInboundMessageId(data) },
|
|
242
175
|
content,
|
|
@@ -308,11 +241,12 @@ export class MilkyWsEndpoint implements EndpointInstance {
|
|
|
308
241
|
resolve();
|
|
309
242
|
});
|
|
310
243
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
});
|
|
244
|
+
this.#clientSocketRelease?.();
|
|
245
|
+
this.#clientSocketRelease = this.client.acceptWebSocket(ws);
|
|
314
246
|
|
|
315
247
|
ws.on('close', (code, reason) => {
|
|
248
|
+
this.#clientSocketRelease?.();
|
|
249
|
+
this.#clientSocketRelease = undefined;
|
|
316
250
|
const reasonStr = typeof reason === 'string'
|
|
317
251
|
? reason
|
|
318
252
|
: Buffer.isBuffer(reason)
|
|
@@ -354,23 +288,4 @@ export class MilkyWsEndpoint implements EndpointInstance {
|
|
|
354
288
|
});
|
|
355
289
|
}
|
|
356
290
|
|
|
357
|
-
#onMessage(data: unknown): void {
|
|
358
|
-
try {
|
|
359
|
-
const raw = typeof data === 'string'
|
|
360
|
-
? data
|
|
361
|
-
: Buffer.isBuffer(data)
|
|
362
|
-
? data.toString()
|
|
363
|
-
: data instanceof ArrayBuffer
|
|
364
|
-
? new TextDecoder().decode(data)
|
|
365
|
-
: String(data ?? '');
|
|
366
|
-
const event = JSON.parse(raw) as MilkyEvent;
|
|
367
|
-
this.admit(event);
|
|
368
|
-
} catch (error) {
|
|
369
|
-
this.#logger.warn(formatCompact({
|
|
370
|
-
op: 'milky_parse_failed',
|
|
371
|
-
endpoint: this.#options.config.id,
|
|
372
|
-
error: error instanceof Error ? error.message : String(error),
|
|
373
|
-
}));
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
291
|
}
|
package/src/wss-endpoint.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Milky reverse WSS endpoint — httpHostToken WS upgrade inbound + baseUrl HTTP API outbound.
|
|
3
3
|
*/
|
|
4
|
-
import { clearInterval } from 'node:timers';
|
|
5
4
|
import {
|
|
5
|
+
ClientEndpoint,
|
|
6
|
+
createEndpointLifecycle,
|
|
6
7
|
createRecallEndpointControl,
|
|
8
|
+
type EndpointLifecycle,
|
|
7
9
|
type EndpointControl,
|
|
8
|
-
type EndpointInstance,
|
|
9
10
|
type EndpointManagement,
|
|
10
11
|
type EndpointSendRequest,
|
|
11
12
|
} from 'zhin.js/adapter';
|
|
12
|
-
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
13
13
|
import type { HttpHost, WsConnection } from '@zhin.js/host-http';
|
|
14
14
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
15
15
|
import type { CapabilityId } from 'zhin.js';
|
|
16
16
|
import { verifyMilkyAccessToken } from './milky-auth.js';
|
|
17
17
|
import { createMilkyEndpointManagement } from './endpoint-management.js';
|
|
18
|
-
import {
|
|
18
|
+
import { callMilkyClient, createMilkyEndpointClient, forwardMilkyClientEvents, type MilkyClient } from './client.js';
|
|
19
19
|
import {
|
|
20
20
|
buildSendAction,
|
|
21
21
|
callApi,
|
|
@@ -40,40 +40,65 @@ const WS_OPEN = 1;
|
|
|
40
40
|
|
|
41
41
|
export interface MilkyWssEndpointOptions {
|
|
42
42
|
readonly id: CapabilityId;
|
|
43
|
-
readonly gateway: MessageGateway;
|
|
44
|
-
readonly sideEvents?: SideEventGateway;
|
|
45
43
|
readonly http: HttpHost;
|
|
46
44
|
readonly config: MilkyWssConfig;
|
|
47
45
|
readonly callApi?: typeof callApi;
|
|
48
46
|
}
|
|
49
47
|
|
|
50
|
-
export class MilkyWssEndpoint
|
|
48
|
+
export class MilkyWssEndpoint extends ClientEndpoint<MilkyClient> {
|
|
49
|
+
readonly client: MilkyClient;
|
|
51
50
|
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
52
51
|
|
|
53
52
|
readonly #options: MilkyWssEndpointOptions;
|
|
54
53
|
readonly #callApi: typeof callApi;
|
|
55
|
-
readonly management: EndpointManagement
|
|
54
|
+
readonly management: EndpointManagement;
|
|
56
55
|
readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
57
56
|
#ws?: MilkyWsSocket;
|
|
58
57
|
#wsRelease?: () => void;
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#started = false;
|
|
62
|
-
#unregisterAgent?: () => void;
|
|
58
|
+
#clientSocketRelease?: () => void;
|
|
59
|
+
readonly #lifecycle: EndpointLifecycle;
|
|
63
60
|
|
|
64
61
|
constructor(options: MilkyWssEndpointOptions) {
|
|
62
|
+
super();
|
|
65
63
|
this.#logger = getAdapterLogger('milky', options.config.id);
|
|
66
64
|
this.#options = options;
|
|
67
65
|
this.#callApi = options.callApi ?? callApi;
|
|
66
|
+
this.#lifecycle = createEndpointLifecycle({
|
|
67
|
+
name: options.config.id,
|
|
68
|
+
reconnect: false,
|
|
69
|
+
heartbeat: { intervalMs: options.config.heartbeat_interval },
|
|
70
|
+
});
|
|
71
|
+
this.client = createMilkyEndpointClient(options.config, this.#callApi);
|
|
72
|
+
this.management = createMilkyEndpointManagement({
|
|
73
|
+
callApi: (action, params) => callMilkyClient(this.client, action, params),
|
|
74
|
+
});
|
|
75
|
+
this.bindClientEvents(
|
|
76
|
+
(receive) => forwardMilkyClientEvents(this.client, receive),
|
|
77
|
+
(name, payload) => {
|
|
78
|
+
if (name === 'event') this.#admitRaw(payload as MilkyEvent);
|
|
79
|
+
},
|
|
80
|
+
(_name, error) => this.#warnPlatformEvent(error),
|
|
81
|
+
);
|
|
68
82
|
}
|
|
69
83
|
|
|
70
84
|
async start(): Promise<void> {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
await this.#lifecycle.start(async (lifecycleHandle) => {
|
|
86
|
+
const handle = this.#options.http.ws(this.#options.config.path);
|
|
87
|
+
this.#wsRelease = handle.onConnection((connection) => {
|
|
88
|
+
this.#acceptConnection(connection);
|
|
89
|
+
});
|
|
90
|
+
lifecycleHandle.onForceClose(() => {
|
|
91
|
+
this.#wsRelease?.();
|
|
92
|
+
this.#wsRelease = undefined;
|
|
93
|
+
this.#clientSocketRelease?.();
|
|
94
|
+
this.#clientSocketRelease = undefined;
|
|
95
|
+
try {
|
|
96
|
+
this.#ws?.close();
|
|
97
|
+
} catch {
|
|
98
|
+
/* ignore */
|
|
99
|
+
}
|
|
100
|
+
this.#ws = undefined;
|
|
101
|
+
});
|
|
77
102
|
});
|
|
78
103
|
this.#logger.info(formatCompact({
|
|
79
104
|
op: 'listen',
|
|
@@ -83,39 +108,15 @@ export class MilkyWssEndpoint implements EndpointInstance {
|
|
|
83
108
|
}));
|
|
84
109
|
}
|
|
85
110
|
|
|
86
|
-
open(): void {
|
|
87
|
-
this.#open = true;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
close(): void {
|
|
91
|
-
this.#open = false;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
111
|
async stop(): Promise<void> {
|
|
95
|
-
this
|
|
96
|
-
this.#
|
|
97
|
-
this.#unregisterAgent = undefined;
|
|
98
|
-
this.#wsRelease?.();
|
|
99
|
-
this.#wsRelease = undefined;
|
|
100
|
-
if (this.#heartbeatTimer) {
|
|
101
|
-
clearInterval(this.#heartbeatTimer);
|
|
102
|
-
this.#heartbeatTimer = undefined;
|
|
103
|
-
}
|
|
104
|
-
if (this.#ws) {
|
|
105
|
-
try {
|
|
106
|
-
this.#ws.close();
|
|
107
|
-
} catch {
|
|
108
|
-
/* ignore */
|
|
109
|
-
}
|
|
110
|
-
this.#ws = undefined;
|
|
111
|
-
}
|
|
112
|
-
this.#started = false;
|
|
112
|
+
this.close();
|
|
113
|
+
await this.#lifecycle.stop();
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
116
117
|
const message = formatOutboundSegments(payload);
|
|
117
118
|
const { action, params } = buildSendAction(conversation, message);
|
|
118
|
-
const data = await
|
|
119
|
+
const data = await callMilkyClient<{ message_seq?: number }>(this.client, action, params);
|
|
119
120
|
const messageId = formatOutboundMessageId(conversation, data?.message_seq);
|
|
120
121
|
this.#logger.debug(formatCompact({
|
|
121
122
|
op: 'milky_send',
|
|
@@ -126,100 +127,30 @@ export class MilkyWssEndpoint implements EndpointInstance {
|
|
|
126
127
|
return messageId;
|
|
127
128
|
}
|
|
128
129
|
|
|
129
|
-
callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
|
|
130
|
-
return this.#callApi(this.apiOptions(), action, params);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
130
|
async recallMessage(id: string): Promise<void> {
|
|
134
131
|
const parsed = parseMilkyMessageId(id);
|
|
135
132
|
if (!parsed) throw new Error(`Invalid message id: ${id}`);
|
|
136
133
|
if (parsed.message_scene === 'group') {
|
|
137
|
-
await this.
|
|
134
|
+
await callMilkyClient(this.client, 'recall_group_message', {
|
|
138
135
|
group_id: parsed.peer_id,
|
|
139
136
|
message_seq: parsed.message_seq,
|
|
140
137
|
});
|
|
141
138
|
} else {
|
|
142
|
-
await this.
|
|
139
|
+
await callMilkyClient(this.client, 'recall_private_message', {
|
|
143
140
|
user_id: parsed.peer_id,
|
|
144
141
|
message_seq: parsed.message_seq,
|
|
145
142
|
});
|
|
146
143
|
}
|
|
147
144
|
}
|
|
148
145
|
|
|
149
|
-
|
|
150
|
-
await this.callApi('kick_group_member', {
|
|
151
|
-
group_id: groupId,
|
|
152
|
-
user_id: userId,
|
|
153
|
-
reject_add_request: rejectAddRequest,
|
|
154
|
-
});
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async muteMember(groupId: number, userId: number, duration = 600): Promise<boolean> {
|
|
159
|
-
await this.callApi('set_group_member_mute', {
|
|
160
|
-
group_id: groupId,
|
|
161
|
-
user_id: userId,
|
|
162
|
-
duration,
|
|
163
|
-
});
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async muteAll(groupId: number, enable = true): Promise<boolean> {
|
|
168
|
-
await this.callApi('set_group_whole_mute', { group_id: groupId, is_mute: enable });
|
|
169
|
-
return true;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async setAdmin(groupId: number, userId: number, enable = true): Promise<boolean> {
|
|
173
|
-
await this.callApi('set_group_member_admin', {
|
|
174
|
-
group_id: groupId,
|
|
175
|
-
user_id: userId,
|
|
176
|
-
is_set: enable,
|
|
177
|
-
});
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
async setCard(groupId: number, userId: number, card: string): Promise<boolean> {
|
|
182
|
-
await this.callApi('set_group_member_card', {
|
|
183
|
-
group_id: groupId,
|
|
184
|
-
user_id: userId,
|
|
185
|
-
card,
|
|
186
|
-
});
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
async setTitle(groupId: number, userId: number, title: string): Promise<boolean> {
|
|
191
|
-
await this.callApi('set_group_member_special_title', {
|
|
192
|
-
group_id: groupId,
|
|
193
|
-
user_id: userId,
|
|
194
|
-
special_title: title,
|
|
195
|
-
});
|
|
196
|
-
return true;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async setGroupName(groupId: number, name: string): Promise<boolean> {
|
|
200
|
-
await this.callApi('set_group_name', { group_id: groupId, new_group_name: name });
|
|
201
|
-
return true;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async getMemberList(groupId: number): Promise<unknown[]> {
|
|
205
|
-
return this.callApi('get_group_member_list', { group_id: groupId }) as Promise<unknown[]>;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async getGroupInfo(groupId: number): Promise<unknown> {
|
|
209
|
-
return this.callApi('get_group_info', { group_id: groupId });
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
admit(event: MilkyEvent): void {
|
|
146
|
+
#admitRaw(event: MilkyEvent): void {
|
|
213
147
|
const data = parseMessageReceiveData(event);
|
|
214
|
-
if (!
|
|
148
|
+
if (!data) return;
|
|
215
149
|
this.#admitMessage(data, event);
|
|
216
150
|
}
|
|
217
151
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
baseUrl: this.#options.config.baseUrl,
|
|
221
|
-
access_token: this.#options.config.access_token,
|
|
222
|
-
};
|
|
152
|
+
#warnPlatformEvent(error: unknown): void {
|
|
153
|
+
this.#logger.warn(formatCompact({ op: 'milky_platform_event_failed', error: error instanceof Error ? error.message : String(error) }));
|
|
223
154
|
}
|
|
224
155
|
|
|
225
156
|
#admitMessage(data: MilkyIncomingMessage, event: MilkyEvent): void {
|
|
@@ -230,7 +161,7 @@ export class MilkyWssEndpoint implements EndpointInstance {
|
|
|
230
161
|
const audioUrl = extractInboundAudioUrl(data);
|
|
231
162
|
const nickname = senderNickname(data);
|
|
232
163
|
const mentioned = isMentioned(data, event.self_id);
|
|
233
|
-
void this
|
|
164
|
+
void this.emit('message.receive', {
|
|
234
165
|
conversation,
|
|
235
166
|
message: { conversation, id: formatInboundMessageId(data) },
|
|
236
167
|
content,
|
|
@@ -270,12 +201,22 @@ export class MilkyWssEndpoint implements EndpointInstance {
|
|
|
270
201
|
}
|
|
271
202
|
}
|
|
272
203
|
this.#ws = socket;
|
|
273
|
-
this.#startHeartbeat()
|
|
274
|
-
|
|
275
|
-
|
|
204
|
+
this.#lifecycle.startHeartbeat(() => {
|
|
205
|
+
try {
|
|
206
|
+
if (this.#ws?.readyState === WS_OPEN) this.#ws.ping?.();
|
|
207
|
+
} catch {
|
|
208
|
+
/* ignore */
|
|
209
|
+
}
|
|
276
210
|
});
|
|
211
|
+
this.#clientSocketRelease?.();
|
|
212
|
+
this.#clientSocketRelease = this.client.acceptWebSocket(socket);
|
|
277
213
|
socket.on('close', () => {
|
|
278
|
-
|
|
214
|
+
this.#clientSocketRelease?.();
|
|
215
|
+
this.#clientSocketRelease = undefined;
|
|
216
|
+
if (this.#ws === socket) {
|
|
217
|
+
this.#ws = undefined;
|
|
218
|
+
this.#lifecycle.stopHeartbeat();
|
|
219
|
+
}
|
|
279
220
|
});
|
|
280
221
|
this.#logger.debug(formatCompact({
|
|
281
222
|
endpoint: this.#options.config.id,
|
|
@@ -284,36 +225,4 @@ export class MilkyWssEndpoint implements EndpointInstance {
|
|
|
284
225
|
}));
|
|
285
226
|
}
|
|
286
227
|
|
|
287
|
-
#onMessage(data: unknown): void {
|
|
288
|
-
try {
|
|
289
|
-
const raw = typeof data === 'string'
|
|
290
|
-
? data
|
|
291
|
-
: Buffer.isBuffer(data)
|
|
292
|
-
? data.toString()
|
|
293
|
-
: data instanceof ArrayBuffer
|
|
294
|
-
? new TextDecoder().decode(data)
|
|
295
|
-
: String(data ?? '');
|
|
296
|
-
const event = JSON.parse(raw) as MilkyEvent;
|
|
297
|
-
this.admit(event);
|
|
298
|
-
} catch (error) {
|
|
299
|
-
this.#logger.warn(formatCompact({
|
|
300
|
-
op: 'milky_parse_failed',
|
|
301
|
-
endpoint: this.#options.config.id,
|
|
302
|
-
error: error instanceof Error ? error.message : String(error),
|
|
303
|
-
}));
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
#startHeartbeat(): void {
|
|
308
|
-
if (this.#heartbeatTimer) clearInterval(this.#heartbeatTimer);
|
|
309
|
-
const interval = this.#options.config.heartbeat_interval;
|
|
310
|
-
if (interval <= 0) return;
|
|
311
|
-
this.#heartbeatTimer = setInterval(() => {
|
|
312
|
-
try {
|
|
313
|
-
if (this.#ws?.readyState === WS_OPEN) this.#ws.ping?.();
|
|
314
|
-
} catch {
|
|
315
|
-
/* ignore */
|
|
316
|
-
}
|
|
317
|
-
}, interval);
|
|
318
|
-
}
|
|
319
228
|
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent tool deps for milky (scene management / callApi).
|
|
3
|
-
* Endpoints register themselves on start; tools look up by config name / endpoint id.
|
|
4
|
-
*/
|
|
5
|
-
export interface MilkyAgentEndpoint {
|
|
6
|
-
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
7
|
-
kickMember(groupId: number, userId: number, rejectAddRequest?: boolean): Promise<boolean>;
|
|
8
|
-
muteMember(groupId: number, userId: number, duration?: number): Promise<boolean>;
|
|
9
|
-
muteAll(groupId: number, enable?: boolean): Promise<boolean>;
|
|
10
|
-
setAdmin(groupId: number, userId: number, enable?: boolean): Promise<boolean>;
|
|
11
|
-
setCard(groupId: number, userId: number, card: string): Promise<boolean>;
|
|
12
|
-
setTitle(groupId: number, userId: number, title: string): Promise<boolean>;
|
|
13
|
-
setGroupName(groupId: number, name: string): Promise<boolean>;
|
|
14
|
-
getMemberList(groupId: number): Promise<unknown[]>;
|
|
15
|
-
getGroupInfo(groupId: number): Promise<unknown>;
|
|
16
|
-
recallMessage?(id: string): Promise<void>;
|
|
17
|
-
}
|
|
18
|
-
export interface MilkyAgentDeps {
|
|
19
|
-
getEndpoint: (endpointKey: string) => MilkyAgentEndpoint;
|
|
20
|
-
}
|
|
21
|
-
export declare function registerMilkyAgentEndpoint(endpointKey: string, endpoint: MilkyAgentEndpoint): () => void;
|
|
22
|
-
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
23
|
-
export declare function setMilkyAgentDeps(deps: MilkyAgentDeps | null): void;
|
|
24
|
-
export declare function getMilkyAgentDeps(): MilkyAgentDeps;
|
package/lib/milky-agent-deps.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent tool deps for milky (scene management / callApi).
|
|
3
|
-
* Endpoints register themselves on start; tools look up by config name / endpoint id.
|
|
4
|
-
*/
|
|
5
|
-
const endpoints = new Map();
|
|
6
|
-
let override = null;
|
|
7
|
-
export function registerMilkyAgentEndpoint(endpointKey, endpoint) {
|
|
8
|
-
endpoints.set(endpointKey, endpoint);
|
|
9
|
-
return () => {
|
|
10
|
-
if (endpoints.get(endpointKey) === endpoint) {
|
|
11
|
-
endpoints.delete(endpointKey);
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
/** Optional override used by tests / transitional callers. Pass `null` to clear. */
|
|
16
|
-
export function setMilkyAgentDeps(deps) {
|
|
17
|
-
override = deps;
|
|
18
|
-
}
|
|
19
|
-
export function getMilkyAgentDeps() {
|
|
20
|
-
if (override)
|
|
21
|
-
return override;
|
|
22
|
-
return {
|
|
23
|
-
getEndpoint(endpointKey) {
|
|
24
|
-
const registered = endpoints.get(endpointKey);
|
|
25
|
-
if (!registered)
|
|
26
|
-
throw new Error(`Endpoint ${endpointKey} 不存在`);
|
|
27
|
-
return registered;
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
}
|