@zhin.js/adapter-onebot11 1.1.20 → 1.1.22

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,72 @@
1
1
  # @zhin.js/adapter-onebot11
2
2
 
3
+ ## 1.1.22
4
+
5
+ ### Patch Changes
6
+
7
+ - b249e5e: ADR 0044 C4:事件映射迁出 core。新增 `@zhin.js/adapter-onebot-events`,承载 OneBot v11 / NapCat / icqq 的原始通知与请求 → 标准事件映射(`receiveOneBotLikeSideEvent`、`mapNoticeParts`、`mapRequestParts`、`resolveSideEventDedupeKey` 及 OneBot 映射表,icqq 差异用覆盖表表达);KOOK、Slack 的映射迁入各自适配器;`@zhin.js/core` 只保留标准事件名、类型与 `buildNotice` / `buildRequest` / `buildSystem` / `senderFromId`,不再含任何平台名。core 不再导出上述映射函数与 `ONEBOT_/KOOK_/SLACK_NOTICE_PARTS_MAP`、`SideEventPlatform`、`OneBotLike*` 类型,适配器改从共享包导入。
8
+ - 41ff1a5: ADR 0044 C5: every official adapter now declares a capability `profile` (its `segments` moved into `profile.default.messaging.segments`). icqq declares the complete profile (per conversation kind: scene, all 14 actions with real limits, events, feedback, delivery) and implements the seven new management ports (`pokeUser`, `unmuteGroupMember`, `muteAllGroupMembers`, `setGroupCard`, `setGroupTitle`, `setGroupEssence`, `inviteGroupMember`) with fresh bot-role reads. qq official declares its passive reply windows; undeclared capabilities stay unsupported.
9
+ - cbd3f14: ADR 0044 integration and C6 guard. `delivery.spool` now reproduces the pre-0044 media rule (icqq `false`, every other official adapter `true`). icqq narrows `actions.recall.others` unless the Bot is admin or owner, and declares recall for groups. Endpoint kind capabilities merge the Endpoint's `operations` (Discord reactions keep working; Discord declares reaction feedback). Adapters in the `@zhin.js/` scope are trusted: their custom prompt text is not labelled `external_content`. Core no longer keeps a telegram/discord interactive-policy table (adapters declare `segments.interactive`). New `pnpm check:no-platform-branches` blocks platform-name branches in runtime code.
10
+ - 4fe49b5: 支持配置按需生效:分离保存与有效版本,独立变更先应用,账号资料更新保留连接,模型按回合使用确定版本;Console 返回结构化应用结果。监听改为仅显式 --watch 启用,开发模板显式选择源码和配置监听。
11
+
12
+ 自变更消费配置应用回执,避免已处理配置被二次重载;持久化本次写入修订和应用结果,未知结果或修订冲突时停止自动续作并保留现场,恢复不会覆盖未经本次变更确认的配置。
13
+
14
+ - Updated dependencies [b249e5e]
15
+ - Updated dependencies [57661f6]
16
+ - Updated dependencies [cbd3f14]
17
+ - Updated dependencies [299863a]
18
+ - Updated dependencies [aaa93d7]
19
+ - Updated dependencies [bd524d1]
20
+ - Updated dependencies [126ae99]
21
+ - Updated dependencies [4fe49b5]
22
+ - Updated dependencies [cce07db]
23
+ - Updated dependencies [b652db3]
24
+ - Updated dependencies [0315ba0]
25
+ - Updated dependencies [00be764]
26
+ - @zhin.js/adapter-onebot-events@0.0.1
27
+ - @zhin.js/core@1.1.56
28
+ - @zhin.js/adapter@1.1.28
29
+ - @zhin.js/im-contract@1.1.11
30
+ - @zhin.js/tool@1.1.15
31
+ - @zhin.js/skill@1.1.13
32
+ - @zhin.js/host-http@1.1.14
33
+ - @zhin.js/feature-kit@1.1.12
34
+ - @zhin.js/client@1.1.24
35
+ - @zhin.js/page@1.1.14
36
+ - @zhin.js/agent-feature@1.1.13
37
+ - @zhin.js/permission@1.1.12
38
+
39
+ ## 1.1.21
40
+
41
+ ### Patch Changes
42
+
43
+ - b88e988: Preserve account-profile write acknowledgements already observed before cancellation, distinguish native Discord REST rejection from unknown outcomes, and retain the last valid profile when a newer concurrent read fails. Keep scene-scoped SOUL presence consistent with the actual prompt and allow discovered URLs to pass through the existing network authorization policy.
44
+ - af8f803: Use the current Endpoint's platform profile for Agent account identity, with per-field configured fallbacks and scoped, identity-approved profile tools. Keep SOUL first-person and independent of account names. Profile ports preserve the source Client, cancellation, truthful update outcomes and private image authority.
45
+
46
+ Adapter Feature API 2.1 and Tool Feature API 1.1 describe the additive profile requirements; new Core, official adapters and generated plugins declare the corresponding floors. Existing compatible API-2.0/API-1.0 plugins remain accepted by the newer providers. npm releases remain patch.
47
+
48
+ Private IM assets now bind to the full canonical conversation and principal. Existing IM asset records that contain only the earlier lossy hash remain on disk until their original expiry, but cannot be safely rebound; recapture them. No automatic file deletion, platform profile mutation or workspace SOUL rewrite occurs during upgrade.
49
+
50
+ - Updated dependencies [b88e988]
51
+ - Updated dependencies [f78eb84]
52
+ - Updated dependencies [c15cef9]
53
+ - Updated dependencies [b0c02a2]
54
+ - Updated dependencies [af8f803]
55
+ - Updated dependencies [26fda97]
56
+ - Updated dependencies [976886c]
57
+ - Updated dependencies [032dc96]
58
+ - @zhin.js/adapter@1.1.27
59
+ - @zhin.js/client@1.1.23
60
+ - @zhin.js/im-contract@1.1.10
61
+ - @zhin.js/core@1.1.55
62
+ - @zhin.js/tool@1.1.14
63
+ - @zhin.js/page@1.1.13
64
+ - @zhin.js/host-http@1.1.13
65
+ - @zhin.js/agent-feature@1.1.12
66
+ - @zhin.js/feature-kit@1.1.11
67
+ - @zhin.js/permission@1.1.11
68
+ - @zhin.js/skill@1.1.12
69
+
3
70
  ## 1.1.20
4
71
 
5
72
  ### Patch Changes
@@ -9,11 +9,23 @@ export { OneBot11WsEndpoint } from "../../lib/ws-endpoint.js";
9
9
  export { OneBot11WssEndpoint } from "../../lib/wss-endpoint.js";
10
10
  export default defineAdapter({
11
11
  capabilities: ['inbound', 'outbound'],
12
- operations: ['recall'],
13
12
  // OneBot file 参数原生消费 url / base64:// 媒体;无卡片交互面,交互段降级纯文本。
14
- segments: {
15
- outboundMedia: ['url', 'base64'],
16
- interactive: 'text',
13
+ profile: {
14
+ default: {
15
+ messaging: {
16
+ segments: {
17
+ outboundMedia: ['url', 'base64'],
18
+ interactive: 'text',
19
+ },
20
+ quote: true,
21
+ recall: {},
22
+ },
23
+ actions: {
24
+ recall: { scopes: ['private', 'group'] },
25
+ quoteReply: { scopes: ['private', 'group'] },
26
+ },
27
+ delivery: { send: true, proactive: true, spool: true },
28
+ },
17
29
  },
18
30
  createAdapter: context => new Onebot11Adapter(context),
19
31
  });
@@ -15,11 +15,23 @@ export type { OneBot11WsSocket, OneBot11WsCreateOptions } from '../../src/ws-typ
15
15
 
16
16
  export default defineAdapter<OneBot11EndpointConfig>({
17
17
  capabilities: ['inbound', 'outbound'],
18
- operations: ['recall'],
19
18
  // OneBot file 参数原生消费 url / base64:// 媒体;无卡片交互面,交互段降级纯文本。
20
- segments: {
21
- outboundMedia: ['url', 'base64'],
22
- interactive: 'text',
19
+ profile: {
20
+ default: {
21
+ messaging: {
22
+ segments: {
23
+ outboundMedia: ['url', 'base64'],
24
+ interactive: 'text',
25
+ },
26
+ quote: true,
27
+ recall: {},
28
+ },
29
+ actions: {
30
+ recall: { scopes: ['private', 'group'] },
31
+ quoteReply: { scopes: ['private', 'group'] },
32
+ },
33
+ delivery: { send: true, proactive: true, spool: true },
34
+ },
23
35
  },
24
36
  createAdapter: context => new Onebot11Adapter(context),
25
37
  });
package/lib/protocol.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { EndpointProfileConfiguration } from 'zhin.js/adapter';
1
2
  /**
2
3
  * OneBot 11 protocol helpers (no legacy Adapter/Endpoint / segment-mapper).
3
4
  * Canonicalization is owned by gateway/core before endpoint.send.
@@ -5,7 +6,7 @@
5
6
  */
6
7
  import { type MediaRef, type ConversationRef } from '@zhin.js/im-contract';
7
8
  /** One endpoint config after AdapterIndex expands `plugins.<instanceKey>.endpoints`. */
8
- export interface OneBot11EndpointConfig {
9
+ export interface OneBot11EndpointConfig extends EndpointProfileConfiguration {
9
10
  readonly connection?: 'ws' | 'wss';
10
11
  readonly id: string;
11
12
  readonly access_token?: string;
@@ -0,0 +1,12 @@
1
+ import type { EndpointProfile, PendingRpcRequest } from 'zhin.js/adapter';
2
+ interface Onebot11SelfProfileOptions {
3
+ readonly call: (request: PendingRpcRequest) => Promise<unknown>;
4
+ readonly reportAccount: (id: string | undefined) => void;
5
+ }
6
+ interface Onebot11SelfProfile extends EndpointProfile {
7
+ /** The same verified account observations used by transport ingress and shutdown. */
8
+ reportAccount(id: string | undefined): void;
9
+ }
10
+ /** OneBot11's standard self-information action has no standard profile mutation counterpart. */
11
+ export declare function createOnebot11SelfProfile({ call, reportAccount }: Onebot11SelfProfileOptions): Onebot11SelfProfile;
12
+ export {};
@@ -0,0 +1,56 @@
1
+ /** OneBot11's standard self-information action has no standard profile mutation counterpart. */
2
+ export function createOnebot11SelfProfile({ call, reportAccount }) {
3
+ let nickname;
4
+ let accountId;
5
+ let accountRevision = 0;
6
+ let readSequence = 0;
7
+ let publishedSequence = 0;
8
+ return Object.freeze({
9
+ writableFields: Object.freeze([]),
10
+ get current() { return nickname === undefined ? {} : { nickname }; },
11
+ reportAccount(id) {
12
+ if (id !== accountId || id === undefined) {
13
+ accountId = id;
14
+ nickname = undefined;
15
+ accountRevision++;
16
+ readSequence++;
17
+ publishedSequence = readSequence;
18
+ }
19
+ reportAccount(id);
20
+ },
21
+ async read({ signal }) {
22
+ const revision = accountRevision;
23
+ const sequence = ++readSequence;
24
+ const assertAccount = () => {
25
+ signal.throwIfAborted();
26
+ if (revision !== accountRevision)
27
+ throw new Error('OneBot11 account changed while reading self profile');
28
+ };
29
+ const self = await call({ action: 'get_login_info', signal });
30
+ assertAccount();
31
+ if (!self?.user_id || typeof self.nickname !== 'string')
32
+ throw new Error('OneBot11 returned an incomplete self profile');
33
+ if (sequence > publishedSequence) {
34
+ publishedSequence = sequence;
35
+ accountId = String(self.user_id);
36
+ nickname = self.nickname;
37
+ reportAccount(accountId);
38
+ }
39
+ // Keep confirmed login facts in current, while lookup errors remain lookup errors.
40
+ const details = await call({ action: 'get_stranger_info', params: { user_id: self.user_id, no_cache: true }, signal });
41
+ assertAccount();
42
+ if (!details || String(details.user_id) !== String(self.user_id))
43
+ throw new Error('OneBot11 returned another account profile');
44
+ const observedNickname = typeof details.nickname === 'string' ? details.nickname : self.nickname;
45
+ if (sequence >= publishedSequence) {
46
+ publishedSequence = sequence;
47
+ nickname = observedNickname;
48
+ }
49
+ const profile = { nickname: observedNickname,
50
+ ...(details.sex === 'male' || details.sex === 'female' || details.sex === 'unknown' ? { gender: details.sex } : {}),
51
+ ...(typeof details.age === 'number' && Number.isFinite(details.age) ? { age: details.age } : {}),
52
+ };
53
+ return profile;
54
+ },
55
+ });
56
+ }
@@ -1,4 +1,4 @@
1
- import { receiveOneBotLikeSideEvent } from '@zhin.js/core';
1
+ import { receiveOneBotLikeSideEvent } from '@zhin.js/adapter-onebot-events';
2
2
  import { formatCompact } from '@zhin.js/logger';
3
3
  export function receiveOneBot11SideEvent(raw, context) {
4
4
  const { emit, endpointKey, caller, logger } = context;
@@ -1,3 +1,4 @@
1
+ import { createOnebot11SelfProfile } from './self-profile.js';
1
2
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
3
  /**
3
4
  * OneBot11 WS client endpoint — outbound connect to OneBot implementation.
@@ -25,6 +26,7 @@ export class OneBot11WsEndpoint extends Onebot11Client {
25
26
  #ws;
26
27
  #stopped = false;
27
28
  #rpc;
29
+ #profile;
28
30
  constructor(options) {
29
31
  const rpc = createOneBot11Rpc({ endpointId: options.config.id, signal: options.signal,
30
32
  socket: () => this.#lifecycle.state === 'open' ? this.#ws : undefined,
@@ -36,6 +38,7 @@ export class OneBot11WsEndpoint extends Onebot11Client {
36
38
  const callApi = (request) => this.#rpc.call(request).then(readOnebot11Response);
37
39
  this.#management = createOneBot11EndpointManagement({ callApi });
38
40
  this.#content = createOneBot11ContentPort(callApi);
41
+ this.#profile = createOnebot11SelfProfile({ call: callApi, reportAccount: id => this[endpointSpec].reportSelfUserId(id) });
39
42
  const readTransportState = () => this.#lifecycle.state;
40
43
  this[endpointSpec] = createEndpointSpecification(this, {
41
44
  name: options.config.id,
@@ -44,6 +47,7 @@ export class OneBot11WsEndpoint extends Onebot11Client {
44
47
  send: request => this.#send(request),
45
48
  get transportState() { return readTransportState(); },
46
49
  management: this.#management, control: this.#control, content: this.#content,
50
+ profile: this.#profile,
47
51
  });
48
52
  this.#releaseClientEvents = forwardOnebot11ClientEvents(this, (name, payload) => {
49
53
  void this[endpointSpec].events.run(() => {
@@ -106,7 +110,7 @@ export class OneBot11WsEndpoint extends Onebot11Client {
106
110
  this.#rpc.dispose();
107
111
  this.#releaseClientEvents();
108
112
  this.#ws = undefined;
109
- this[endpointSpec].reportSelfUserId(undefined);
113
+ this.#profile.reportAccount(undefined);
110
114
  }
111
115
  }
112
116
  async #send({ conversation, payload, signal }) {
@@ -134,7 +138,7 @@ export class OneBot11WsEndpoint extends Onebot11Client {
134
138
  #admitRaw(ev) {
135
139
  const selfId = resolveOnebot11SelfId(ev.self_id);
136
140
  if (!this.#stopped && selfId)
137
- this[endpointSpec].reportSelfUserId(selfId);
141
+ this.#profile.reportAccount(selfId);
138
142
  if (!isMessageEvent(ev)) {
139
143
  receiveOneBot11SideEvent(ev, {
140
144
  emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
@@ -181,7 +185,7 @@ export class OneBot11WsEndpoint extends Onebot11Client {
181
185
  if (this.#stopped || this.#transportState !== 'open')
182
186
  return;
183
187
  if (selfId)
184
- this[endpointSpec].reportSelfUserId(selfId);
188
+ this.#profile.reportAccount(selfId);
185
189
  }
186
190
  #warnPlatformEvent(error) {
187
191
  this.#logger.warn(formatCompact({
@@ -1,3 +1,4 @@
1
+ import { createOnebot11SelfProfile } from './self-profile.js';
1
2
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
3
  /**
3
4
  * OneBot11 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
@@ -26,6 +27,7 @@ export class OneBot11WssEndpoint extends Onebot11Client {
26
27
  #connectionLifecycle;
27
28
  #connectionTask = Promise.resolve();
28
29
  #rpc;
30
+ #profile;
29
31
  #started = false;
30
32
  #stopped = false;
31
33
  constructor(options) {
@@ -44,6 +46,7 @@ export class OneBot11WssEndpoint extends Onebot11Client {
44
46
  const callApi = (request) => this.#rpc.call(request).then(readOnebot11Response);
45
47
  this.#management = createOneBot11EndpointManagement({ callApi });
46
48
  this.#content = createOneBot11ContentPort(callApi);
49
+ this.#profile = createOnebot11SelfProfile({ call: callApi, reportAccount: id => this[endpointSpec].reportSelfUserId(id) });
47
50
  const readTransportState = () => this.#transportState;
48
51
  this[endpointSpec] = createEndpointSpecification(this, {
49
52
  name: options.config.id,
@@ -52,6 +55,7 @@ export class OneBot11WssEndpoint extends Onebot11Client {
52
55
  send: request => this.#send(request),
53
56
  get transportState() { return readTransportState(); },
54
57
  management: this.#management, control: this.#control, content: this.#content,
58
+ profile: this.#profile,
55
59
  });
56
60
  this.#releaseClientEvents = forwardOnebot11ClientEvents(this, (name, payload) => {
57
61
  void this[endpointSpec].events.run(() => {
@@ -121,7 +125,7 @@ export class OneBot11WssEndpoint extends Onebot11Client {
121
125
  this.#releaseClientEvents();
122
126
  this.#ws = undefined;
123
127
  this.#started = false;
124
- this[endpointSpec].reportSelfUserId(undefined);
128
+ this.#profile.reportAccount(undefined);
125
129
  }
126
130
  }
127
131
  async #send({ conversation, payload, signal }) {
@@ -142,7 +146,7 @@ export class OneBot11WssEndpoint extends Onebot11Client {
142
146
  #admitRaw(ev) {
143
147
  const selfId = resolveOnebot11SelfId(ev.self_id);
144
148
  if (!this.#stopped && selfId)
145
- this[endpointSpec].reportSelfUserId(selfId);
149
+ this.#profile.reportAccount(selfId);
146
150
  if (!isMessageEvent(ev)) {
147
151
  receiveOneBot11SideEvent(ev, {
148
152
  emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
@@ -188,7 +192,7 @@ export class OneBot11WssEndpoint extends Onebot11Client {
188
192
  if (this.#stopped || this.#transportState !== 'open')
189
193
  return;
190
194
  if (selfId)
191
- this[endpointSpec].reportSelfUserId(selfId);
195
+ this.#profile.reportAccount(selfId);
192
196
  }
193
197
  #warnPlatformEvent(error) {
194
198
  this.#logger.warn(formatCompact({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-onebot11",
3
- "version": "1.1.20",
3
+ "version": "1.1.22",
4
4
  "description": "Zhin.js OneBot 11 adapter for Plugin Runtime (WebSocket client)",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -45,19 +45,20 @@
45
45
  "@imhelper/onebot-v11": "1.0.7",
46
46
  "imhelper": "1.0.7",
47
47
  "ws": "^8.21.1",
48
- "@zhin.js/adapter": "1.1.26",
49
- "@zhin.js/agent-feature": "1.1.11",
50
- "@zhin.js/client": "1.1.22",
48
+ "@zhin.js/adapter-onebot-events": "0.0.1",
49
+ "@zhin.js/adapter": "1.1.28",
50
+ "@zhin.js/agent-feature": "1.1.13",
51
+ "@zhin.js/client": "1.1.24",
51
52
  "@zhin.js/console-contract": "1.1.3",
52
- "@zhin.js/core": "1.1.54",
53
- "@zhin.js/feature-kit": "1.1.10",
54
- "@zhin.js/host-http": "1.1.12",
55
- "@zhin.js/im-contract": "1.1.9",
53
+ "@zhin.js/core": "1.1.56",
54
+ "@zhin.js/feature-kit": "1.1.12",
55
+ "@zhin.js/host-http": "1.1.14",
56
+ "@zhin.js/im-contract": "1.1.11",
56
57
  "@zhin.js/logger": "1.1.2",
57
- "@zhin.js/page": "1.1.12",
58
- "@zhin.js/permission": "1.1.10",
59
- "@zhin.js/skill": "1.1.11",
60
- "@zhin.js/tool": "1.1.13"
58
+ "@zhin.js/page": "1.1.14",
59
+ "@zhin.js/permission": "1.1.12",
60
+ "@zhin.js/skill": "1.1.13",
61
+ "@zhin.js/tool": "1.1.15"
61
62
  },
62
63
  "devDependencies": {
63
64
  "@types/node": "^26.1.2",
@@ -65,15 +66,15 @@
65
66
  "typescript": "^6.0.3",
66
67
  "vitest": "^4.1.10",
67
68
  "zod": "^4.4.3",
68
- "@zhin.js/host-http": "1.1.12",
69
- "zhin.js": "1.1.19"
69
+ "@zhin.js/host-http": "1.1.14",
70
+ "zhin.js": "1.1.21"
70
71
  },
71
72
  "peerDependencies": {
72
73
  "zod": "^4.0.0",
73
- "@zhin.js/adapter": "^1.1.26",
74
- "@zhin.js/command": "^1.1.12",
75
- "@zhin.js/core": "^1.1.54",
76
- "zhin.js": "^1.1.19"
74
+ "@zhin.js/adapter": "^1.1.28",
75
+ "@zhin.js/command": "^1.1.14",
76
+ "@zhin.js/core": "^1.1.56",
77
+ "zhin.js": "^1.1.21"
77
78
  },
78
79
  "peerDependenciesMeta": {
79
80
  "@zhin.js/command": {
@@ -107,7 +108,7 @@
107
108
  "features": [
108
109
  {
109
110
  "package": "@zhin.js/adapter",
110
- "api": "^2.0.0"
111
+ "api": "^2.1.0"
111
112
  },
112
113
  {
113
114
  "package": "@zhin.js/command",
@@ -115,7 +116,7 @@
115
116
  },
116
117
  {
117
118
  "package": "@zhin.js/tool",
118
- "api": "^1.0.0"
119
+ "api": "^1.1.0"
119
120
  },
120
121
  {
121
122
  "package": "@zhin.js/skill",
package/plugin.js CHANGED
@@ -4,6 +4,7 @@ import { permissionHostToken, createSceneRolePlatformChecker } from '@zhin.js/pe
4
4
  import { createOnebot11ConsoleRpc } from "./lib/console.js";
5
5
  export default definePlugin({
6
6
  name: 'onebot11',
7
+ configUpdate: { featurePaths: [['nickname'], ['profile'], ['endpoints']] },
7
8
  metadata: {
8
9
  displayName: 'OneBot 11 Adapter',
9
10
  },
package/schema.json CHANGED
@@ -115,6 +115,27 @@
115
115
  "id": {
116
116
  "type": "string",
117
117
  "description": "OneBot11 bot name"
118
+ },
119
+ "nickname": {
120
+ "type": [
121
+ "string",
122
+ "null"
123
+ ],
124
+ "description": "账号昵称兜底",
125
+ "x-i18n": {
126
+ "en": {
127
+ "description": "Fallback account nickname"
128
+ }
129
+ }
130
+ },
131
+ "profile": {
132
+ "$ref": "#/$defs/selfProfileFallback",
133
+ "description": "账号资料兜底",
134
+ "x-i18n": {
135
+ "en": {
136
+ "description": "Fallback account profile"
137
+ }
138
+ }
118
139
  }
119
140
  },
120
141
  "required": [
@@ -136,9 +157,93 @@
136
157
  "description": "Command prefix. The default empty string allows unprefixed commands; '/' requires a leading slash. Each endpoints[i] can override it."
137
158
  }
138
159
  }
160
+ },
161
+ "nickname": {
162
+ "type": [
163
+ "string",
164
+ "null"
165
+ ],
166
+ "description": "账号昵称兜底",
167
+ "x-i18n": {
168
+ "en": {
169
+ "description": "Fallback account nickname"
170
+ }
171
+ }
172
+ },
173
+ "profile": {
174
+ "$ref": "#/$defs/selfProfileFallback",
175
+ "description": "账号资料兜底",
176
+ "x-i18n": {
177
+ "en": {
178
+ "description": "Fallback account profile"
179
+ }
180
+ }
139
181
  }
140
182
  },
141
183
  "required": [
142
184
  "endpoints"
143
- ]
185
+ ],
186
+ "$defs": {
187
+ "selfProfileFallback": {
188
+ "type": "object",
189
+ "additionalProperties": false,
190
+ "properties": {
191
+ "gender": {
192
+ "type": [
193
+ "string",
194
+ "null"
195
+ ],
196
+ "enum": [
197
+ "male",
198
+ "female",
199
+ "other",
200
+ "unknown",
201
+ null
202
+ ]
203
+ },
204
+ "age": {
205
+ "type": [
206
+ "integer",
207
+ "null"
208
+ ],
209
+ "minimum": 0
210
+ },
211
+ "birthday": {
212
+ "type": [
213
+ "string",
214
+ "null"
215
+ ],
216
+ "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
217
+ },
218
+ "signature": {
219
+ "type": [
220
+ "string",
221
+ "null"
222
+ ]
223
+ },
224
+ "avatar": {
225
+ "type": [
226
+ "string",
227
+ "null"
228
+ ],
229
+ "pattern": "^(https?://[^\\s/?#]+(?:[/?#].*)?|)$"
230
+ },
231
+ "interests": {
232
+ "type": [
233
+ "array",
234
+ "null"
235
+ ],
236
+ "items": {
237
+ "type": "string"
238
+ }
239
+ },
240
+ "bio": {
241
+ "type": [
242
+ "string",
243
+ "null"
244
+ ]
245
+ }
246
+ }
247
+ }
248
+ }
144
249
  }
package/src/protocol.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { EndpointProfileConfiguration } from 'zhin.js/adapter';
1
2
  /**
2
3
  * OneBot 11 protocol helpers (no legacy Adapter/Endpoint / segment-mapper).
3
4
  * Canonicalization is owned by gateway/core before endpoint.send.
@@ -9,7 +10,7 @@ import { formatCompact, getLogger } from '@zhin.js/logger';
9
10
  const logger = getLogger('onebot11');
10
11
 
11
12
  /** One endpoint config after AdapterIndex expands `plugins.<instanceKey>.endpoints`. */
12
- export interface OneBot11EndpointConfig {
13
+ export interface OneBot11EndpointConfig extends EndpointProfileConfiguration {
13
14
  readonly connection?: 'ws' | 'wss';
14
15
  readonly id: string;
15
16
  readonly access_token?: string;
@@ -0,0 +1,67 @@
1
+ import type { EndpointProfile, PendingRpcRequest } from 'zhin.js/adapter';
2
+ import type { SelfProfile } from '@zhin.js/im-contract';
3
+
4
+ interface Onebot11SelfProfileOptions {
5
+ readonly call: (request: PendingRpcRequest) => Promise<unknown>;
6
+ readonly reportAccount: (id: string | undefined) => void;
7
+ }
8
+ interface Onebot11SelfProfile extends EndpointProfile {
9
+ /** The same verified account observations used by transport ingress and shutdown. */
10
+ reportAccount(id: string | undefined): void;
11
+ }
12
+
13
+ /** OneBot11's standard self-information action has no standard profile mutation counterpart. */
14
+ export function createOnebot11SelfProfile({ call, reportAccount }: Onebot11SelfProfileOptions): Onebot11SelfProfile {
15
+ let nickname: string | undefined;
16
+ let accountId: string | undefined;
17
+ let accountRevision = 0;
18
+ let readSequence = 0;
19
+ let publishedSequence = 0;
20
+ return Object.freeze<Onebot11SelfProfile>({
21
+ writableFields: Object.freeze([]),
22
+ get current() { return nickname === undefined ? {} : { nickname }; },
23
+ reportAccount(id) {
24
+ if (id !== accountId || id === undefined) {
25
+ accountId = id;
26
+ nickname = undefined;
27
+ accountRevision++;
28
+ readSequence++;
29
+ publishedSequence = readSequence;
30
+ }
31
+ reportAccount(id);
32
+ },
33
+ async read({ signal }) {
34
+ const revision = accountRevision;
35
+ const sequence = ++readSequence;
36
+ const assertAccount = () => {
37
+ signal.throwIfAborted();
38
+ if (revision !== accountRevision) throw new Error('OneBot11 account changed while reading self profile');
39
+ };
40
+ const self = await call({ action: 'get_login_info', signal }) as { user_id?: number | string; nickname?: string } | undefined;
41
+ assertAccount();
42
+ if (!self?.user_id || typeof self.nickname !== 'string') throw new Error('OneBot11 returned an incomplete self profile');
43
+ if (sequence > publishedSequence) {
44
+ publishedSequence = sequence;
45
+ accountId = String(self.user_id);
46
+ nickname = self.nickname;
47
+ reportAccount(accountId);
48
+ }
49
+ // Keep confirmed login facts in current, while lookup errors remain lookup errors.
50
+ const details = await call({ action: 'get_stranger_info', params: { user_id: self.user_id, no_cache: true }, signal }) as {
51
+ user_id?: number | string; nickname?: string; sex?: string; age?: number;
52
+ } | undefined;
53
+ assertAccount();
54
+ if (!details || String(details.user_id) !== String(self.user_id)) throw new Error('OneBot11 returned another account profile');
55
+ const observedNickname = typeof details.nickname === 'string' ? details.nickname : self.nickname;
56
+ if (sequence >= publishedSequence) {
57
+ publishedSequence = sequence;
58
+ nickname = observedNickname;
59
+ }
60
+ const profile: SelfProfile = { nickname: observedNickname,
61
+ ...(details.sex === 'male' || details.sex === 'female' || details.sex === 'unknown' ? { gender: details.sex } : {}),
62
+ ...(typeof details.age === 'number' && Number.isFinite(details.age) ? { age: details.age } : {}),
63
+ };
64
+ return profile;
65
+ },
66
+ });
67
+ }
@@ -1,4 +1,4 @@
1
- import { receiveOneBotLikeSideEvent } from '@zhin.js/core';
1
+ import { receiveOneBotLikeSideEvent } from '@zhin.js/adapter-onebot-events';
2
2
  import type { EndpointEventEmitter } from 'zhin.js/adapter';
3
3
  import { formatCompact, type getAdapterLogger } from '@zhin.js/logger';
4
4
  import type { OneBot11Event } from './protocol.js';
@@ -1,3 +1,4 @@
1
+ import { createOnebot11SelfProfile } from './self-profile.js';
1
2
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
3
  /**
3
4
  * OneBot11 WS client endpoint — outbound connect to OneBot implementation.
@@ -68,6 +69,7 @@ export class OneBot11WsEndpoint extends Onebot11Client implements Endpoint {
68
69
  #ws?: OneBot11WsSocket;
69
70
  #stopped = false;
70
71
  readonly #rpc: OneBot11Rpc;
72
+ readonly #profile: ReturnType<typeof createOnebot11SelfProfile>;
71
73
 
72
74
  constructor(options: OneBot11WsEndpointOptions) {
73
75
  const rpc = createOneBot11Rpc({ endpointId: options.config.id, signal: options.signal,
@@ -80,6 +82,7 @@ export class OneBot11WsEndpoint extends Onebot11Client implements Endpoint {
80
82
  const callApi = (request: PendingRpcRequest) => this.#rpc.call(request).then(readOnebot11Response);
81
83
  this.#management = createOneBot11EndpointManagement({ callApi });
82
84
  this.#content = createOneBot11ContentPort(callApi);
85
+ this.#profile = createOnebot11SelfProfile({ call: callApi, reportAccount: id => this[endpointSpec].reportSelfUserId(id) });
83
86
  const readTransportState = () => this.#lifecycle.state;
84
87
  this[endpointSpec] = createEndpointSpecification(this, {
85
88
  name: options.config.id,
@@ -88,6 +91,7 @@ export class OneBot11WsEndpoint extends Onebot11Client implements Endpoint {
88
91
  send: request => this.#send(request),
89
92
  get transportState() { return readTransportState(); },
90
93
  management: this.#management, control: this.#control, content: this.#content,
94
+ profile: this.#profile,
91
95
  });
92
96
  this.#releaseClientEvents = forwardOnebot11ClientEvents(this, (name, payload) => {
93
97
  void this[endpointSpec].events.run(() => {
@@ -145,7 +149,7 @@ export class OneBot11WsEndpoint extends Onebot11Client implements Endpoint {
145
149
  this.#rpc.dispose();
146
150
  this.#releaseClientEvents();
147
151
  this.#ws = undefined;
148
- this[endpointSpec].reportSelfUserId(undefined);
152
+ this.#profile.reportAccount(undefined);
149
153
  }
150
154
  }
151
155
 
@@ -175,7 +179,7 @@ export class OneBot11WsEndpoint extends Onebot11Client implements Endpoint {
175
179
 
176
180
  #admitRaw(ev: OneBot11Event): void {
177
181
  const selfId = resolveOnebot11SelfId(ev.self_id);
178
- if (!this.#stopped && selfId) this[endpointSpec].reportSelfUserId(selfId);
182
+ if (!this.#stopped && selfId) this.#profile.reportAccount(selfId);
179
183
  if (!isMessageEvent(ev)) {
180
184
  receiveOneBot11SideEvent(ev, {
181
185
  emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
@@ -221,7 +225,7 @@ export class OneBot11WsEndpoint extends Onebot11Client implements Endpoint {
221
225
  }));
222
226
  });
223
227
  if (this.#stopped || this.#transportState !== 'open') return;
224
- if (selfId) this[endpointSpec].reportSelfUserId(selfId);
228
+ if (selfId) this.#profile.reportAccount(selfId);
225
229
  }
226
230
 
227
231
  #warnPlatformEvent(error: unknown): void {
@@ -1,3 +1,4 @@
1
+ import { createOnebot11SelfProfile } from './self-profile.js';
1
2
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
3
  /**
3
4
  * OneBot11 reverse WSS endpoint — accepts inbound WebSocket from OneBot implementation.
@@ -64,6 +65,7 @@ export class OneBot11WssEndpoint extends Onebot11Client implements Endpoint {
64
65
  readonly #connectionLifecycle: EndpointLifecycle;
65
66
  #connectionTask = Promise.resolve();
66
67
  readonly #rpc: OneBot11Rpc;
68
+ readonly #profile: ReturnType<typeof createOnebot11SelfProfile>;
67
69
  #started = false;
68
70
  #stopped = false;
69
71
 
@@ -83,6 +85,7 @@ export class OneBot11WssEndpoint extends Onebot11Client implements Endpoint {
83
85
  const callApi = (request: PendingRpcRequest) => this.#rpc.call(request).then(readOnebot11Response);
84
86
  this.#management = createOneBot11EndpointManagement({ callApi });
85
87
  this.#content = createOneBot11ContentPort(callApi);
88
+ this.#profile = createOnebot11SelfProfile({ call: callApi, reportAccount: id => this[endpointSpec].reportSelfUserId(id) });
86
89
  const readTransportState = () => this.#transportState;
87
90
  this[endpointSpec] = createEndpointSpecification(this, {
88
91
  name: options.config.id,
@@ -91,6 +94,7 @@ export class OneBot11WssEndpoint extends Onebot11Client implements Endpoint {
91
94
  send: request => this.#send(request),
92
95
  get transportState() { return readTransportState(); },
93
96
  management: this.#management, control: this.#control, content: this.#content,
97
+ profile: this.#profile,
94
98
  });
95
99
  this.#releaseClientEvents = forwardOnebot11ClientEvents(this, (name, payload) => {
96
100
  void this[endpointSpec].events.run(() => {
@@ -155,7 +159,7 @@ export class OneBot11WssEndpoint extends Onebot11Client implements Endpoint {
155
159
  this.#releaseClientEvents();
156
160
  this.#ws = undefined;
157
161
  this.#started = false;
158
- this[endpointSpec].reportSelfUserId(undefined);
162
+ this.#profile.reportAccount(undefined);
159
163
  }
160
164
  }
161
165
 
@@ -178,7 +182,7 @@ export class OneBot11WssEndpoint extends Onebot11Client implements Endpoint {
178
182
 
179
183
  #admitRaw(ev: OneBot11Event): void {
180
184
  const selfId = resolveOnebot11SelfId(ev.self_id);
181
- if (!this.#stopped && selfId) this[endpointSpec].reportSelfUserId(selfId);
185
+ if (!this.#stopped && selfId) this.#profile.reportAccount(selfId);
182
186
  if (!isMessageEvent(ev)) {
183
187
  receiveOneBot11SideEvent(ev, {
184
188
  emit: (name, payload) => this[endpointSpec].events.emit(name, payload),
@@ -223,7 +227,7 @@ export class OneBot11WssEndpoint extends Onebot11Client implements Endpoint {
223
227
  }));
224
228
  });
225
229
  if (this.#stopped || this.#transportState !== 'open') return;
226
- if (selfId) this[endpointSpec].reportSelfUserId(selfId);
230
+ if (selfId) this.#profile.reportAccount(selfId);
227
231
  }
228
232
 
229
233
  #warnPlatformEvent(error: unknown): void {