@zhin.js/adapter-milky 1.0.1 → 1.1.2

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.
Files changed (99) hide show
  1. package/CHANGELOG.md +1046 -0
  2. package/{skills/milky/PERMITS.md → PERMITS.md} +2 -2
  3. package/README.md +60 -117
  4. package/adapters/milky/index.js +51 -0
  5. package/adapters/milky/index.ts +74 -0
  6. package/commands/milky/endpoint/add/[id]/index.js +3 -0
  7. package/commands/milky/endpoint/add/[id]/index.ts +3 -0
  8. package/commands/milky/endpoint/definition.js +19 -0
  9. package/commands/milky/endpoint/definition.ts +19 -0
  10. package/commands/milky/endpoint/list/index.js +3 -0
  11. package/commands/milky/endpoint/list/index.ts +3 -0
  12. package/commands/milky/endpoint/remove/[id]/index.js +3 -0
  13. package/commands/milky/endpoint/remove/[id]/index.ts +3 -0
  14. package/lib/client.d.ts +21 -0
  15. package/lib/client.js +33 -0
  16. package/lib/endpoint-management.d.ts +17 -0
  17. package/lib/endpoint-management.js +47 -0
  18. package/lib/endpoint.d.ts +7 -0
  19. package/lib/endpoint.js +6 -0
  20. package/lib/index.d.ts +6 -20
  21. package/lib/index.js +4 -35
  22. package/lib/milky-auth.d.ts +2 -0
  23. package/lib/milky-auth.js +16 -0
  24. package/lib/milky-runtime-state.d.ts +1 -0
  25. package/lib/milky-runtime-state.js +6 -0
  26. package/lib/protocol.d.ts +160 -0
  27. package/lib/protocol.js +491 -0
  28. package/lib/sse-client.d.ts +20 -0
  29. package/lib/sse-client.js +85 -0
  30. package/lib/sse-endpoint.d.ts +32 -0
  31. package/lib/sse-endpoint.js +207 -0
  32. package/lib/webhook-endpoint.d.ts +22 -0
  33. package/lib/webhook-endpoint.js +149 -0
  34. package/lib/ws-endpoint.d.ts +23 -0
  35. package/lib/ws-endpoint.js +233 -0
  36. package/lib/ws-types.d.ts +11 -0
  37. package/lib/ws-types.js +1 -0
  38. package/lib/wss-endpoint.d.ts +25 -0
  39. package/lib/wss-endpoint.js +199 -0
  40. package/package.json +78 -19
  41. package/plugin.js +14 -0
  42. package/schema.json +119 -0
  43. package/src/client.ts +80 -0
  44. package/src/endpoint-management.ts +74 -0
  45. package/src/endpoint.ts +26 -0
  46. package/src/index.ts +62 -47
  47. package/src/milky-auth.ts +14 -0
  48. package/src/milky-runtime-state.ts +7 -0
  49. package/src/protocol.ts +633 -0
  50. package/src/sse-client.ts +106 -0
  51. package/src/sse-endpoint.ts +258 -0
  52. package/src/webhook-endpoint.ts +196 -0
  53. package/src/ws-endpoint.ts +291 -0
  54. package/src/ws-types.ts +12 -0
  55. package/src/wss-endpoint.ts +241 -0
  56. package/lib/adapter.d.ts +0 -29
  57. package/lib/adapter.d.ts.map +0 -1
  58. package/lib/adapter.js +0 -91
  59. package/lib/adapter.js.map +0 -1
  60. package/lib/api.d.ts +0 -10
  61. package/lib/api.d.ts.map +0 -1
  62. package/lib/api.js +0 -48
  63. package/lib/api.js.map +0 -1
  64. package/lib/endpoint-sse.d.ts +0 -31
  65. package/lib/endpoint-sse.d.ts.map +0 -1
  66. package/lib/endpoint-sse.js +0 -195
  67. package/lib/endpoint-sse.js.map +0 -1
  68. package/lib/endpoint-webhook.d.ts +0 -34
  69. package/lib/endpoint-webhook.d.ts.map +0 -1
  70. package/lib/endpoint-webhook.js +0 -187
  71. package/lib/endpoint-webhook.js.map +0 -1
  72. package/lib/endpoint-ws.d.ts +0 -35
  73. package/lib/endpoint-ws.d.ts.map +0 -1
  74. package/lib/endpoint-ws.js +0 -262
  75. package/lib/endpoint-ws.js.map +0 -1
  76. package/lib/endpoint-wss.d.ts +0 -34
  77. package/lib/endpoint-wss.d.ts.map +0 -1
  78. package/lib/endpoint-wss.js +0 -225
  79. package/lib/endpoint-wss.js.map +0 -1
  80. package/lib/index.d.ts.map +0 -1
  81. package/lib/index.js.map +0 -1
  82. package/lib/types.d.ts +0 -75
  83. package/lib/types.d.ts.map +0 -1
  84. package/lib/types.js +0 -5
  85. package/lib/types.js.map +0 -1
  86. package/lib/utils.d.ts +0 -35
  87. package/lib/utils.d.ts.map +0 -1
  88. package/lib/utils.js +0 -165
  89. package/lib/utils.js.map +0 -1
  90. package/plugin.yml +0 -3
  91. package/skills/milky/SKILL.md +0 -65
  92. package/src/adapter.ts +0 -103
  93. package/src/api.ts +0 -63
  94. package/src/endpoint-sse.ts +0 -228
  95. package/src/endpoint-webhook.ts +0 -221
  96. package/src/endpoint-ws.ts +0 -296
  97. package/src/endpoint-wss.ts +0 -268
  98. package/src/types.ts +0 -73
  99. package/src/utils.ts +0 -181
@@ -1,296 +0,0 @@
1
- /**
2
- * Milky WebSocket 正向连接 Bot(应用连协议端 ws(s)://baseUrl/event)
3
- */
4
- import WebSocket from 'ws';
5
- import { EventEmitter } from 'events';
6
- import { clearInterval } from 'node:timers';
7
- import { formatCompact, Endpoint, Message, segment, SendOptions } from 'zhin.js';
8
- import { callApi } from './api.js';
9
- import type { MilkyWsConfig, MilkyEvent } from './types.js';
10
- import type { MilkyAdapter } from './adapter.js';
11
- import {
12
- formatMilkyMessagePayload,
13
- parseMessageReceiveData,
14
- toMilkyOutgoingSegments,
15
- parseMilkyMessageId,
16
- } from './utils.js';
17
-
18
- export class MilkyWsClient extends EventEmitter implements Endpoint<MilkyWsConfig, MilkyEvent> {
19
- $connected: boolean;
20
- private ws?: WebSocket;
21
- private reconnectTimer?: NodeJS.Timeout;
22
- private heartbeatTimer?: NodeJS.Timeout;
23
-
24
- get logger() {
25
- return this.adapter.plugin.logger;
26
- }
27
-
28
- constructor(public adapter: MilkyAdapter, public $config: MilkyWsConfig) {
29
- super();
30
- this.$connected = false;
31
- }
32
-
33
- get $id() {
34
- return this.$config.name;
35
- }
36
-
37
- private get eventUrl(): string {
38
- const base = this.$config.baseUrl.replace(/\/$/, '');
39
- const url = base.replace(/^http/, 'ws') + '/event';
40
- const token = this.$config.access_token;
41
- if (token) return `${url}${url.includes('?') ? '&' : '?'}access_token=${encodeURIComponent(token)}`;
42
- return url;
43
- }
44
-
45
- private apiOptions() {
46
- return { baseUrl: this.$config.baseUrl, access_token: this.$config.access_token };
47
- }
48
-
49
- async $connect(): Promise<void> {
50
- return new Promise((resolve, reject) => {
51
- const headers: Record<string, string> = {};
52
- if (this.$config.access_token) {
53
- headers['Authorization'] = `Bearer ${this.$config.access_token}`;
54
- }
55
- this.ws = new WebSocket(this.eventUrl, { headers });
56
-
57
- this.ws.on('open', () => {
58
- this.$connected = true;
59
- if (!this.$config.access_token) {
60
- this.logger.warn(formatCompact({ endpoint: this.$id, ok: false, error: 'missing access_token' }));
61
- }
62
- this.logger.info(formatCompact({ endpoint: this.$id, mode: 'ws' }));
63
- this.startHeartbeat();
64
- resolve();
65
- });
66
-
67
- this.ws.on('message', (data) => {
68
- try {
69
- const event = JSON.parse(data.toString()) as MilkyEvent;
70
- this.handleEvent(event);
71
- } catch (error) {
72
- this.emit('error', error);
73
- }
74
- });
75
-
76
- this.ws.on('close', (code, reason) => {
77
- this.$connected = false;
78
- const reasonStr = reason?.toString?.() || String(reason);
79
- const codeHint = code === 1005 ? ' [无状态,多为服务端/代理未发 close 帧即断开]' : code === 1006 ? ' [异常关闭]' : '';
80
- this.logger.warn(formatCompact( {
81
- op: 'disconnect',
82
- endpoint: this.$config.name,
83
- code,
84
- error: `${reasonStr || 'closed'}${codeHint}`,
85
- reconnect_ms: this.$config.reconnect_interval ?? 5000,
86
- }));
87
- reject(new Error(`WS closed: ${code} ${reasonStr}`));
88
- this.scheduleReconnect();
89
- });
90
-
91
- this.ws.on('error', (error) => {
92
- this.logger.warn(formatCompact( {
93
- op: 'ws_error',
94
- endpoint: this.$config.name,
95
- ok: false,
96
- error: error instanceof Error ? error.message : String(error),
97
- }));
98
- reject(error);
99
- });
100
- });
101
- }
102
-
103
- async $disconnect(): Promise<void> {
104
- if (this.reconnectTimer) {
105
- clearTimeout(this.reconnectTimer);
106
- this.reconnectTimer = undefined;
107
- }
108
- if (this.heartbeatTimer) {
109
- clearInterval(this.heartbeatTimer);
110
- this.heartbeatTimer = undefined;
111
- }
112
- if (this.ws) {
113
- this.ws.close();
114
- this.ws = undefined;
115
- }
116
- this.$connected = false;
117
- }
118
-
119
- private handleEvent(event: MilkyEvent): void {
120
- const data = parseMessageReceiveData(event);
121
- if (data) {
122
- const message = this.$formatMessage(event);
123
- this.adapter.emit('message.receive', message);
124
- this.logger.debug(
125
- `${this.$config.name} recv ${message.$channel.type}(${message.$channel.id}):${segment.raw(message.$content)}`,
126
- );
127
- }
128
- // 其他 event_type 可在此扩展 Notice / Request
129
- }
130
-
131
- $formatMessage(event: MilkyEvent): Message<MilkyEvent> {
132
- const data = parseMessageReceiveData(event);
133
- if (!data) {
134
- return Message.from(event, {
135
- $id: '',
136
- $adapter: 'milky',
137
- $endpoint: this.$config.name,
138
- $channel: { id: '', type: 'private' },
139
- $sender: { id: '', name: '' },
140
- $content: [],
141
- $raw: '',
142
- $timestamp: event.time ?? 0,
143
- $recall: async () => {},
144
- $reply: async () => '',
145
- });
146
- }
147
- const payload = formatMilkyMessagePayload(
148
- event,
149
- data,
150
- (id) => this.$recallMessage(id),
151
- (channel, content, _quote) =>
152
- this.adapter.sendMessage({
153
- ...channel,
154
- context: 'milky',
155
- endpoint: this.$config.name,
156
- content: content as import('zhin.js').SendContent,
157
- }),
158
- 'milky',
159
- this.$config.name,
160
- );
161
- return Message.from(event, payload);
162
- }
163
-
164
- async $sendMessage(options: SendOptions): Promise<string> {
165
- const content = Array.isArray(options.content) ? options.content : [options.content];
166
- const segments = content.map((c) =>
167
- typeof c === 'string' ? { type: 'text' as const, data: { text: c } } : (c as { type: string; data?: Record<string, unknown> }),
168
- );
169
- const message = toMilkyOutgoingSegments(segments);
170
- if (options.type === 'group') {
171
- const result = await callApi(this.apiOptions(), 'send_group_message', {
172
- group_id: parseInt(options.id, 10),
173
- message,
174
- });
175
- const seq = (result as { message_seq?: number }).message_seq;
176
- this.logger.debug(`${this.$config.name} send group(${options.id}):${segment.raw(options.content)}`);
177
- return seq != null ? `group:${options.id}:${seq}` : '';
178
- }
179
- if (options.type === 'private') {
180
- const result = await callApi(this.apiOptions(), 'send_private_message', {
181
- user_id: parseInt(options.id, 10),
182
- message,
183
- });
184
- const seq = (result as { message_seq?: number }).message_seq;
185
- this.logger.debug(`${this.$config.name} send private(${options.id}):${segment.raw(options.content)}`);
186
- return seq != null ? `friend:${options.id}:${seq}` : '';
187
- }
188
- throw new Error('Either group or private must be provided');
189
- }
190
-
191
- async $recallMessage(id: string): Promise<void> {
192
- const parsed = parseMilkyMessageId(id);
193
- if (!parsed) throw new Error(`Invalid message id: ${id}`);
194
- if (parsed.message_scene === 'group') {
195
- await callApi(this.apiOptions(), 'recall_group_message', {
196
- group_id: parsed.peer_id,
197
- message_seq: parsed.message_seq,
198
- });
199
- } else {
200
- await callApi(this.apiOptions(), 'recall_private_message', {
201
- user_id: parsed.peer_id,
202
- message_seq: parsed.message_seq,
203
- });
204
- }
205
- }
206
-
207
- async kickMember(groupId: number, userId: number, rejectAddRequest = false): Promise<boolean> {
208
- await callApi(this.apiOptions(), 'kick_group_member', {
209
- group_id: groupId,
210
- user_id: userId,
211
- reject_add_request: rejectAddRequest,
212
- });
213
- this.logger.debug(formatCompact( { op: 'kick', endpoint: this.$id, group: groupId, user: userId }));
214
- return true;
215
- }
216
-
217
- async muteMember(groupId: number, userId: number, duration = 600): Promise<boolean> {
218
- await callApi(this.apiOptions(), 'set_group_member_mute', {
219
- group_id: groupId,
220
- user_id: userId,
221
- duration,
222
- });
223
- this.logger.debug(formatCompact( { op: duration > 0 ? 'mute' : 'unmute', endpoint: this.$id, group: groupId, user: userId, duration }));
224
- return true;
225
- }
226
-
227
- async muteAll(groupId: number, enable = true): Promise<boolean> {
228
- await callApi(this.apiOptions(), 'set_group_whole_mute', { group_id: groupId, is_mute: enable });
229
- this.logger.debug(formatCompact( { op: 'mute_all', endpoint: this.$id, group: groupId, enable }));
230
- return true;
231
- }
232
-
233
- async setAdmin(groupId: number, userId: number, enable = true): Promise<boolean> {
234
- await callApi(this.apiOptions(), 'set_group_member_admin', {
235
- group_id: groupId,
236
- user_id: userId,
237
- is_set: enable,
238
- });
239
- this.logger.debug(formatCompact( { op: 'set_admin', endpoint: this.$id, group: groupId, user: userId, enable }));
240
- return true;
241
- }
242
-
243
- async setCard(groupId: number, userId: number, card: string): Promise<boolean> {
244
- await callApi(this.apiOptions(), 'set_group_member_card', {
245
- group_id: groupId,
246
- user_id: userId,
247
- card,
248
- });
249
- this.logger.debug(formatCompact( { op: 'set_card', endpoint: this.$id, group: groupId, user: userId }));
250
- return true;
251
- }
252
-
253
- async setTitle(groupId: number, userId: number, title: string): Promise<boolean> {
254
- await callApi(this.apiOptions(), 'set_group_member_special_title', {
255
- group_id: groupId,
256
- user_id: userId,
257
- special_title: title,
258
- });
259
- this.logger.debug(formatCompact( { op: 'set_title', endpoint: this.$id, group: groupId, user: userId }));
260
- return true;
261
- }
262
-
263
- async setGroupName(groupId: number, name: string): Promise<boolean> {
264
- await callApi(this.apiOptions(), 'set_group_name', { group_id: groupId, new_group_name: name });
265
- this.logger.debug(formatCompact( { op: 'set_group_name', endpoint: this.$id, group: groupId }));
266
- return true;
267
- }
268
-
269
- async getMemberList(groupId: number): Promise<unknown[]> {
270
- return callApi(this.apiOptions(), 'get_group_member_list', { group_id: groupId }) as Promise<unknown[]>;
271
- }
272
-
273
- async getGroupInfo(groupId: number): Promise<unknown> {
274
- return callApi(this.apiOptions(), 'get_group_info', { group_id: groupId });
275
- }
276
-
277
- private startHeartbeat(): void {
278
- const interval = this.$config.heartbeat_interval ?? 30000;
279
- if (interval <= 0) return; // 设为 0 可关闭心跳(部分网关如 onebots 对 ping 处理异常时会 1006 断连)
280
- this.heartbeatTimer = setInterval(() => {
281
- if (this.ws?.readyState === WebSocket.OPEN) this.ws.ping();
282
- }, interval);
283
- }
284
-
285
- private scheduleReconnect(): void {
286
- if (this.reconnectTimer) return;
287
- const interval = this.$config.reconnect_interval ?? 5000;
288
- this.reconnectTimer = setTimeout(() => {
289
- this.reconnectTimer = undefined;
290
- this.$connect().catch((err) => {
291
- this.emit('error', err);
292
- this.scheduleReconnect();
293
- });
294
- }, interval);
295
- }
296
- }
@@ -1,268 +0,0 @@
1
- /**
2
- * Milky 反向 WebSocket Bot:应用开 WS 服务端,协议端来连;鉴权后收事件同正向
3
- */
4
- import { IncomingMessage } from 'http';
5
- import WebSocket, { WebSocketServer } from 'ws';
6
- import { EventEmitter } from 'events';
7
- import { clearInterval } from 'node:timers';
8
- import { formatCompact, Endpoint, Message, segment, SendOptions } from 'zhin.js';
9
- import type { Router } from '@zhin.js/host-router';
10
- import { callApi } from './api.js';
11
- import type { MilkyWssConfig, MilkyEvent } from './types.js';
12
- import type { MilkyAdapter } from './adapter.js';
13
- import {
14
- formatMilkyMessagePayload,
15
- parseMessageReceiveData,
16
- toMilkyOutgoingSegments,
17
- parseMilkyMessageId,
18
- } from './utils.js';
19
-
20
- function getAccessTokenFromWsRequest(req: IncomingMessage): string | undefined {
21
- const auth = req.headers['authorization'];
22
- if (typeof auth === 'string' && auth.startsWith('Bearer ')) return auth.slice(7);
23
- const url = req.url ?? '';
24
- const idx = url.indexOf('?');
25
- if (idx >= 0) {
26
- const params = new URLSearchParams(url.slice(idx));
27
- return params.get('access_token') ?? undefined;
28
- }
29
- return undefined;
30
- }
31
-
32
- export class MilkyWssServer extends EventEmitter implements Endpoint<MilkyWssConfig, MilkyEvent> {
33
- $connected: boolean;
34
- #wss?: WebSocketServer;
35
- #client?: WebSocket;
36
- private heartbeatTimer?: NodeJS.Timeout;
37
-
38
- get logger() {
39
- return this.adapter.plugin.logger;
40
- }
41
-
42
- constructor(
43
- public adapter: MilkyAdapter,
44
- public router: Router,
45
- public $config: MilkyWssConfig,
46
- ) {
47
- super();
48
- this.$connected = false;
49
- }
50
-
51
- get $id() {
52
- return this.$config.name;
53
- }
54
-
55
- private apiOptions() {
56
- return { baseUrl: this.$config.baseUrl, access_token: this.$config.access_token };
57
- }
58
-
59
- async $connect(): Promise<void> {
60
- const path = this.$config.path.startsWith('/') ? this.$config.path : `/${this.$config.path}`;
61
- const token = this.$config.access_token;
62
- if (!token) this.logger.warn(formatCompact({ endpoint: this.$id, ok: false, error: 'missing access_token' }));
63
-
64
- this.#wss = this.router.ws(path, {
65
- verifyClient: (info: { req: IncomingMessage }) => {
66
- const received = getAccessTokenFromWsRequest(info.req);
67
- if (token && received !== token) {
68
- this.logger.error('反向 WS 鉴权失败');
69
- return false;
70
- }
71
- return true;
72
- },
73
- });
74
-
75
- this.$connected = true;
76
- this.logger.info(formatCompact( { op: 'listen', endpoint: this.$id, mode: 'wss', path }));
77
-
78
- this.#wss.on('connection', (client, req) => {
79
- this.#client = client;
80
- this.startHeartbeat();
81
- this.logger.info(formatCompact({ endpoint: this.$id, peer: req.socket?.remoteAddress }));
82
-
83
- client.on('message', (data) => {
84
- try {
85
- const event = JSON.parse(data.toString()) as MilkyEvent;
86
- this.handleEvent(event);
87
- } catch (err) {
88
- this.emit('error', err);
89
- }
90
- });
91
-
92
- client.on('close', () => {
93
- this.#client = undefined;
94
- this.logger.warn(formatCompact( { op: 'disconnect', endpoint: this.$id }));
95
- });
96
-
97
- client.on('error', (err) => this.logger.error('反向 WS 连接错误', err));
98
- });
99
- }
100
-
101
- async $disconnect(): Promise<void> {
102
- if (this.heartbeatTimer) {
103
- clearInterval(this.heartbeatTimer);
104
- this.heartbeatTimer = undefined;
105
- }
106
- this.#wss?.close();
107
- this.#wss = undefined;
108
- this.#client = undefined;
109
- this.$connected = false;
110
- }
111
-
112
- $formatMessage(event: MilkyEvent): Message<MilkyEvent> {
113
- const data = parseMessageReceiveData(event);
114
- if (!data) {
115
- return Message.from(event, {
116
- $id: '',
117
- $adapter: 'milky',
118
- $endpoint: this.$config.name,
119
- $channel: { id: '', type: 'private' },
120
- $sender: { id: '', name: '' },
121
- $content: [],
122
- $raw: '',
123
- $timestamp: event.time ?? 0,
124
- $recall: async () => {},
125
- $reply: async () => '',
126
- });
127
- }
128
- const payload = formatMilkyMessagePayload(
129
- event,
130
- data,
131
- (id) => this.$recallMessage(id),
132
- (channel, content) =>
133
- this.adapter.sendMessage({
134
- ...channel,
135
- context: 'milky',
136
- endpoint: this.$config.name,
137
- content: content as import('zhin.js').SendContent,
138
- }),
139
- 'milky',
140
- this.$config.name,
141
- );
142
- return Message.from(event, payload);
143
- }
144
-
145
- private handleEvent(event: MilkyEvent): void {
146
- const data = parseMessageReceiveData(event);
147
- if (data) {
148
- const message = this.$formatMessage(event);
149
- this.adapter.emit('message.receive', message);
150
- this.logger.debug(
151
- `${this.$config.name} recv ${message.$channel.type}(${message.$channel.id}):${segment.raw(message.$content)}`,
152
- );
153
- }
154
- }
155
-
156
- async $sendMessage(options: SendOptions): Promise<string> {
157
- const content = Array.isArray(options.content) ? options.content : [options.content];
158
- const segments = content.map((c) =>
159
- typeof c === 'string' ? { type: 'text' as const, data: { text: c } } : (c as { type: string; data?: Record<string, unknown> }),
160
- );
161
- const message = toMilkyOutgoingSegments(segments);
162
- if (options.type === 'group') {
163
- const result = await callApi(this.apiOptions(), 'send_group_message', {
164
- group_id: parseInt(options.id, 10),
165
- message,
166
- });
167
- const seq = (result as { message_seq?: number }).message_seq;
168
- this.logger.debug(`${this.$config.name} send group(${options.id}):${segment.raw(options.content)}`);
169
- return seq != null ? `group:${options.id}:${seq}` : '';
170
- }
171
- if (options.type === 'private') {
172
- const result = await callApi(this.apiOptions(), 'send_private_message', {
173
- user_id: parseInt(options.id, 10),
174
- message,
175
- });
176
- const seq = (result as { message_seq?: number }).message_seq;
177
- this.logger.debug(`${this.$config.name} send private(${options.id}):${segment.raw(options.content)}`);
178
- return seq != null ? `friend:${options.id}:${seq}` : '';
179
- }
180
- throw new Error('Either group or private must be provided');
181
- }
182
-
183
- async $recallMessage(id: string): Promise<void> {
184
- const parsed = parseMilkyMessageId(id);
185
- if (!parsed) throw new Error(`Invalid message id: ${id}`);
186
- if (parsed.message_scene === 'group') {
187
- await callApi(this.apiOptions(), 'recall_group_message', {
188
- group_id: parsed.peer_id,
189
- message_seq: parsed.message_seq,
190
- });
191
- } else {
192
- await callApi(this.apiOptions(), 'recall_private_message', {
193
- user_id: parsed.peer_id,
194
- message_seq: parsed.message_seq,
195
- });
196
- }
197
- }
198
-
199
- async kickMember(groupId: number, userId: number, rejectAddRequest = false): Promise<boolean> {
200
- await callApi(this.apiOptions(), 'kick_group_member', {
201
- group_id: groupId,
202
- user_id: userId,
203
- reject_add_request: rejectAddRequest,
204
- });
205
- return true;
206
- }
207
-
208
- async muteMember(groupId: number, userId: number, duration = 600): Promise<boolean> {
209
- await callApi(this.apiOptions(), 'set_group_member_mute', {
210
- group_id: groupId,
211
- user_id: userId,
212
- duration,
213
- });
214
- return true;
215
- }
216
-
217
- async muteAll(groupId: number, enable = true): Promise<boolean> {
218
- await callApi(this.apiOptions(), 'set_group_whole_mute', { group_id: groupId, is_mute: enable });
219
- return true;
220
- }
221
-
222
- async setAdmin(groupId: number, userId: number, enable = true): Promise<boolean> {
223
- await callApi(this.apiOptions(), 'set_group_member_admin', {
224
- group_id: groupId,
225
- user_id: userId,
226
- is_set: enable,
227
- });
228
- return true;
229
- }
230
-
231
- async setCard(groupId: number, userId: number, card: string): Promise<boolean> {
232
- await callApi(this.apiOptions(), 'set_group_member_card', {
233
- group_id: groupId,
234
- user_id: userId,
235
- card,
236
- });
237
- return true;
238
- }
239
-
240
- async setTitle(groupId: number, userId: number, title: string): Promise<boolean> {
241
- await callApi(this.apiOptions(), 'set_group_member_special_title', {
242
- group_id: groupId,
243
- user_id: userId,
244
- special_title: title,
245
- });
246
- return true;
247
- }
248
-
249
- async setGroupName(groupId: number, name: string): Promise<boolean> {
250
- await callApi(this.apiOptions(), 'set_group_name', { group_id: groupId, new_group_name: name });
251
- return true;
252
- }
253
-
254
- async getMemberList(groupId: number): Promise<unknown[]> {
255
- return callApi(this.apiOptions(), 'get_group_member_list', { group_id: groupId }) as Promise<unknown[]>;
256
- }
257
-
258
- async getGroupInfo(groupId: number): Promise<unknown> {
259
- return callApi(this.apiOptions(), 'get_group_info', { group_id: groupId });
260
- }
261
-
262
- private startHeartbeat(): void {
263
- const interval = this.$config.heartbeat_interval ?? 30000;
264
- this.heartbeatTimer = setInterval(() => {
265
- if (this.#client?.readyState === WebSocket.OPEN) this.#client.ping();
266
- }, interval);
267
- }
268
- }
package/src/types.ts DELETED
@@ -1,73 +0,0 @@
1
- /**
2
- * Milky 适配器类型与配置(与官方文档一致)
3
- */
4
-
5
- /** 配置公共字段;单一适配器下 context 均为 'milky',连接方式由 connection 区分 */
6
- export interface MilkyConfigBase {
7
- context: 'milky';
8
- name: string;
9
- baseUrl: string;
10
- access_token?: string;
11
- }
12
-
13
- /** WebSocket 正向连接 */
14
- export interface MilkyWsConfig extends MilkyConfigBase {
15
- connection: 'ws';
16
- reconnect_interval?: number;
17
- heartbeat_interval?: number;
18
- }
19
-
20
- /** SSE 连接 */
21
- export interface MilkySseConfig extends MilkyConfigBase {
22
- connection: 'sse';
23
- }
24
-
25
- /** Webhook(协议端 POST 到应用) */
26
- export interface MilkyWebhookConfig extends MilkyConfigBase {
27
- connection: 'webhook';
28
- path: string;
29
- }
30
-
31
- /** WebSocket 反向(协议端连应用) */
32
- export interface MilkyWssConfig extends MilkyConfigBase {
33
- connection: 'wss';
34
- path: string;
35
- heartbeat_interval?: number;
36
- }
37
-
38
- export type MilkyEndpointConfig = MilkyWsConfig | MilkySseConfig | MilkyWebhookConfig | MilkyWssConfig;
39
-
40
- /** 协议端 API 响应:status、retcode、data?、message? */
41
- export interface MilkyApiResponse<T = unknown> {
42
- status: string;
43
- retcode: number;
44
- data?: T;
45
- message?: string;
46
- }
47
-
48
- /** 事件结构:event_type、time、self_id、data */
49
- export interface MilkyEvent {
50
- event_type: string;
51
- time: number;
52
- self_id: number;
53
- data?: Record<string, unknown>;
54
- }
55
-
56
- /** 接收消息 data(message_receive):message_scene、peer_id、message_seq、sender_id、time、segments、可选 friend/group/group_member */
57
- export interface MilkyIncomingMessage {
58
- message_scene: 'friend' | 'group' | 'temp';
59
- peer_id: number;
60
- message_seq: number;
61
- sender_id: number;
62
- time: number;
63
- segments: MilkyIncomingSegment[];
64
- friend?: { user_id: number; nickname?: string };
65
- group?: { group_id: number; group_name?: string };
66
- group_member?: { user_id: number; nickname?: string; card?: string; role?: string };
67
- }
68
-
69
- /** 接收消息段:type、data */
70
- export interface MilkyIncomingSegment {
71
- type: string;
72
- data?: Record<string, unknown>;
73
- }