@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 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
@@ -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.name, {
24
- name: config.name,
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') {
@@ -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.name, {
35
- name: config.name,
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, getLogger } from '@zhin.js/logger';
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.name,
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.name,
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({ endpoint: config.name, mode: 'ws' }));
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.name,
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.name,
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.name,
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.name,
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.name,
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 name?: string;
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 name: string;
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 name: string;
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(endpointId: string, body: SatoriEventBody & {
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
- }): string;
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 name = (typeof config.name === 'string' && config.name)
27
- || (typeof entry?.name === 'string' && entry.name)
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
- name,
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
- name,
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(endpointId, body) {
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: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
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
- return user?.name ?? body.message.member?.nick ?? user?.id ?? '';
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.2",
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.5",
43
- "@zhin.js/command": "1.0.7",
44
- "@zhin.js/core": "1.5.2",
45
- "@zhin.js/host-http": "1.0.6",
46
- "@zhin.js/im-contract": "1.0.1",
47
- "@zhin.js/logger": "1.0.75",
48
- "@zhin.js/plugin-runtime": "1.1.3"
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.6"
55
+ "@zhin.js/host-http": "1.0.8"
56
56
  },
57
57
  "peerDependencies": {
58
- "@zhin.js/adapter": "1.1.5",
59
- "@zhin.js/core": "1.5.2",
60
- "@zhin.js/plugin-runtime": "1.1.3",
61
- "zhin.js": "6.0.2"
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。每项与顶层字段同构(name 必填,其余覆盖顶层)",
40
+ "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
23
41
  "items": {
24
42
  "type": "object",
25
43
  "additionalProperties": true,
26
44
  "properties": {
27
- "name": {
28
- "type": "string",
29
- "description": "Satori bot name"
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
- "name",
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, getLogger } from '@zhin.js/logger';
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.name,
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.name,
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({ endpoint: config.name, mode: 'ws' }));
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.name,
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.name,
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.name,
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.name,
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
- op: 'disconnect',
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.name,
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 name?: string;
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 name: string;
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 name: string;
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 name = (typeof config.name === 'string' && config.name)
132
- || (typeof entry?.name === 'string' && entry.name)
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
- name,
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
- name,
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
- endpointId: string,
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: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
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(body: SatoriEventBody & { message: SatoriMessage }): string {
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
- return user?.name ?? body.message.member?.nick ?? user?.id ?? '';
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