@zhin.js/adapter-milky 1.0.0 → 1.1.0

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 (100) hide show
  1. package/CHANGELOG.md +962 -0
  2. package/README.md +58 -117
  3. package/adapters/milky.js +51 -0
  4. package/adapters/milky.ts +74 -0
  5. package/agent/PERMITS.md +14 -0
  6. package/{skills/milky/SKILL.md → agent/skills/milky.md} +1 -1
  7. package/commands/endpoint/add/[id].js +3 -0
  8. package/commands/endpoint/add/[id].ts +3 -0
  9. package/commands/endpoint/list.js +3 -0
  10. package/commands/endpoint/list.ts +3 -0
  11. package/commands/endpoint/remove/[id].js +3 -0
  12. package/commands/endpoint/remove/[id].ts +3 -0
  13. package/lib/client.d.ts +21 -0
  14. package/lib/client.js +33 -0
  15. package/lib/endpoint-management.d.ts +17 -0
  16. package/lib/endpoint-management.js +47 -0
  17. package/lib/endpoint.d.ts +7 -0
  18. package/lib/endpoint.js +6 -0
  19. package/lib/index.d.ts +6 -20
  20. package/lib/index.js +4 -33
  21. package/lib/milky-auth.d.ts +2 -0
  22. package/lib/milky-auth.js +16 -0
  23. package/lib/milky-endpoint-commands.d.ts +1 -0
  24. package/lib/milky-endpoint-commands.js +18 -0
  25. package/lib/milky-runtime-state.d.ts +1 -0
  26. package/lib/milky-runtime-state.js +6 -0
  27. package/lib/protocol.d.ts +174 -0
  28. package/lib/protocol.js +494 -0
  29. package/lib/sse-client.d.ts +20 -0
  30. package/lib/sse-client.js +85 -0
  31. package/lib/sse-endpoint.d.ts +32 -0
  32. package/lib/sse-endpoint.js +207 -0
  33. package/lib/webhook-endpoint.d.ts +22 -0
  34. package/lib/webhook-endpoint.js +149 -0
  35. package/lib/ws-endpoint.d.ts +23 -0
  36. package/lib/ws-endpoint.js +233 -0
  37. package/lib/ws-types.d.ts +11 -0
  38. package/lib/ws-types.js +1 -0
  39. package/lib/wss-endpoint.d.ts +25 -0
  40. package/lib/wss-endpoint.js +184 -0
  41. package/package.json +73 -19
  42. package/plugin.js +14 -0
  43. package/schema.json +100 -0
  44. package/src/client.ts +80 -0
  45. package/src/endpoint-management.ts +74 -0
  46. package/src/endpoint.ts +26 -0
  47. package/src/index.ts +63 -45
  48. package/src/milky-auth.ts +14 -0
  49. package/src/milky-endpoint-commands.ts +19 -0
  50. package/src/milky-runtime-state.ts +7 -0
  51. package/src/protocol.ts +647 -0
  52. package/src/sse-client.ts +106 -0
  53. package/src/sse-endpoint.ts +258 -0
  54. package/src/webhook-endpoint.ts +196 -0
  55. package/src/ws-endpoint.ts +291 -0
  56. package/src/ws-types.ts +12 -0
  57. package/src/wss-endpoint.ts +228 -0
  58. package/lib/adapter.d.ts +0 -28
  59. package/lib/adapter.d.ts.map +0 -1
  60. package/lib/adapter.js +0 -90
  61. package/lib/adapter.js.map +0 -1
  62. package/lib/api.d.ts +0 -10
  63. package/lib/api.d.ts.map +0 -1
  64. package/lib/api.js +0 -48
  65. package/lib/api.js.map +0 -1
  66. package/lib/bot-sse.d.ts +0 -31
  67. package/lib/bot-sse.d.ts.map +0 -1
  68. package/lib/bot-sse.js +0 -195
  69. package/lib/bot-sse.js.map +0 -1
  70. package/lib/bot-webhook.d.ts +0 -34
  71. package/lib/bot-webhook.d.ts.map +0 -1
  72. package/lib/bot-webhook.js +0 -187
  73. package/lib/bot-webhook.js.map +0 -1
  74. package/lib/bot-ws.d.ts +0 -35
  75. package/lib/bot-ws.d.ts.map +0 -1
  76. package/lib/bot-ws.js +0 -262
  77. package/lib/bot-ws.js.map +0 -1
  78. package/lib/bot-wss.d.ts +0 -34
  79. package/lib/bot-wss.d.ts.map +0 -1
  80. package/lib/bot-wss.js +0 -225
  81. package/lib/bot-wss.js.map +0 -1
  82. package/lib/index.d.ts.map +0 -1
  83. package/lib/index.js.map +0 -1
  84. package/lib/types.d.ts +0 -75
  85. package/lib/types.d.ts.map +0 -1
  86. package/lib/types.js +0 -5
  87. package/lib/types.js.map +0 -1
  88. package/lib/utils.d.ts +0 -35
  89. package/lib/utils.d.ts.map +0 -1
  90. package/lib/utils.js +0 -157
  91. package/lib/utils.js.map +0 -1
  92. package/plugin.yml +0 -3
  93. package/src/adapter.ts +0 -101
  94. package/src/api.ts +0 -63
  95. package/src/bot-sse.ts +0 -228
  96. package/src/bot-webhook.ts +0 -221
  97. package/src/bot-ws.ts +0 -296
  98. package/src/bot-wss.ts +0 -268
  99. package/src/types.ts +0 -73
  100. package/src/utils.ts +0 -174
package/lib/types.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
package/lib/utils.d.ts DELETED
@@ -1,35 +0,0 @@
1
- /**
2
- * Milky 事件/消息段与 zhin Message 的转换
3
- */
4
- import type { MessageBase } from 'zhin.js';
5
- import type { MilkyEvent, MilkyIncomingMessage, MilkyIncomingSegment } from './types.js';
6
- /** 将 Milky 接收消息段转为 zhin 的 $content(MessageSegment[]) */
7
- export declare function formatMilkySegments(segments: MilkyIncomingSegment[]): Array<{
8
- type: string;
9
- data: Record<string, unknown>;
10
- }>;
11
- /** message_receive 事件的 data 转为 zhin Message 的构造参数(含 $adapter、$bot) */
12
- export declare function formatMilkyMessagePayload(event: MilkyEvent, data: MilkyIncomingMessage, recallFn: (msgId: string) => Promise<void>, replyFn: (channel: {
13
- id: string;
14
- type: 'group' | 'private';
15
- }, content: (string | {
16
- type: string;
17
- data?: Record<string, unknown>;
18
- })[], quote?: boolean | string) => Promise<string>, adapterName: 'milky', botName: string): MessageBase;
19
- /** 根据 event_type 判断是否为 message_receive,并解析 data 为 MilkyIncomingMessage */
20
- export declare function parseMessageReceiveData(event: MilkyEvent): MilkyIncomingMessage | null;
21
- /** zhin SendContent(string | MessageElement 或数组)转为 Milky OutgoingSegment[](发送用) */
22
- export declare function toMilkyOutgoingSegments(content: (string | {
23
- type: string;
24
- data?: Record<string, unknown>;
25
- })[]): Array<{
26
- type: string;
27
- data: Record<string, unknown>;
28
- }>;
29
- /** 解析 $id 为 message_scene、peer_id、message_seq(用于撤回) */
30
- export declare function parseMilkyMessageId(msgId: string): {
31
- message_scene: 'friend' | 'group' | 'temp';
32
- peer_id: number;
33
- message_seq: number;
34
- } | null;
35
- //# sourceMappingURL=utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,SAAS,CAAC;AAExD,OAAO,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEzF,wDAAwD;AACxD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAmD5H;AAED,sEAAsE;AACtE,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,oBAAoB,EAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EAC1C,OAAO,EAAE,CAAC,OAAO,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,GAAG,SAAS,CAAA;CAAE,EAAE,OAAO,EAAE,CAAC,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,EAClL,WAAW,EAAE,OAAO,EACpB,OAAO,EAAE,MAAM,GACd,WAAW,CAyBb;AAED,0EAA0E;AAC1E,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,UAAU,GAAG,oBAAoB,GAAG,IAAI,CAKtF;AAED,mFAAmF;AACnF,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,CAAC,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,EAAE,GACrE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAoDxD;AAED,uDAAuD;AACvD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,GACZ;IAAE,aAAa,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAS7F"}
package/lib/utils.js DELETED
@@ -1,157 +0,0 @@
1
- /** 将 Milky 接收消息段转为 zhin 的 $content(MessageSegment[]) */
2
- export function formatMilkySegments(segments) {
3
- return segments.map((seg) => {
4
- const type = seg.type;
5
- const data = seg.data ?? {};
6
- switch (type) {
7
- case 'text':
8
- return { type: 'text', data: { text: data.text ?? '' } };
9
- case 'mention':
10
- return { type: 'at', data: { id: String(data.user_id ?? '') } };
11
- case 'mention_all':
12
- return { type: 'at', data: { type: 'all' } };
13
- case 'face':
14
- return { type: 'face', data: { id: data.face_id ?? '' } };
15
- case 'reply':
16
- return {
17
- type: 'reply',
18
- data: { message_seq: data.message_seq },
19
- };
20
- case 'image':
21
- return {
22
- type: 'image',
23
- data: {
24
- url: data.temp_url ?? data.resource_id ?? '',
25
- },
26
- };
27
- case 'record':
28
- return {
29
- type: 'record',
30
- data: {
31
- url: data.temp_url ?? data.resource_id ?? '',
32
- },
33
- };
34
- case 'video':
35
- return {
36
- type: 'video',
37
- data: {
38
- url: data.temp_url ?? data.resource_id ?? '',
39
- },
40
- };
41
- case 'file':
42
- return {
43
- type: 'file',
44
- data: {
45
- file_id: data.file_id,
46
- name: data.file_name,
47
- },
48
- };
49
- default:
50
- return { type, data: data };
51
- }
52
- });
53
- }
54
- /** message_receive 事件的 data 转为 zhin Message 的构造参数(含 $adapter、$bot) */
55
- export function formatMilkyMessagePayload(event, data, recallFn, replyFn, adapterName, botName) {
56
- const scene = data.message_scene;
57
- const isGroup = scene === 'group';
58
- const channelId = data.peer_id.toString();
59
- const msgId = `${data.message_scene}:${data.peer_id}:${data.message_seq}`;
60
- const channel = { id: channelId, type: (isGroup ? 'group' : 'private') };
61
- const senderId = data.sender_id.toString();
62
- const senderName = (isGroup ? data.group_member?.card ?? data.group_member?.nickname : data.friend?.nickname) ?? senderId;
63
- const content = formatMilkySegments(data.segments);
64
- const raw = content.map((c) => (c.type === 'text' ? c.data.text : '')).join('');
65
- return {
66
- $id: msgId,
67
- $adapter: 'milky',
68
- $bot: botName,
69
- $channel: channel,
70
- $sender: { id: senderId, name: senderName },
71
- $content: content,
72
- $raw: raw,
73
- $timestamp: data.time,
74
- $recall: () => recallFn(msgId),
75
- $reply: (cnt, quote) => replyFn(channel, (Array.isArray(cnt) ? cnt : [cnt]), quote),
76
- };
77
- }
78
- /** 根据 event_type 判断是否为 message_receive,并解析 data 为 MilkyIncomingMessage */
79
- export function parseMessageReceiveData(event) {
80
- if (event.event_type !== 'message_receive' || !event.data)
81
- return null;
82
- const data = event.data;
83
- if (!data.message_scene || !Number.isInteger(data.peer_id) || !Array.isArray(data.segments))
84
- return null;
85
- return data;
86
- }
87
- /** zhin SendContent(string | MessageElement 或数组)转为 Milky OutgoingSegment[](发送用) */
88
- export function toMilkyOutgoingSegments(content) {
89
- const out = [];
90
- for (const seg of content) {
91
- const type = typeof seg === 'string' ? 'text' : seg.type;
92
- const data = typeof seg === 'string' ? { text: seg } : (seg.data ?? {});
93
- switch (type) {
94
- case 'text':
95
- out.push({ type: 'text', data: { text: String(data.text ?? '') } });
96
- break;
97
- case 'at':
98
- if (data.type === 'all') {
99
- out.push({ type: 'mention_all', data: {} });
100
- }
101
- else {
102
- const id = data.id;
103
- if (id)
104
- out.push({ type: 'mention', data: { user_id: Number(id) || 0 } });
105
- }
106
- break;
107
- case 'face':
108
- out.push({ type: 'face', data: { face_id: String(data.id ?? ''), is_large: false } });
109
- break;
110
- case 'reply':
111
- // message_id 可能为 scene:peer:seq 或仅 message_seq
112
- const mid = data.message_id ?? data.message_seq;
113
- const seq = typeof mid === 'number' ? mid : parseInt(String(mid).split(':').pop() ?? '0', 10);
114
- out.push({ type: 'reply', data: { message_seq: seq } });
115
- break;
116
- case 'image':
117
- out.push({
118
- type: 'image',
119
- data: { uri: String(data.url ?? data.uri ?? '') },
120
- });
121
- break;
122
- case 'record':
123
- out.push({
124
- type: 'record',
125
- data: { uri: String(data.url ?? data.uri ?? '') },
126
- });
127
- break;
128
- case 'video':
129
- out.push({
130
- type: 'video',
131
- data: {
132
- uri: String(data.url ?? data.uri ?? ''),
133
- thumb_uri: data.thumb_uri,
134
- },
135
- });
136
- break;
137
- default:
138
- out.push({ type, data: data });
139
- }
140
- }
141
- return out;
142
- }
143
- /** 解析 $id 为 message_scene、peer_id、message_seq(用于撤回) */
144
- export function parseMilkyMessageId(msgId) {
145
- const parts = msgId.split(':');
146
- if (parts.length < 3)
147
- return null;
148
- const [scene, peer, seq] = parts;
149
- if (scene !== 'friend' && scene !== 'group' && scene !== 'temp')
150
- return null;
151
- const peerId = parseInt(peer, 10);
152
- const messageSeq = parseInt(seq, 10);
153
- if (Number.isNaN(peerId) || Number.isNaN(messageSeq))
154
- return null;
155
- return { message_scene: scene, peer_id: peerId, message_seq: messageSeq };
156
- }
157
- //# sourceMappingURL=utils.js.map
package/lib/utils.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/plugin.yml DELETED
@@ -1,3 +0,0 @@
1
- name: adapter-milky
2
- description: Milky 协议适配器:QQ 群管理
3
- version: 0.0.8
package/src/adapter.ts DELETED
@@ -1,101 +0,0 @@
1
- /**
2
- * Milky 适配器:单一适配器支持 WS 正向 / SSE / Webhook / 反向 WS,由 config.connection 区分
3
- */
4
- import { formatCompact, Adapter, Plugin, type IGroupManagement } from 'zhin.js';
5
- import type { Router } from '@zhin.js/host-router';
6
- import { MilkyWsClient } from './bot-ws.js';
7
- import { MilkySseClient } from './bot-sse.js';
8
- import { MilkyWebhookBot } from './bot-webhook.js';
9
- import { MilkyWssServer } from './bot-wss.js';
10
- import type {
11
- MilkyBotConfig,
12
- MilkyWsConfig,
13
- MilkySseConfig,
14
- MilkyWebhookConfig,
15
- MilkyWssConfig,
16
- } from './types.js';
17
-
18
- export type MilkyBot = MilkyWsClient | MilkySseClient | MilkyWebhookBot | MilkyWssServer;
19
-
20
- export class MilkyAdapter extends Adapter<MilkyBot> {
21
- #router?: Router;
22
-
23
- constructor(plugin: Plugin) {
24
- super(plugin, 'milky', []);
25
- }
26
-
27
- createBot(config: MilkyBotConfig): MilkyBot {
28
- switch (config.connection) {
29
- case 'ws':
30
- return new MilkyWsClient(this, config as MilkyWsConfig);
31
- case 'sse':
32
- return new MilkySseClient(this, config as MilkySseConfig);
33
- case 'webhook':
34
- if (!this.#router) throw new Error('Milky connection: webhook 需要 router,请安装并在配置中启用 @zhin.js/host-router');
35
- return new MilkyWebhookBot(this, this.#router, config as MilkyWebhookConfig);
36
- case 'wss':
37
- if (!this.#router) throw new Error('Milky connection: wss 需要 router,请安装并在配置中启用 @zhin.js/host-router');
38
- return new MilkyWssServer(this, this.#router, config as MilkyWssConfig);
39
- default:
40
- throw new Error(`Unknown Milky connection: ${(config as MilkyBotConfig).connection}`);
41
- }
42
- }
43
-
44
- async kickMember(botId: string, sceneId: string, userId: string) {
45
- const bot = this.bots.get(botId);
46
- if (!bot) throw new Error(`Bot ${botId} 不存在`);
47
- return bot.kickMember(Number(sceneId), Number(userId), false);
48
- }
49
-
50
- async muteMember(botId: string, sceneId: string, userId: string, duration = 600) {
51
- const bot = this.bots.get(botId);
52
- if (!bot) throw new Error(`Bot ${botId} 不存在`);
53
- return bot.muteMember(Number(sceneId), Number(userId), duration);
54
- }
55
-
56
- async muteAll(botId: string, sceneId: string, enable = true) {
57
- const bot = this.bots.get(botId);
58
- if (!bot) throw new Error(`Bot ${botId} 不存在`);
59
- return bot.muteAll(Number(sceneId), enable);
60
- }
61
-
62
- async setAdmin(botId: string, sceneId: string, userId: string, enable = true) {
63
- const bot = this.bots.get(botId);
64
- if (!bot) throw new Error(`Bot ${botId} 不存在`);
65
- return bot.setAdmin(Number(sceneId), Number(userId), enable);
66
- }
67
-
68
- async setMemberNickname(botId: string, sceneId: string, userId: string, nickname: string) {
69
- const bot = this.bots.get(botId);
70
- if (!bot) throw new Error(`Bot ${botId} 不存在`);
71
- return bot.setCard(Number(sceneId), Number(userId), nickname);
72
- }
73
-
74
- async setGroupName(botId: string, sceneId: string, name: string) {
75
- const bot = this.bots.get(botId);
76
- if (!bot) throw new Error(`Bot ${botId} 不存在`);
77
- return bot.setGroupName(Number(sceneId), name);
78
- }
79
-
80
- async listMembers(botId: string, sceneId: string) {
81
- const bot = this.bots.get(botId);
82
- if (!bot) throw new Error(`Bot ${botId} 不存在`);
83
- const members = await bot.getMemberList(Number(sceneId));
84
- return { members: Array.isArray(members) ? members : [], count: Array.isArray(members) ? members.length : 0 };
85
- }
86
-
87
- async getGroupInfo(botId: string, sceneId: string) {
88
- const bot = this.bots.get(botId);
89
- if (!bot) throw new Error(`Bot ${botId} 不存在`);
90
- return bot.getGroupInfo(Number(sceneId));
91
- }
92
-
93
- async start(): Promise<void> {
94
- // 同步获取已就绪的 router,或通过 useContext 在 router 挂载后赋值
95
- this.#router = (this.plugin.inject as (key: string) => Router | undefined)('router');
96
- (this.plugin.useContext as (key: string, fn: (router: Router) => void) => void)('router', (router: Router) => {
97
- this.#router = router;
98
- });
99
- await super.start();
100
- }
101
- }
package/src/api.ts DELETED
@@ -1,63 +0,0 @@
1
- /**
2
- * Milky HTTP API 封装:鉴权 + POST /api/:api
3
- */
4
- import type { MilkyApiResponse } from './types.js';
5
-
6
- export interface MilkyApiClientOptions {
7
- baseUrl: string;
8
- access_token?: string;
9
- }
10
-
11
- /** 鉴权:Header Authorization: Bearer {token} 或 URL query access_token=xxx */
12
- function authHeaders(access_token?: string): Record<string, string> {
13
- const headers: Record<string, string> = {};
14
- if (access_token) {
15
- headers['Authorization'] = `Bearer ${access_token}`;
16
- }
17
- return headers;
18
- }
19
-
20
- function authQuery(access_token?: string): string {
21
- if (!access_token) return '';
22
- return `access_token=${encodeURIComponent(access_token)}`;
23
- }
24
-
25
- /**
26
- * 调用协议端 API:POST {baseUrl}/api/{apiName},Body JSON,鉴权。
27
- * 非 200 或 retcode !== 0 时抛错。
28
- */
29
- export async function callApi<T = unknown>(
30
- options: MilkyApiClientOptions,
31
- apiName: string,
32
- params: Record<string, unknown> = {},
33
- ): Promise<T> {
34
- const { baseUrl, access_token } = options;
35
- const url = new URL(`/api/${apiName}`, baseUrl.replace(/\/$/, ''));
36
- const q = authQuery(access_token);
37
- if (q) url.search = (url.search ? url.search + '&' : '') + q;
38
-
39
- const res = await fetch(url.toString(), {
40
- method: 'POST',
41
- headers: {
42
- 'Content-Type': 'application/json',
43
- ...authHeaders(access_token),
44
- },
45
- body: JSON.stringify(Object.keys(params).length ? params : {}),
46
- });
47
-
48
- const text = await res.text();
49
- let body: MilkyApiResponse<T>;
50
- try {
51
- body = JSON.parse(text) as MilkyApiResponse<T>;
52
- } catch {
53
- throw new Error(`Milky API ${apiName}: invalid JSON response (${res.status}) ${text.slice(0, 200)}`);
54
- }
55
-
56
- if (res.status !== 200) {
57
- throw new Error(`Milky API ${apiName}: HTTP ${res.status} ${body.message ?? text}`);
58
- }
59
- if (body.retcode !== 0) {
60
- throw new Error(`Milky API ${apiName}: retcode=${body.retcode} ${body.message ?? ''}`);
61
- }
62
- return (body.data ?? {}) as T;
63
- }
package/src/bot-sse.ts DELETED
@@ -1,228 +0,0 @@
1
- /**
2
- * Milky SSE Bot:应用 GET 协议端 /event,Accept: text/event-stream,解析 SSE 的 milky_event
3
- */
4
- import EventSource from 'eventsource';
5
- import { EventEmitter } from 'events';
6
- import { formatCompact, Bot, Message, segment, SendOptions } from 'zhin.js';
7
- import { callApi } from './api.js';
8
- import type { MilkySseConfig, MilkyEvent } from './types.js';
9
- import type { MilkyAdapter } from './adapter.js';
10
- import {
11
- formatMilkyMessagePayload,
12
- parseMessageReceiveData,
13
- toMilkyOutgoingSegments,
14
- parseMilkyMessageId,
15
- } from './utils.js';
16
-
17
- export class MilkySseClient extends EventEmitter implements Bot<MilkySseConfig, MilkyEvent> {
18
- $connected: boolean;
19
- private es?: EventSource;
20
-
21
- get logger() {
22
- return this.adapter.plugin.logger;
23
- }
24
-
25
- constructor(public adapter: MilkyAdapter, public $config: MilkySseConfig) {
26
- super();
27
- this.$connected = false;
28
- }
29
-
30
- get $id() {
31
- return this.$config.name;
32
- }
33
-
34
- private get eventUrl(): string {
35
- const base = this.$config.baseUrl.replace(/\/$/, '');
36
- const url = `${base}/event`;
37
- const token = this.$config.access_token;
38
- if (token) return `${url}${url.includes('?') ? '&' : '?'}access_token=${encodeURIComponent(token)}`;
39
- return url;
40
- }
41
-
42
- private apiOptions() {
43
- return { baseUrl: this.$config.baseUrl, access_token: this.$config.access_token };
44
- }
45
-
46
- async $connect(): Promise<void> {
47
- const headers: Record<string, string> = { Accept: 'text/event-stream' };
48
- if (this.$config.access_token) {
49
- headers['Authorization'] = `Bearer ${this.$config.access_token}`;
50
- }
51
- this.es = new EventSource(this.eventUrl, { headers });
52
- this.$connected = true;
53
- if (!this.$config.access_token) {
54
- this.logger.warn(formatCompact({ bot: this.$id, ok: false, error: 'missing access_token' }));
55
- }
56
-
57
- this.es.addEventListener('milky_event', (e: MessageEvent) => {
58
- try {
59
- const event = JSON.parse(e.data) as MilkyEvent;
60
- this.handleEvent(event);
61
- } catch (err: unknown) {
62
- this.emit('error', err instanceof Error ? err : new Error(String(err)));
63
- }
64
- });
65
-
66
- this.es.onerror = (err: unknown) => {
67
- this.emit('error', err instanceof Error ? err : new Error(String(err)));
68
- };
69
- }
70
-
71
- async $disconnect(): Promise<void> {
72
- if (this.es) {
73
- this.es.close();
74
- this.es = undefined;
75
- }
76
- this.$connected = false;
77
- }
78
-
79
- $formatMessage(event: MilkyEvent): Message<MilkyEvent> {
80
- const data = parseMessageReceiveData(event);
81
- if (!data) {
82
- return Message.from(event, {
83
- $id: '',
84
- $adapter: 'milky',
85
- $bot: this.$config.name,
86
- $channel: { id: '', type: 'private' },
87
- $sender: { id: '', name: '' },
88
- $content: [],
89
- $raw: '',
90
- $timestamp: event.time ?? 0,
91
- $recall: async () => {},
92
- $reply: async () => '',
93
- });
94
- }
95
- const payload = formatMilkyMessagePayload(
96
- event,
97
- data,
98
- (id) => this.$recallMessage(id),
99
- (channel, content) =>
100
- this.adapter.sendMessage({
101
- ...channel,
102
- context: 'milky',
103
- bot: this.$config.name,
104
- content: content as import('zhin.js').SendContent,
105
- }),
106
- 'milky',
107
- this.$config.name,
108
- );
109
- return Message.from(event, payload);
110
- }
111
-
112
- private handleEvent(event: MilkyEvent): void {
113
- const data = parseMessageReceiveData(event);
114
- if (data) {
115
- const message = this.$formatMessage(event);
116
- this.adapter.emit('message.receive', message);
117
- this.logger.debug(
118
- `${this.$config.name} recv ${message.$channel.type}(${message.$channel.id}):${segment.raw(message.$content)}`,
119
- );
120
- }
121
- }
122
-
123
- async $sendMessage(options: SendOptions): Promise<string> {
124
- const content = Array.isArray(options.content) ? options.content : [options.content];
125
- const segments = content.map((c) =>
126
- typeof c === 'string' ? { type: 'text' as const, data: { text: c } } : (c as { type: string; data?: Record<string, unknown> }),
127
- );
128
- const message = toMilkyOutgoingSegments(segments);
129
- if (options.type === 'group') {
130
- const result = await callApi(this.apiOptions(), 'send_group_message', {
131
- group_id: parseInt(options.id, 10),
132
- message,
133
- });
134
- const seq = (result as { message_seq?: number }).message_seq;
135
- this.logger.debug(`${this.$config.name} send group(${options.id}):${segment.raw(options.content)}`);
136
- return seq != null ? `group:${options.id}:${seq}` : '';
137
- }
138
- if (options.type === 'private') {
139
- const result = await callApi(this.apiOptions(), 'send_private_message', {
140
- user_id: parseInt(options.id, 10),
141
- message,
142
- });
143
- const seq = (result as { message_seq?: number }).message_seq;
144
- this.logger.debug(`${this.$config.name} send private(${options.id}):${segment.raw(options.content)}`);
145
- return seq != null ? `friend:${options.id}:${seq}` : '';
146
- }
147
- throw new Error('Either group or private must be provided');
148
- }
149
-
150
- async $recallMessage(id: string): Promise<void> {
151
- const parsed = parseMilkyMessageId(id);
152
- if (!parsed) throw new Error(`Invalid message id: ${id}`);
153
- if (parsed.message_scene === 'group') {
154
- await callApi(this.apiOptions(), 'recall_group_message', {
155
- group_id: parsed.peer_id,
156
- message_seq: parsed.message_seq,
157
- });
158
- } else {
159
- await callApi(this.apiOptions(), 'recall_private_message', {
160
- user_id: parsed.peer_id,
161
- message_seq: parsed.message_seq,
162
- });
163
- }
164
- }
165
-
166
- async kickMember(groupId: number, userId: number, rejectAddRequest = false): Promise<boolean> {
167
- await callApi(this.apiOptions(), 'kick_group_member', {
168
- group_id: groupId,
169
- user_id: userId,
170
- reject_add_request: rejectAddRequest,
171
- });
172
- return true;
173
- }
174
-
175
- async muteMember(groupId: number, userId: number, duration = 600): Promise<boolean> {
176
- await callApi(this.apiOptions(), 'set_group_member_mute', {
177
- group_id: groupId,
178
- user_id: userId,
179
- duration,
180
- });
181
- return true;
182
- }
183
-
184
- async muteAll(groupId: number, enable = true): Promise<boolean> {
185
- await callApi(this.apiOptions(), 'set_group_whole_mute', { group_id: groupId, is_mute: enable });
186
- return true;
187
- }
188
-
189
- async setAdmin(groupId: number, userId: number, enable = true): Promise<boolean> {
190
- await callApi(this.apiOptions(), 'set_group_member_admin', {
191
- group_id: groupId,
192
- user_id: userId,
193
- is_set: enable,
194
- });
195
- return true;
196
- }
197
-
198
- async setCard(groupId: number, userId: number, card: string): Promise<boolean> {
199
- await callApi(this.apiOptions(), 'set_group_member_card', {
200
- group_id: groupId,
201
- user_id: userId,
202
- card,
203
- });
204
- return true;
205
- }
206
-
207
- async setTitle(groupId: number, userId: number, title: string): Promise<boolean> {
208
- await callApi(this.apiOptions(), 'set_group_member_special_title', {
209
- group_id: groupId,
210
- user_id: userId,
211
- special_title: title,
212
- });
213
- return true;
214
- }
215
-
216
- async setGroupName(groupId: number, name: string): Promise<boolean> {
217
- await callApi(this.apiOptions(), 'set_group_name', { group_id: groupId, new_group_name: name });
218
- return true;
219
- }
220
-
221
- async getMemberList(groupId: number): Promise<unknown[]> {
222
- return callApi(this.apiOptions(), 'get_group_member_list', { group_id: groupId }) as Promise<unknown[]>;
223
- }
224
-
225
- async getGroupInfo(groupId: number): Promise<unknown> {
226
- return callApi(this.apiOptions(), 'get_group_info', { group_id: groupId });
227
- }
228
- }