@zhin.js/adapter-satori 5.0.2 → 5.0.5
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 +50 -0
- package/adapters/satori.js +2 -2
- package/adapters/satori.ts +2 -2
- package/lib/endpoint.js +27 -25
- package/lib/protocol.d.ts +8 -5
- package/lib/protocol.js +9 -7
- package/package.json +13 -13
- package/schema.json +41 -5
- package/src/endpoint.ts +30 -27
- package/src/protocol.ts +15 -11
- /package/commands/endpoint/add/{[name].js → [id].js} +0 -0
- /package/commands/endpoint/add/{[name].ts → [id].ts} +0 -0
- /package/commands/endpoint/remove/{[name].js → [id].js} +0 -0
- /package/commands/endpoint/remove/{[name].ts → [id].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# @zhin.js/adapter-satori
|
|
2
2
|
|
|
3
|
+
## 5.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [2d0a622]
|
|
8
|
+
- @zhin.js/command@1.0.10
|
|
9
|
+
- @zhin.js/adapter@1.1.7
|
|
10
|
+
- @zhin.js/core@1.5.5
|
|
11
|
+
- zhin.js@6.0.5
|
|
12
|
+
|
|
13
|
+
## 5.0.4
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [0de46a8]
|
|
18
|
+
- Updated dependencies [c106ecc]
|
|
19
|
+
- Updated dependencies [ca92e03]
|
|
20
|
+
- Updated dependencies [b0f37ae]
|
|
21
|
+
- Updated dependencies [ba08a2f]
|
|
22
|
+
- Updated dependencies [daffd4c]
|
|
23
|
+
- Updated dependencies [36c7400]
|
|
24
|
+
- Updated dependencies [162fa34]
|
|
25
|
+
- Updated dependencies [e40b048]
|
|
26
|
+
- Updated dependencies [f1708c3]
|
|
27
|
+
- Updated dependencies [e53444f]
|
|
28
|
+
- Updated dependencies [92b0dd7]
|
|
29
|
+
- Updated dependencies [a7df753]
|
|
30
|
+
- @zhin.js/host-http@1.0.8
|
|
31
|
+
- @zhin.js/im-contract@1.0.3
|
|
32
|
+
- @zhin.js/adapter@1.1.7
|
|
33
|
+
- @zhin.js/plugin-runtime@1.1.5
|
|
34
|
+
- @zhin.js/command@1.0.9
|
|
35
|
+
- @zhin.js/core@1.5.4
|
|
36
|
+
- zhin.js@6.0.4
|
|
37
|
+
|
|
38
|
+
## 5.0.3
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- f8c7a54: fix: im
|
|
43
|
+
- Updated dependencies [f8c7a54]
|
|
44
|
+
- @zhin.js/logger@1.0.76
|
|
45
|
+
- @zhin.js/host-http@1.0.7
|
|
46
|
+
- @zhin.js/adapter@1.1.6
|
|
47
|
+
- @zhin.js/command@1.0.8
|
|
48
|
+
- @zhin.js/core@1.5.3
|
|
49
|
+
- @zhin.js/im-contract@1.0.2
|
|
50
|
+
- @zhin.js/plugin-runtime@1.1.4
|
|
51
|
+
- zhin.js@6.0.3
|
|
52
|
+
|
|
3
53
|
## 5.0.2
|
|
4
54
|
|
|
5
55
|
### Patch Changes
|
package/adapters/satori.js
CHANGED
|
@@ -20,8 +20,8 @@ export default defineAdapter({
|
|
|
20
20
|
const config = resolveSatoriConfig(context.config);
|
|
21
21
|
const gateway = context.use(messageGatewayToken);
|
|
22
22
|
// 注册到插件运行时状态(satori.endpoint list 的"运行中"数据源)
|
|
23
|
-
context.use(satoriRuntimeStateToken).endpoints.set(config.
|
|
24
|
-
|
|
23
|
+
context.use(satoriRuntimeStateToken).endpoints.set(config.id, {
|
|
24
|
+
id: config.id,
|
|
25
25
|
mode: config.connection,
|
|
26
26
|
});
|
|
27
27
|
if (config.connection === 'webhook') {
|
package/adapters/satori.ts
CHANGED
|
@@ -31,8 +31,8 @@ export default defineAdapter<SatoriAdapterConfig>({
|
|
|
31
31
|
const config = resolveSatoriConfig(context.config);
|
|
32
32
|
const gateway = context.use(messageGatewayToken);
|
|
33
33
|
// 注册到插件运行时状态(satori.endpoint list 的"运行中"数据源)
|
|
34
|
-
context.use(satoriRuntimeStateToken).endpoints.set(config.
|
|
35
|
-
|
|
34
|
+
context.use(satoriRuntimeStateToken).endpoints.set(config.id, {
|
|
35
|
+
id: config.id,
|
|
36
36
|
mode: config.connection,
|
|
37
37
|
});
|
|
38
38
|
if (config.connection === 'webhook') {
|
package/lib/endpoint.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* SatoriEndpoint — WebSocket and webhook lifecycle, outbound, admit.
|
|
3
3
|
*/
|
|
4
4
|
import { createEndpointLifecycle, } from '@zhin.js/adapter';
|
|
5
|
-
import { formatCompact,
|
|
5
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
6
6
|
import { SatoriOpcode, buildWsUrl, callSatoriApi, extractCreatedMessageId, formatInboundContent, formatMessageId, formatSatoriOutbound, isMessageEvent, isSelfMentioned, parseMessageRef, resolveInboundSender, satoriInboundConversation, } from './protocol.js';
|
|
7
7
|
import { registerSatoriWebhookRoutes } from './webhook.js';
|
|
8
8
|
import { WS_OPEN, defaultCreateWebSocket, } from './ws.js';
|
|
9
|
-
const logger = getLogger('satori');
|
|
10
9
|
export class SatoriWsEndpoint {
|
|
10
|
+
#logger;
|
|
11
11
|
#options;
|
|
12
12
|
#lifecycle;
|
|
13
13
|
#ws = null;
|
|
@@ -16,10 +16,11 @@ export class SatoriWsEndpoint {
|
|
|
16
16
|
#open = false;
|
|
17
17
|
management = createSatoriEndpointManagement((resource, method, params) => this.#api(resource, method, params));
|
|
18
18
|
constructor(options) {
|
|
19
|
+
this.#logger = getAdapterLogger('satori', options.config.id);
|
|
19
20
|
this.#options = options;
|
|
20
21
|
const { config } = options;
|
|
21
22
|
this.#lifecycle = createEndpointLifecycle({
|
|
22
|
-
name: config.
|
|
23
|
+
name: config.id,
|
|
23
24
|
reconnect: {
|
|
24
25
|
initialIntervalMs: 5_000,
|
|
25
26
|
// 固定间隔(multiplier 1、无抖动),对齐旧 5s 固定重连语义
|
|
@@ -71,9 +72,9 @@ export class SatoriWsEndpoint {
|
|
|
71
72
|
content,
|
|
72
73
|
});
|
|
73
74
|
const msgId = extractCreatedMessageId(result);
|
|
74
|
-
logger.debug(formatCompact({
|
|
75
|
+
this.#logger.debug(formatCompact({
|
|
75
76
|
op: 'satori_send',
|
|
76
|
-
endpoint: this.#options.config.
|
|
77
|
+
endpoint: this.#options.config.id,
|
|
77
78
|
channel: conversation.id,
|
|
78
79
|
messageId: msgId || undefined,
|
|
79
80
|
}));
|
|
@@ -97,16 +98,16 @@ export class SatoriWsEndpoint {
|
|
|
97
98
|
message: { conversation, id: body.message.id },
|
|
98
99
|
content,
|
|
99
100
|
sender,
|
|
101
|
+
endpointId: this.#options.config.id,
|
|
102
|
+
...(mentioned ? { mentioned: true } : {}),
|
|
100
103
|
metadata: Object.freeze({
|
|
101
104
|
type: body.type,
|
|
102
105
|
channelType: isPrivateChannelType(body) ? 'private' : 'group',
|
|
103
106
|
sn: body.sn,
|
|
104
107
|
platform: this.#login?.platform,
|
|
105
|
-
endpoint: this.#options.config.name,
|
|
106
|
-
...(mentioned ? { mentioned: true } : {}),
|
|
107
108
|
}),
|
|
108
109
|
}).catch((err) => {
|
|
109
|
-
logger.warn(formatCompact({
|
|
110
|
+
this.#logger.warn(formatCompact({
|
|
110
111
|
op: 'satori_gateway_receive_failed',
|
|
111
112
|
channel: conversation.id,
|
|
112
113
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -143,7 +144,7 @@ export class SatoriWsEndpoint {
|
|
|
143
144
|
}
|
|
144
145
|
});
|
|
145
146
|
ws.on('open', () => {
|
|
146
|
-
logger.debug(formatCompact({
|
|
147
|
+
this.#logger.debug(formatCompact({ mode: 'ws' }));
|
|
147
148
|
this.#sendSignal(SatoriOpcode.IDENTIFY, {
|
|
148
149
|
token: config.token,
|
|
149
150
|
sn: this.#lastSn,
|
|
@@ -165,9 +166,9 @@ export class SatoriWsEndpoint {
|
|
|
165
166
|
this.#handleSignal(signal);
|
|
166
167
|
}
|
|
167
168
|
catch (error) {
|
|
168
|
-
logger.warn(formatCompact({
|
|
169
|
+
this.#logger.warn(formatCompact({
|
|
169
170
|
op: 'ws_parse_error',
|
|
170
|
-
endpoint: config.
|
|
171
|
+
endpoint: config.id,
|
|
171
172
|
error: error instanceof Error ? error.message : String(error),
|
|
172
173
|
}));
|
|
173
174
|
}
|
|
@@ -188,9 +189,9 @@ export class SatoriWsEndpoint {
|
|
|
188
189
|
handle.notifyClosed(`Satori WS closed: ${numericCode} ${reasonStr || 'closed'}`);
|
|
189
190
|
});
|
|
190
191
|
ws.on('error', (error) => {
|
|
191
|
-
logger.warn(formatCompact({
|
|
192
|
+
this.#logger.warn(formatCompact({
|
|
192
193
|
op: 'ws_error',
|
|
193
|
-
endpoint: config.
|
|
194
|
+
endpoint: config.id,
|
|
194
195
|
ok: false,
|
|
195
196
|
error: error instanceof Error ? error.message : String(error),
|
|
196
197
|
}));
|
|
@@ -211,7 +212,7 @@ export class SatoriWsEndpoint {
|
|
|
211
212
|
const logins = signal.body.logins;
|
|
212
213
|
this.#login = logins[0];
|
|
213
214
|
if (!this.#login?.platform || !this.#login?.user?.id) {
|
|
214
|
-
logger.warn(formatCompact({ op: 'ready', ok: false, error: 'missing platform/user' }));
|
|
215
|
+
this.#logger.warn(formatCompact({ op: 'ready', ok: false, error: 'missing platform/user' }));
|
|
215
216
|
}
|
|
216
217
|
return;
|
|
217
218
|
}
|
|
@@ -240,6 +241,7 @@ export class SatoriWsEndpoint {
|
|
|
240
241
|
}
|
|
241
242
|
}
|
|
242
243
|
export class SatoriWebhookEndpoint {
|
|
244
|
+
#logger;
|
|
243
245
|
#options;
|
|
244
246
|
#login;
|
|
245
247
|
#routeReleases = [];
|
|
@@ -247,6 +249,7 @@ export class SatoriWebhookEndpoint {
|
|
|
247
249
|
#started = false;
|
|
248
250
|
management = createSatoriEndpointManagement((resource, method, params) => this.#api(resource, method, params));
|
|
249
251
|
constructor(options) {
|
|
252
|
+
this.#logger = getAdapterLogger('satori', options.config.id);
|
|
250
253
|
this.#options = options;
|
|
251
254
|
}
|
|
252
255
|
/** Used by webhook handler. */
|
|
@@ -262,17 +265,17 @@ export class SatoriWebhookEndpoint {
|
|
|
262
265
|
this.#started = true;
|
|
263
266
|
if (!this.#options.config.token) {
|
|
264
267
|
// 未配 token 时 webhook 无鉴权:任何人知道 path 即可注入假事件。
|
|
265
|
-
logger.warn(formatCompact({
|
|
268
|
+
this.#logger.warn(formatCompact({
|
|
266
269
|
op: 'webhook_no_token',
|
|
267
|
-
endpoint: this.#options.config.
|
|
270
|
+
endpoint: this.#options.config.id,
|
|
268
271
|
path: this.#options.config.path,
|
|
269
272
|
hint: 'set token to authenticate Satori webhook callbacks',
|
|
270
273
|
}));
|
|
271
274
|
}
|
|
272
275
|
this.#routeReleases.push(...registerSatoriWebhookRoutes(this.#options.http, this));
|
|
273
|
-
logger.info(formatCompact({
|
|
276
|
+
this.#logger.info(formatCompact({
|
|
274
277
|
op: 'listen',
|
|
275
|
-
endpoint: this.#options.config.
|
|
278
|
+
endpoint: this.#options.config.id,
|
|
276
279
|
mode: 'webhook',
|
|
277
280
|
path: this.#options.config.path,
|
|
278
281
|
}));
|
|
@@ -288,9 +291,8 @@ export class SatoriWebhookEndpoint {
|
|
|
288
291
|
for (const release of this.#routeReleases.splice(0))
|
|
289
292
|
release();
|
|
290
293
|
this.#started = false;
|
|
291
|
-
logger.debug(formatCompact({
|
|
294
|
+
this.#logger.debug(formatCompact({
|
|
292
295
|
op: 'disconnect',
|
|
293
|
-
endpoint: this.#options.config.name,
|
|
294
296
|
}));
|
|
295
297
|
}
|
|
296
298
|
async send({ conversation, payload }) {
|
|
@@ -300,9 +302,9 @@ export class SatoriWebhookEndpoint {
|
|
|
300
302
|
content,
|
|
301
303
|
});
|
|
302
304
|
const msgId = extractCreatedMessageId(result);
|
|
303
|
-
logger.debug(formatCompact({
|
|
305
|
+
this.#logger.debug(formatCompact({
|
|
304
306
|
op: 'satori_send',
|
|
305
|
-
endpoint: this.#options.config.
|
|
307
|
+
endpoint: this.#options.config.id,
|
|
306
308
|
channel: conversation.id,
|
|
307
309
|
messageId: msgId || undefined,
|
|
308
310
|
}));
|
|
@@ -325,16 +327,16 @@ export class SatoriWebhookEndpoint {
|
|
|
325
327
|
message: { conversation, id: body.message.id },
|
|
326
328
|
content,
|
|
327
329
|
sender,
|
|
330
|
+
endpointId: this.#options.config.id,
|
|
331
|
+
...(mentioned ? { mentioned: true } : {}),
|
|
328
332
|
metadata: Object.freeze({
|
|
329
333
|
type: body.type,
|
|
330
334
|
channelType: isPrivateChannelType(body) ? 'private' : 'group',
|
|
331
335
|
sn: body.sn,
|
|
332
336
|
platform: this.#login?.platform,
|
|
333
|
-
endpoint: this.#options.config.name,
|
|
334
|
-
...(mentioned ? { mentioned: true } : {}),
|
|
335
337
|
}),
|
|
336
338
|
}).catch((err) => {
|
|
337
|
-
logger.warn(formatCompact({
|
|
339
|
+
this.#logger.warn(formatCompact({
|
|
338
340
|
op: 'satori_gateway_receive_failed',
|
|
339
341
|
channel: conversation.id,
|
|
340
342
|
error: err instanceof Error ? err.message : String(err),
|
package/lib/protocol.d.ts
CHANGED
|
@@ -78,7 +78,7 @@ export interface SatoriWireSegment {
|
|
|
78
78
|
}
|
|
79
79
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
80
80
|
export interface SatoriAdapterConfig {
|
|
81
|
-
readonly
|
|
81
|
+
readonly id?: string;
|
|
82
82
|
readonly connection?: 'ws' | 'webhook';
|
|
83
83
|
readonly baseUrl?: string;
|
|
84
84
|
readonly token?: string;
|
|
@@ -95,7 +95,7 @@ export interface SatoriAdapterConfig {
|
|
|
95
95
|
export interface ResolvedSatoriWsConfig {
|
|
96
96
|
readonly context: 'satori';
|
|
97
97
|
readonly connection: 'ws';
|
|
98
|
-
readonly
|
|
98
|
+
readonly id: string;
|
|
99
99
|
readonly baseUrl: string;
|
|
100
100
|
readonly token?: string;
|
|
101
101
|
readonly heartbeat_interval: number;
|
|
@@ -103,7 +103,7 @@ export interface ResolvedSatoriWsConfig {
|
|
|
103
103
|
export interface ResolvedSatoriWebhookConfig {
|
|
104
104
|
readonly context: 'satori';
|
|
105
105
|
readonly connection: 'webhook';
|
|
106
|
-
readonly
|
|
106
|
+
readonly id: string;
|
|
107
107
|
readonly baseUrl: string;
|
|
108
108
|
readonly token?: string;
|
|
109
109
|
readonly path: string;
|
|
@@ -129,12 +129,15 @@ export declare function formatInboundContent(body: SatoriEventBody & {
|
|
|
129
129
|
* 入站归一化 → ConversationRef:Channel.type 1 (DIRECT) → kind 'private';
|
|
130
130
|
* 其余频道消息 → kind 'group',所属 guild 容器进 `parent`(kind 'channel')。
|
|
131
131
|
*/
|
|
132
|
-
export declare function satoriInboundConversation(
|
|
132
|
+
export declare function satoriInboundConversation(endpointKey: string, body: SatoriEventBody & {
|
|
133
133
|
message: SatoriMessage;
|
|
134
134
|
}): ConversationRef;
|
|
135
135
|
export declare function resolveInboundSender(body: SatoriEventBody & {
|
|
136
136
|
message: SatoriMessage;
|
|
137
|
-
}):
|
|
137
|
+
}): {
|
|
138
|
+
id: string;
|
|
139
|
+
name?: string;
|
|
140
|
+
};
|
|
138
141
|
/**
|
|
139
142
|
* Detect `<at id="…"/>` elements in message content targeting the bot selfId.
|
|
140
143
|
* selfId 来源:READY/事件携带的 `login.user.id`。
|
package/lib/protocol.js
CHANGED
|
@@ -23,8 +23,8 @@ export function resolveSatoriConfig(config = {}) {
|
|
|
23
23
|
if (!baseUrl) {
|
|
24
24
|
throw new TypeError('Satori adapter requires baseUrl (plugins.<key>.baseUrl or endpoints with context: satori)');
|
|
25
25
|
}
|
|
26
|
-
const
|
|
27
|
-
|| (typeof entry?.
|
|
26
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
27
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
28
28
|
|| process.env.SATORI_BOT_NAME
|
|
29
29
|
|| 'satori-bot';
|
|
30
30
|
const token = (typeof config.token === 'string' && config.token)
|
|
@@ -39,7 +39,7 @@ export function resolveSatoriConfig(config = {}) {
|
|
|
39
39
|
return {
|
|
40
40
|
context: 'satori',
|
|
41
41
|
connection: 'webhook',
|
|
42
|
-
|
|
42
|
+
id,
|
|
43
43
|
baseUrl,
|
|
44
44
|
token,
|
|
45
45
|
path,
|
|
@@ -51,7 +51,7 @@ export function resolveSatoriConfig(config = {}) {
|
|
|
51
51
|
return {
|
|
52
52
|
context: 'satori',
|
|
53
53
|
connection: 'ws',
|
|
54
|
-
|
|
54
|
+
id,
|
|
55
55
|
baseUrl,
|
|
56
56
|
token,
|
|
57
57
|
heartbeat_interval: heartbeat,
|
|
@@ -117,11 +117,11 @@ export function formatInboundContent(body) {
|
|
|
117
117
|
* 入站归一化 → ConversationRef:Channel.type 1 (DIRECT) → kind 'private';
|
|
118
118
|
* 其余频道消息 → kind 'group',所属 guild 容器进 `parent`(kind 'channel')。
|
|
119
119
|
*/
|
|
120
|
-
export function satoriInboundConversation(
|
|
120
|
+
export function satoriInboundConversation(endpointKey, body) {
|
|
121
121
|
const channel = body.channel ?? body.message.channel;
|
|
122
122
|
const kind = isPrivateChannel(channel) ? 'private' : 'group';
|
|
123
123
|
return {
|
|
124
|
-
endpoint: { id:
|
|
124
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
125
125
|
kind,
|
|
126
126
|
id: channel?.id ?? '',
|
|
127
127
|
...(kind === 'group' && body.guild?.id
|
|
@@ -131,7 +131,9 @@ export function satoriInboundConversation(endpointId, body) {
|
|
|
131
131
|
}
|
|
132
132
|
export function resolveInboundSender(body) {
|
|
133
133
|
const user = body.user ?? body.message.user ?? body.message.member?.user;
|
|
134
|
-
|
|
134
|
+
const name = user?.name ?? body.message.member?.nick;
|
|
135
|
+
const id = user?.id ?? name ?? '';
|
|
136
|
+
return name && name !== id ? { id, name } : { id };
|
|
135
137
|
}
|
|
136
138
|
/**
|
|
137
139
|
* Detect `<at id="…"/>` elements in message content targeting the bot selfId.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-satori",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.5",
|
|
4
4
|
"description": "Zhin.js Satori adapter for Plugin Runtime (WebSocket client)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -39,26 +39,26 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"ws": "^8.21.1",
|
|
42
|
-
"@zhin.js/adapter": "1.1.
|
|
43
|
-
"@zhin.js/command": "1.0.
|
|
44
|
-
"@zhin.js/core": "1.5.
|
|
45
|
-
"@zhin.js/host-http": "1.0.
|
|
46
|
-
"@zhin.js/im-contract": "1.0.
|
|
47
|
-
"@zhin.js/logger": "1.0.
|
|
48
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
42
|
+
"@zhin.js/adapter": "1.1.7",
|
|
43
|
+
"@zhin.js/command": "1.0.10",
|
|
44
|
+
"@zhin.js/core": "1.5.5",
|
|
45
|
+
"@zhin.js/host-http": "1.0.8",
|
|
46
|
+
"@zhin.js/im-contract": "1.0.3",
|
|
47
|
+
"@zhin.js/logger": "1.0.76",
|
|
48
|
+
"@zhin.js/plugin-runtime": "1.1.5"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^26.1.2",
|
|
52
52
|
"@types/ws": "^8.18.1",
|
|
53
53
|
"typescript": "^6.0.3",
|
|
54
54
|
"vitest": "^4.1.10",
|
|
55
|
-
"@zhin.js/host-http": "1.0.
|
|
55
|
+
"@zhin.js/host-http": "1.0.8"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@zhin.js/adapter": "1.1.
|
|
59
|
-
"@zhin.js/core": "1.5.
|
|
60
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
61
|
-
"zhin.js": "6.0.
|
|
58
|
+
"@zhin.js/adapter": "1.1.7",
|
|
59
|
+
"@zhin.js/core": "1.5.5",
|
|
60
|
+
"@zhin.js/plugin-runtime": "1.1.5",
|
|
61
|
+
"zhin.js": "6.0.5"
|
|
62
62
|
},
|
|
63
63
|
"peerDependenciesMeta": {
|
|
64
64
|
"zhin.js": {
|
package/schema.json
CHANGED
|
@@ -17,16 +17,48 @@
|
|
|
17
17
|
"default": 10000,
|
|
18
18
|
"description": "WS PING interval in milliseconds"
|
|
19
19
|
},
|
|
20
|
+
"master": {
|
|
21
|
+
"type": [
|
|
22
|
+
"string",
|
|
23
|
+
"number"
|
|
24
|
+
],
|
|
25
|
+
"description": "框架 master(platform user id;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
|
|
26
|
+
},
|
|
27
|
+
"trusted": {
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": {
|
|
30
|
+
"type": [
|
|
31
|
+
"string",
|
|
32
|
+
"number"
|
|
33
|
+
],
|
|
34
|
+
"description": "Trusted platform user id"
|
|
35
|
+
},
|
|
36
|
+
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
|
|
37
|
+
},
|
|
20
38
|
"endpoints": {
|
|
21
39
|
"type": "array",
|
|
22
|
-
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(
|
|
40
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
23
41
|
"items": {
|
|
24
42
|
"type": "object",
|
|
25
43
|
"additionalProperties": true,
|
|
26
44
|
"properties": {
|
|
27
|
-
"
|
|
28
|
-
"type":
|
|
29
|
-
|
|
45
|
+
"master": {
|
|
46
|
+
"type": [
|
|
47
|
+
"string",
|
|
48
|
+
"number"
|
|
49
|
+
],
|
|
50
|
+
"description": "本 endpoint 的框架 master(platform user id);覆盖顶层 master"
|
|
51
|
+
},
|
|
52
|
+
"trusted": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"items": {
|
|
55
|
+
"type": [
|
|
56
|
+
"string",
|
|
57
|
+
"number"
|
|
58
|
+
],
|
|
59
|
+
"description": "Trusted platform user id"
|
|
60
|
+
},
|
|
61
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
30
62
|
},
|
|
31
63
|
"baseUrl": {
|
|
32
64
|
"type": "string",
|
|
@@ -39,10 +71,14 @@
|
|
|
39
71
|
"path": {
|
|
40
72
|
"type": "string",
|
|
41
73
|
"description": "Webhook POST path (connection: webhook)"
|
|
74
|
+
},
|
|
75
|
+
"id": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "Satori bot name"
|
|
42
78
|
}
|
|
43
79
|
},
|
|
44
80
|
"required": [
|
|
45
|
-
"
|
|
81
|
+
"id",
|
|
46
82
|
"baseUrl"
|
|
47
83
|
]
|
|
48
84
|
}
|
package/src/endpoint.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from '@zhin.js/adapter';
|
|
14
14
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
15
15
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
16
|
-
import { formatCompact,
|
|
16
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
17
17
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
18
18
|
import {
|
|
19
19
|
SatoriOpcode,
|
|
@@ -43,8 +43,6 @@ import {
|
|
|
43
43
|
type SatoriWsSocket,
|
|
44
44
|
} from './ws.js';
|
|
45
45
|
|
|
46
|
-
const logger = getLogger('satori');
|
|
47
|
-
|
|
48
46
|
export type SatoriApiCaller = typeof callSatoriApi;
|
|
49
47
|
|
|
50
48
|
export interface SatoriWsEndpointOptions {
|
|
@@ -56,6 +54,8 @@ export interface SatoriWsEndpointOptions {
|
|
|
56
54
|
}
|
|
57
55
|
|
|
58
56
|
export class SatoriWsEndpoint implements EndpointInstance {
|
|
57
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
58
|
+
|
|
59
59
|
readonly #options: SatoriWsEndpointOptions;
|
|
60
60
|
readonly #lifecycle: EndpointLifecycle;
|
|
61
61
|
#ws: SatoriWsSocket | null = null;
|
|
@@ -67,10 +67,11 @@ export class SatoriWsEndpoint implements EndpointInstance {
|
|
|
67
67
|
);
|
|
68
68
|
|
|
69
69
|
constructor(options: SatoriWsEndpointOptions) {
|
|
70
|
+
this.#logger = getAdapterLogger('satori', options.config.id);
|
|
70
71
|
this.#options = options;
|
|
71
72
|
const { config } = options;
|
|
72
73
|
this.#lifecycle = createEndpointLifecycle({
|
|
73
|
-
name: config.
|
|
74
|
+
name: config.id,
|
|
74
75
|
reconnect: {
|
|
75
76
|
initialIntervalMs: 5_000,
|
|
76
77
|
// 固定间隔(multiplier 1、无抖动),对齐旧 5s 固定重连语义
|
|
@@ -125,9 +126,9 @@ export class SatoriWsEndpoint implements EndpointInstance {
|
|
|
125
126
|
content,
|
|
126
127
|
});
|
|
127
128
|
const msgId = extractCreatedMessageId(result);
|
|
128
|
-
logger.debug(formatCompact({
|
|
129
|
+
this.#logger.debug(formatCompact({
|
|
129
130
|
op: 'satori_send',
|
|
130
|
-
endpoint: this.#options.config.
|
|
131
|
+
endpoint: this.#options.config.id,
|
|
131
132
|
channel: conversation.id,
|
|
132
133
|
messageId: msgId || undefined,
|
|
133
134
|
}));
|
|
@@ -149,16 +150,16 @@ export class SatoriWsEndpoint implements EndpointInstance {
|
|
|
149
150
|
message: { conversation, id: body.message.id },
|
|
150
151
|
content,
|
|
151
152
|
sender,
|
|
153
|
+
endpointId: this.#options.config.id,
|
|
154
|
+
...(mentioned ? { mentioned: true } : {}),
|
|
152
155
|
metadata: Object.freeze({
|
|
153
156
|
type: body.type,
|
|
154
157
|
channelType: isPrivateChannelType(body) ? 'private' : 'group',
|
|
155
158
|
sn: body.sn,
|
|
156
159
|
platform: this.#login?.platform,
|
|
157
|
-
endpoint: this.#options.config.name,
|
|
158
|
-
...(mentioned ? { mentioned: true } : {}),
|
|
159
160
|
}),
|
|
160
161
|
}).catch((err) => {
|
|
161
|
-
logger.warn(formatCompact({
|
|
162
|
+
this.#logger.warn(formatCompact({
|
|
162
163
|
op: 'satori_gateway_receive_failed',
|
|
163
164
|
channel: conversation.id,
|
|
164
165
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -198,7 +199,7 @@ export class SatoriWsEndpoint implements EndpointInstance {
|
|
|
198
199
|
});
|
|
199
200
|
|
|
200
201
|
ws.on('open', () => {
|
|
201
|
-
logger.debug(formatCompact({
|
|
202
|
+
this.#logger.debug(formatCompact({ mode: 'ws' }));
|
|
202
203
|
this.#sendSignal(SatoriOpcode.IDENTIFY, {
|
|
203
204
|
token: config.token,
|
|
204
205
|
sn: this.#lastSn,
|
|
@@ -220,9 +221,9 @@ export class SatoriWsEndpoint implements EndpointInstance {
|
|
|
220
221
|
const signal = JSON.parse(raw) as SatoriSignal;
|
|
221
222
|
this.#handleSignal(signal);
|
|
222
223
|
} catch (error) {
|
|
223
|
-
logger.warn(formatCompact({
|
|
224
|
+
this.#logger.warn(formatCompact({
|
|
224
225
|
op: 'ws_parse_error',
|
|
225
|
-
endpoint: config.
|
|
226
|
+
endpoint: config.id,
|
|
226
227
|
error: error instanceof Error ? error.message : String(error),
|
|
227
228
|
}));
|
|
228
229
|
}
|
|
@@ -245,9 +246,9 @@ export class SatoriWsEndpoint implements EndpointInstance {
|
|
|
245
246
|
});
|
|
246
247
|
|
|
247
248
|
ws.on('error', (error) => {
|
|
248
|
-
logger.warn(formatCompact({
|
|
249
|
+
this.#logger.warn(formatCompact({
|
|
249
250
|
op: 'ws_error',
|
|
250
|
-
endpoint: config.
|
|
251
|
+
endpoint: config.id,
|
|
251
252
|
ok: false,
|
|
252
253
|
error: error instanceof Error ? error.message : String(error),
|
|
253
254
|
}));
|
|
@@ -269,7 +270,7 @@ export class SatoriWsEndpoint implements EndpointInstance {
|
|
|
269
270
|
const logins = signal.body.logins as SatoriLogin[];
|
|
270
271
|
this.#login = logins[0];
|
|
271
272
|
if (!this.#login?.platform || !this.#login?.user?.id) {
|
|
272
|
-
logger.warn(formatCompact({ op: 'ready', ok: false, error: 'missing platform/user' }));
|
|
273
|
+
this.#logger.warn(formatCompact({ op: 'ready', ok: false, error: 'missing platform/user' }));
|
|
273
274
|
}
|
|
274
275
|
return;
|
|
275
276
|
}
|
|
@@ -312,6 +313,8 @@ export interface SatoriWebhookEndpointOptions {
|
|
|
312
313
|
}
|
|
313
314
|
|
|
314
315
|
export class SatoriWebhookEndpoint implements EndpointInstance {
|
|
316
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
317
|
+
|
|
315
318
|
readonly #options: SatoriWebhookEndpointOptions;
|
|
316
319
|
#login: SatoriLogin | undefined;
|
|
317
320
|
#routeReleases: HttpRouteRegistration[] = [];
|
|
@@ -322,6 +325,7 @@ export class SatoriWebhookEndpoint implements EndpointInstance {
|
|
|
322
325
|
);
|
|
323
326
|
|
|
324
327
|
constructor(options: SatoriWebhookEndpointOptions) {
|
|
328
|
+
this.#logger = getAdapterLogger('satori', options.config.id);
|
|
325
329
|
this.#options = options;
|
|
326
330
|
}
|
|
327
331
|
|
|
@@ -339,17 +343,17 @@ export class SatoriWebhookEndpoint implements EndpointInstance {
|
|
|
339
343
|
this.#started = true;
|
|
340
344
|
if (!this.#options.config.token) {
|
|
341
345
|
// 未配 token 时 webhook 无鉴权:任何人知道 path 即可注入假事件。
|
|
342
|
-
logger.warn(formatCompact({
|
|
346
|
+
this.#logger.warn(formatCompact({
|
|
343
347
|
op: 'webhook_no_token',
|
|
344
|
-
endpoint: this.#options.config.
|
|
348
|
+
endpoint: this.#options.config.id,
|
|
345
349
|
path: this.#options.config.path,
|
|
346
350
|
hint: 'set token to authenticate Satori webhook callbacks',
|
|
347
351
|
}));
|
|
348
352
|
}
|
|
349
353
|
this.#routeReleases.push(...registerSatoriWebhookRoutes(this.#options.http, this));
|
|
350
|
-
logger.info(formatCompact({
|
|
354
|
+
this.#logger.info(formatCompact({
|
|
351
355
|
op: 'listen',
|
|
352
|
-
endpoint: this.#options.config.
|
|
356
|
+
endpoint: this.#options.config.id,
|
|
353
357
|
mode: 'webhook',
|
|
354
358
|
path: this.#options.config.path,
|
|
355
359
|
}));
|
|
@@ -367,9 +371,8 @@ export class SatoriWebhookEndpoint implements EndpointInstance {
|
|
|
367
371
|
this.#open = false;
|
|
368
372
|
for (const release of this.#routeReleases.splice(0)) release();
|
|
369
373
|
this.#started = false;
|
|
370
|
-
logger.debug(formatCompact({
|
|
371
|
-
|
|
372
|
-
endpoint: this.#options.config.name,
|
|
374
|
+
this.#logger.debug(formatCompact({
|
|
375
|
+
op: 'disconnect',
|
|
373
376
|
}));
|
|
374
377
|
}
|
|
375
378
|
|
|
@@ -380,9 +383,9 @@ export class SatoriWebhookEndpoint implements EndpointInstance {
|
|
|
380
383
|
content,
|
|
381
384
|
});
|
|
382
385
|
const msgId = extractCreatedMessageId(result);
|
|
383
|
-
logger.debug(formatCompact({
|
|
386
|
+
this.#logger.debug(formatCompact({
|
|
384
387
|
op: 'satori_send',
|
|
385
|
-
endpoint: this.#options.config.
|
|
388
|
+
endpoint: this.#options.config.id,
|
|
386
389
|
channel: conversation.id,
|
|
387
390
|
messageId: msgId || undefined,
|
|
388
391
|
}));
|
|
@@ -403,16 +406,16 @@ export class SatoriWebhookEndpoint implements EndpointInstance {
|
|
|
403
406
|
message: { conversation, id: body.message.id },
|
|
404
407
|
content,
|
|
405
408
|
sender,
|
|
409
|
+
endpointId: this.#options.config.id,
|
|
410
|
+
...(mentioned ? { mentioned: true } : {}),
|
|
406
411
|
metadata: Object.freeze({
|
|
407
412
|
type: body.type,
|
|
408
413
|
channelType: isPrivateChannelType(body) ? 'private' : 'group',
|
|
409
414
|
sn: body.sn,
|
|
410
415
|
platform: this.#login?.platform,
|
|
411
|
-
endpoint: this.#options.config.name,
|
|
412
|
-
...(mentioned ? { mentioned: true } : {}),
|
|
413
416
|
}),
|
|
414
417
|
}).catch((err) => {
|
|
415
|
-
logger.warn(formatCompact({
|
|
418
|
+
this.#logger.warn(formatCompact({
|
|
416
419
|
op: 'satori_gateway_receive_failed',
|
|
417
420
|
channel: conversation.id,
|
|
418
421
|
error: err instanceof Error ? err.message : String(err),
|
package/src/protocol.ts
CHANGED
|
@@ -84,7 +84,7 @@ export interface SatoriWireSegment {
|
|
|
84
84
|
|
|
85
85
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
86
86
|
export interface SatoriAdapterConfig {
|
|
87
|
-
readonly
|
|
87
|
+
readonly id?: string;
|
|
88
88
|
readonly connection?: 'ws' | 'webhook';
|
|
89
89
|
readonly baseUrl?: string;
|
|
90
90
|
readonly token?: string;
|
|
@@ -102,7 +102,7 @@ export interface SatoriAdapterConfig {
|
|
|
102
102
|
export interface ResolvedSatoriWsConfig {
|
|
103
103
|
readonly context: 'satori';
|
|
104
104
|
readonly connection: 'ws';
|
|
105
|
-
readonly
|
|
105
|
+
readonly id: string;
|
|
106
106
|
readonly baseUrl: string;
|
|
107
107
|
readonly token?: string;
|
|
108
108
|
readonly heartbeat_interval: number;
|
|
@@ -111,7 +111,7 @@ export interface ResolvedSatoriWsConfig {
|
|
|
111
111
|
export interface ResolvedSatoriWebhookConfig {
|
|
112
112
|
readonly context: 'satori';
|
|
113
113
|
readonly connection: 'webhook';
|
|
114
|
-
readonly
|
|
114
|
+
readonly id: string;
|
|
115
115
|
readonly baseUrl: string;
|
|
116
116
|
readonly token?: string;
|
|
117
117
|
readonly path: string;
|
|
@@ -128,8 +128,8 @@ export function resolveSatoriConfig(config: SatoriAdapterConfig = {}): ResolvedS
|
|
|
128
128
|
'Satori adapter requires baseUrl (plugins.<key>.baseUrl or endpoints with context: satori)',
|
|
129
129
|
);
|
|
130
130
|
}
|
|
131
|
-
const
|
|
132
|
-
|| (typeof entry?.
|
|
131
|
+
const id = (typeof config.id === 'string' && config.id)
|
|
132
|
+
|| (typeof entry?.id === 'string' && entry.id)
|
|
133
133
|
|| process.env.SATORI_BOT_NAME
|
|
134
134
|
|| 'satori-bot';
|
|
135
135
|
const token = (typeof config.token === 'string' && config.token)
|
|
@@ -145,7 +145,7 @@ export function resolveSatoriConfig(config: SatoriAdapterConfig = {}): ResolvedS
|
|
|
145
145
|
return {
|
|
146
146
|
context: 'satori',
|
|
147
147
|
connection: 'webhook',
|
|
148
|
-
|
|
148
|
+
id,
|
|
149
149
|
baseUrl,
|
|
150
150
|
token,
|
|
151
151
|
path,
|
|
@@ -158,7 +158,7 @@ export function resolveSatoriConfig(config: SatoriAdapterConfig = {}): ResolvedS
|
|
|
158
158
|
return {
|
|
159
159
|
context: 'satori',
|
|
160
160
|
connection: 'ws',
|
|
161
|
-
|
|
161
|
+
id,
|
|
162
162
|
baseUrl,
|
|
163
163
|
token,
|
|
164
164
|
heartbeat_interval: heartbeat,
|
|
@@ -234,13 +234,13 @@ export function formatInboundContent(body: SatoriEventBody & { message: SatoriMe
|
|
|
234
234
|
* 其余频道消息 → kind 'group',所属 guild 容器进 `parent`(kind 'channel')。
|
|
235
235
|
*/
|
|
236
236
|
export function satoriInboundConversation(
|
|
237
|
-
|
|
237
|
+
endpointKey: string,
|
|
238
238
|
body: SatoriEventBody & { message: SatoriMessage },
|
|
239
239
|
): ConversationRef {
|
|
240
240
|
const channel = body.channel ?? body.message.channel;
|
|
241
241
|
const kind: ConversationKind = isPrivateChannel(channel) ? 'private' : 'group';
|
|
242
242
|
return {
|
|
243
|
-
endpoint: { id:
|
|
243
|
+
endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
|
|
244
244
|
kind,
|
|
245
245
|
id: channel?.id ?? '',
|
|
246
246
|
...(kind === 'group' && body.guild?.id
|
|
@@ -249,9 +249,13 @@ export function satoriInboundConversation(
|
|
|
249
249
|
};
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
export function resolveInboundSender(
|
|
252
|
+
export function resolveInboundSender(
|
|
253
|
+
body: SatoriEventBody & { message: SatoriMessage },
|
|
254
|
+
): { id: string; name?: string } {
|
|
253
255
|
const user = body.user ?? body.message.user ?? body.message.member?.user;
|
|
254
|
-
|
|
256
|
+
const name = user?.name ?? body.message.member?.nick;
|
|
257
|
+
const id = user?.id ?? name ?? '';
|
|
258
|
+
return name && name !== id ? { id, name } : { id };
|
|
255
259
|
}
|
|
256
260
|
|
|
257
261
|
/**
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|