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