@zhin.js/adapter-napcat 1.1.20 → 1.1.21
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 +30 -0
- package/README.md +11 -0
- package/agents/napcat/skills/napcat-account/tools/set_profile/index.js +1 -1
- package/agents/napcat/skills/napcat-account/tools/set_profile/index.ts +1 -1
- package/lib/client.d.ts +3 -0
- package/lib/client.js +73 -2
- package/lib/http-endpoint.js +2 -0
- package/lib/protocol.d.ts +2 -1
- package/lib/self-profile.d.ts +9 -0
- package/lib/self-profile.js +72 -0
- package/lib/ws-endpoint.js +2 -0
- package/lib/wss-endpoint.js +2 -0
- package/package.json +20 -20
- package/schema.json +106 -1
- package/src/client.ts +67 -3
- package/src/http-endpoint.ts +2 -0
- package/src/protocol.ts +2 -1
- package/src/self-profile.ts +75 -0
- package/src/ws-endpoint.ts +2 -0
- package/src/wss-endpoint.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @zhin.js/adapter-napcat
|
|
2
2
|
|
|
3
|
+
## 1.1.21
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [b88e988]
|
|
14
|
+
- Updated dependencies [f78eb84]
|
|
15
|
+
- Updated dependencies [c15cef9]
|
|
16
|
+
- Updated dependencies [b0c02a2]
|
|
17
|
+
- Updated dependencies [af8f803]
|
|
18
|
+
- Updated dependencies [26fda97]
|
|
19
|
+
- Updated dependencies [976886c]
|
|
20
|
+
- Updated dependencies [032dc96]
|
|
21
|
+
- @zhin.js/adapter@1.1.27
|
|
22
|
+
- @zhin.js/client@1.1.23
|
|
23
|
+
- @zhin.js/im-contract@1.1.10
|
|
24
|
+
- @zhin.js/core@1.1.55
|
|
25
|
+
- @zhin.js/tool@1.1.14
|
|
26
|
+
- @zhin.js/page@1.1.13
|
|
27
|
+
- @zhin.js/host-http@1.1.13
|
|
28
|
+
- @zhin.js/agent-feature@1.1.12
|
|
29
|
+
- @zhin.js/feature-kit@1.1.11
|
|
30
|
+
- @zhin.js/permission@1.1.11
|
|
31
|
+
- @zhin.js/skill@1.1.12
|
|
32
|
+
|
|
3
33
|
## 1.1.20
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -116,3 +116,14 @@ MIT License
|
|
|
116
116
|
### 高阶消息支持边界
|
|
117
117
|
|
|
118
118
|
NapCat 官方消息兼容表标明 share 仅接收,不支持发送;Markdown 仅可在双层合并转发内发送,不支持直接消息。Canonical `markdown`、`share`、`keyboard` 直接出站明确返回 `unsupported_operation / not_sent`,不会将占位段或原始协议猜测报告成功。当前未实现合并转发 Markdown,亦无原生按钮回调闭环。参考:[官方兼容表](https://napneko.github.io/develop/msg)。
|
|
119
|
+
|
|
120
|
+
## 当前账号资料
|
|
121
|
+
|
|
122
|
+
三个传输模式(WS、反向 WS、HTTP)共用当前账号资料端口。读取通过 `get_login_info` 定位真实账号,再取该账号的 `get_stranger_info`;写入只作用于当前登录账号,沿用 Agent 身份类审批。
|
|
123
|
+
|
|
124
|
+
- 可读:昵称、性别、年龄、个性签名。
|
|
125
|
+
- 可写:昵称、性别、签名、头像。年龄、生日、兴趣和独立个人简介没有接到已验证的写接口。
|
|
126
|
+
- `set_qq_profile.personal_note` 是签名,不冒充个人简介;性别变更按上游要求携带当前昵称,其他资料由 NapCat 自身保留。平台接口不提供跨外部客户端的 CAS,因此同时在 QQ 客户端改名可能与此类读后写操作竞争。
|
|
127
|
+
- `set_qq_profile` 内层 `result` 非零同样视为拒绝。头像修改只报告平台已确认成功,不查询或返回修改后的头像 URL,也不把上传源链接或 base64 当成平台头像。
|
|
128
|
+
|
|
129
|
+
`nickname` / `profile` 配置仅补足缺失字段,Endpoint 项可覆盖适配器默认值,配置不写回 QQ。接口语义依据 [NapCat 的资料设置实现](https://github.com/NapNeko/NapCatQQ/blob/main/packages/napcat-onebot/action/go-cqhttp/SetQQProfile.ts) 与 [账号信息实现](https://github.com/NapNeko/NapCatQQ/blob/main/packages/napcat-onebot/action/go-cqhttp/GetStrangerInfo.ts)。
|
|
@@ -8,7 +8,7 @@ export default defineAgentTool({
|
|
|
8
8
|
company: z.string().optional().describe('公司(可选)'),
|
|
9
9
|
email: z.string().optional().describe('邮箱(可选)'),
|
|
10
10
|
college: z.string().optional().describe('学校(可选)'),
|
|
11
|
-
personal_note: z.string().optional().describe('
|
|
11
|
+
personal_note: z.string().optional().describe('个性签名(可选)'),
|
|
12
12
|
}),
|
|
13
13
|
adapter: 'napcat',
|
|
14
14
|
tags: ['napcat', 'qq'],
|
|
@@ -8,7 +8,7 @@ export default defineAgentTool<{ nickname: string; company?: string; email?: str
|
|
|
8
8
|
company: z.string().optional().describe('公司(可选)'),
|
|
9
9
|
email: z.string().optional().describe('邮箱(可选)'),
|
|
10
10
|
college: z.string().optional().describe('学校(可选)'),
|
|
11
|
-
personal_note: z.string().optional().describe('
|
|
11
|
+
personal_note: z.string().optional().describe('个性签名(可选)'),
|
|
12
12
|
}),
|
|
13
13
|
adapter: 'napcat',
|
|
14
14
|
tags: ['napcat', 'qq'],
|
package/lib/client.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import { callNapCatHttpAction, type NapCatEvent, type ResolvedNapCatConfig } from './protocol.js';
|
|
3
3
|
import { type NapCatRpc } from './ws-transport.js';
|
|
4
|
+
import { type SelfProfile } from '@zhin.js/im-contract';
|
|
4
5
|
import type { PendingRpcRequest } from 'zhin.js/adapter';
|
|
5
6
|
import type { NapCatWsSocket } from './ws-types.js';
|
|
6
7
|
export type NapcatApiCall = (action: string, params?: Record<string, unknown>) => Promise<unknown>;
|
|
@@ -43,6 +44,8 @@ export declare class NapcatClient extends EventEmitter<Record<string, [NapCatEve
|
|
|
43
44
|
protected transportSocket?: NapCatWsSocket;
|
|
44
45
|
protected readonly rpc: NapCatRpc;
|
|
45
46
|
protected readonly protocolConfig: ResolvedNapCatConfig;
|
|
47
|
+
/** Confirmed native self facts shared by platform tools and the semantic profile port. */
|
|
48
|
+
get currentSelfProfile(): Readonly<SelfProfile>;
|
|
46
49
|
constructor(options: NapcatClientOptions);
|
|
47
50
|
ingest(event: NapCatEvent): void;
|
|
48
51
|
protected get actionSocket(): NapCatWsSocket | undefined;
|
package/lib/client.js
CHANGED
|
@@ -9,6 +9,11 @@ export class NapcatClient extends EventEmitter {
|
|
|
9
9
|
protocolConfig;
|
|
10
10
|
#httpAction;
|
|
11
11
|
#executionSignal;
|
|
12
|
+
#selfAccountId;
|
|
13
|
+
#selfProfile = Object.freeze({});
|
|
14
|
+
#profileRevision = 0;
|
|
15
|
+
/** Confirmed native self facts shared by platform tools and the semantic profile port. */
|
|
16
|
+
get currentSelfProfile() { return this.#selfProfile; }
|
|
12
17
|
constructor(options) {
|
|
13
18
|
super();
|
|
14
19
|
this.protocolConfig = options.config;
|
|
@@ -28,6 +33,7 @@ export class NapcatClient extends EventEmitter {
|
|
|
28
33
|
callApi = (action, params = {}) => this.requestApi({ action, params });
|
|
29
34
|
/** A semantic operation supplies its caller lifetime to the same native protocol owner. */
|
|
30
35
|
async requestApi(request) {
|
|
36
|
+
const profileRevision = this.#profileRevision;
|
|
31
37
|
const signals = [this.#executionSignal, request.signal].filter((signal) => !!signal);
|
|
32
38
|
const signal = signals.length ? AbortSignal.any(signals) : undefined;
|
|
33
39
|
if (signal?.aborted)
|
|
@@ -38,7 +44,9 @@ export class NapcatClient extends EventEmitter {
|
|
|
38
44
|
access_token: this.protocolConfig.access_token, signal }, request.action, request.params ?? {});
|
|
39
45
|
if (signal?.aborted)
|
|
40
46
|
throw new EndpointDeliveryError('endpoint_inactive', 'Endpoint operation cancelled', 'unknown');
|
|
41
|
-
|
|
47
|
+
const data = readNapCatActionResponse(response).data;
|
|
48
|
+
this.#observeSelfProfile(request, data, profileRevision, signal);
|
|
49
|
+
return data;
|
|
42
50
|
}
|
|
43
51
|
catch (cause) {
|
|
44
52
|
if (cause instanceof EndpointDeliveryError)
|
|
@@ -47,7 +55,70 @@ export class NapcatClient extends EventEmitter {
|
|
|
47
55
|
}
|
|
48
56
|
}
|
|
49
57
|
const response = await this.rpc.call({ ...request, signal });
|
|
50
|
-
|
|
58
|
+
const data = readNapCatActionResponse(response).data;
|
|
59
|
+
this.#observeSelfProfile(request, data, profileRevision, signal);
|
|
60
|
+
return data;
|
|
61
|
+
}
|
|
62
|
+
/** Observe the one native action after its acknowledgement; never submit a second platform effect. */
|
|
63
|
+
#observeSelfProfile(request, data, revision, signal) {
|
|
64
|
+
if (signal?.aborted)
|
|
65
|
+
return;
|
|
66
|
+
const result = data && typeof data === 'object' ? data : undefined;
|
|
67
|
+
if (typeof result?.result === 'number' && result.result !== 0)
|
|
68
|
+
return;
|
|
69
|
+
const params = request.params ?? {};
|
|
70
|
+
let facts = {};
|
|
71
|
+
switch (request.action) {
|
|
72
|
+
case 'get_login_info': {
|
|
73
|
+
if (revision !== this.#profileRevision)
|
|
74
|
+
return;
|
|
75
|
+
const id = result?.user_id;
|
|
76
|
+
if ((typeof id !== 'number' && typeof id !== 'string') || !String(id).trim() || id === 0 || id === '0')
|
|
77
|
+
return;
|
|
78
|
+
if (this.#selfAccountId !== undefined && this.#selfAccountId !== String(id))
|
|
79
|
+
this.#selfProfile = Object.freeze({});
|
|
80
|
+
this.#selfAccountId = String(id);
|
|
81
|
+
if (typeof result?.nickname === 'string')
|
|
82
|
+
facts = { nickname: result.nickname };
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case 'get_stranger_info': {
|
|
86
|
+
if (revision !== this.#profileRevision)
|
|
87
|
+
return;
|
|
88
|
+
if (!this.#selfAccountId || String(params.user_id) !== this.#selfAccountId || String(result?.user_id) !== this.#selfAccountId)
|
|
89
|
+
return;
|
|
90
|
+
facts = {
|
|
91
|
+
...(typeof result?.nickname === 'string' ? { nickname: result.nickname } : {}),
|
|
92
|
+
...(result?.sex === 'male' || result?.sex === 'female' || result?.sex === 'unknown' ? { gender: result.sex } : {}),
|
|
93
|
+
...(typeof result?.age === 'number' && Number.isFinite(result.age) ? { age: result.age } : {}),
|
|
94
|
+
...(typeof result?.long_nick === 'string' ? { signature: result.long_nick } : {}),
|
|
95
|
+
};
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'set_qq_profile': {
|
|
99
|
+
const gender = params.sex === 1 || params.sex === '1' ? 'male'
|
|
100
|
+
: params.sex === 2 || params.sex === '2' ? 'female'
|
|
101
|
+
: params.sex === 0 || params.sex === '0' ? 'unknown' : undefined;
|
|
102
|
+
facts = { ...(typeof params.nickname === 'string' ? { nickname: params.nickname } : {}),
|
|
103
|
+
...(typeof params.personal_note === 'string' ? { signature: params.personal_note } : {}),
|
|
104
|
+
...(gender ? { gender } : {}),
|
|
105
|
+
};
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case 'set_self_longnick':
|
|
109
|
+
if (typeof params.longNick === 'string')
|
|
110
|
+
facts = { signature: params.longNick };
|
|
111
|
+
break;
|
|
112
|
+
case 'set_qq_avatar': {
|
|
113
|
+
const { avatar: _previousAvatar, ...rest } = this.#selfProfile;
|
|
114
|
+
this.#selfProfile = Object.freeze(rest);
|
|
115
|
+
this.#profileRevision += 1;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
default: return;
|
|
119
|
+
}
|
|
120
|
+
this.#selfProfile = Object.freeze({ ...this.#selfProfile, ...facts });
|
|
121
|
+
this.#profileRevision += 1;
|
|
51
122
|
}
|
|
52
123
|
async setTitle({ groupId, userId, title, duration = -1 }) {
|
|
53
124
|
await this.callApi('set_group_special_title', {
|
package/lib/http-endpoint.js
CHANGED
|
@@ -2,6 +2,7 @@ import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
|
2
2
|
import { createEndpointSpecification, endpointSpec, createRecallEndpointControl, } from 'zhin.js/adapter';
|
|
3
3
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
4
4
|
import { createNapCatEndpointManagement } from './endpoint-management.js';
|
|
5
|
+
import { createNapcatSelfProfile } from './self-profile.js';
|
|
5
6
|
import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
|
|
6
7
|
import { buildSendAction, callNapCatHttpAction, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
|
|
7
8
|
import { receiveNapCatSideEvent } from './side-event-dispatch.js';
|
|
@@ -32,6 +33,7 @@ export class NapCatHttpEndpoint extends NapcatClient {
|
|
|
32
33
|
close: () => this.#close(), stop: () => this.#stop(),
|
|
33
34
|
send: request => this.#send(request),
|
|
34
35
|
management: this.#management, control: this.#control, content: this.#content,
|
|
36
|
+
profile: createNapcatSelfProfile(this, id => this[endpointSpec].reportSelfUserId(id)),
|
|
35
37
|
get transportState() { return readTransportState(); },
|
|
36
38
|
});
|
|
37
39
|
this.on('event', event => {
|
package/lib/protocol.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EndpointProfileConfiguration } from 'zhin.js/adapter';
|
|
1
2
|
/**
|
|
2
3
|
* NapCat protocol helpers (OneBot 11 + NapCat extensions).
|
|
3
4
|
* No legacy Adapter/Endpoint / segment-mapper.
|
|
@@ -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 NapCatEndpointConfig {
|
|
9
|
+
export interface NapCatEndpointConfig extends EndpointProfileConfiguration {
|
|
9
10
|
readonly connection?: 'ws' | 'wss' | 'http';
|
|
10
11
|
readonly id: string;
|
|
11
12
|
readonly access_token?: string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EndpointProfile, PendingRpcRequest } from 'zhin.js/adapter';
|
|
2
|
+
import { type SelfProfile } from '@zhin.js/im-contract';
|
|
3
|
+
interface NapcatSelfProfileClient {
|
|
4
|
+
readonly currentSelfProfile?: Readonly<SelfProfile>;
|
|
5
|
+
requestApi(request: PendingRpcRequest): Promise<unknown>;
|
|
6
|
+
}
|
|
7
|
+
/** All three transports share the native action contract and their existing cancellation owner. */
|
|
8
|
+
export declare function createNapcatSelfProfile(client: NapcatSelfProfileClient, reportAccount?: (id: string) => void): EndpointProfile;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
2
|
+
/** All three transports share the native action contract and their existing cancellation owner. */
|
|
3
|
+
export function createNapcatSelfProfile(client, reportAccount) {
|
|
4
|
+
const account = async (signal) => {
|
|
5
|
+
const value = await client.requestApi({ action: 'get_login_info', signal });
|
|
6
|
+
if (!value?.user_id || typeof value.nickname !== 'string')
|
|
7
|
+
throw new Error('NapCat self account is unavailable');
|
|
8
|
+
reportAccount?.(String(value.user_id));
|
|
9
|
+
return value;
|
|
10
|
+
};
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
writableFields: Object.freeze(['nickname', 'gender', 'signature', 'avatar']),
|
|
13
|
+
get current() { return client.currentSelfProfile ?? {}; },
|
|
14
|
+
async read({ signal }) {
|
|
15
|
+
const self = await account(signal);
|
|
16
|
+
const raw = await client.requestApi({ action: 'get_stranger_info', params: { user_id: String(self.user_id), no_cache: true }, signal });
|
|
17
|
+
if (!raw || String(raw.user_id) !== String(self.user_id))
|
|
18
|
+
throw new Error('NapCat returned another account profile');
|
|
19
|
+
const profile = {
|
|
20
|
+
...(typeof raw.nickname === 'string' ? { nickname: raw.nickname } : {}),
|
|
21
|
+
...(raw.sex === 'male' || raw.sex === 'female' || raw.sex === 'unknown' ? { gender: raw.sex } : {}),
|
|
22
|
+
...(typeof raw.age === 'number' && Number.isFinite(raw.age) ? { age: raw.age } : {}),
|
|
23
|
+
...(typeof raw.long_nick === 'string' ? { signature: raw.long_nick } : {}),
|
|
24
|
+
};
|
|
25
|
+
return profile;
|
|
26
|
+
},
|
|
27
|
+
async write({ change, signal }) {
|
|
28
|
+
let action;
|
|
29
|
+
let params;
|
|
30
|
+
switch (change.field) {
|
|
31
|
+
case 'nickname':
|
|
32
|
+
action = 'set_qq_profile';
|
|
33
|
+
params = { nickname: change.value };
|
|
34
|
+
break;
|
|
35
|
+
case 'gender': {
|
|
36
|
+
if (change.value === 'other')
|
|
37
|
+
return { status: 'rejected', message: 'NapCat supports male, female or unknown gender' };
|
|
38
|
+
// The native API requires nickname even when only changing sex.
|
|
39
|
+
let self;
|
|
40
|
+
try {
|
|
41
|
+
self = await account(signal);
|
|
42
|
+
}
|
|
43
|
+
catch (cause) {
|
|
44
|
+
throw new EndpointDeliveryError('endpoint_unavailable', 'NapCat profile change was not sent: self account lookup failed', 'not_sent', { cause });
|
|
45
|
+
}
|
|
46
|
+
action = 'set_qq_profile';
|
|
47
|
+
params = { nickname: self.nickname, sex: change.value === 'male' ? 1 : change.value === 'female' ? 2 : 0 };
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case 'signature':
|
|
51
|
+
action = 'set_self_longnick';
|
|
52
|
+
params = { longNick: change.value };
|
|
53
|
+
break;
|
|
54
|
+
case 'avatar': {
|
|
55
|
+
if (change.value.kind === 'file') {
|
|
56
|
+
return { status: 'rejected', message: 'NapCat avatar requires resolved image bytes, URL or remote path' };
|
|
57
|
+
}
|
|
58
|
+
action = 'set_qq_avatar';
|
|
59
|
+
params = { file: change.value.kind === 'base64' ? `base64://${change.value.value}` : change.value.value };
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
default: return { status: 'rejected', message: `NapCat cannot safely update ${change.field}` };
|
|
63
|
+
}
|
|
64
|
+
const result = await client.requestApi({ action, params, signal });
|
|
65
|
+
if (result && typeof result.result === 'number' && result.result !== 0) {
|
|
66
|
+
return { status: 'rejected', message: result.errMsg ?? 'NapCat rejected the profile change' };
|
|
67
|
+
}
|
|
68
|
+
return change.field === 'avatar' ? { status: 'updated' }
|
|
69
|
+
: { status: 'updated', profile: { [change.field]: change.value } };
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
package/lib/ws-endpoint.js
CHANGED
|
@@ -6,6 +6,7 @@ import WebSocket from 'ws';
|
|
|
6
6
|
import { createEndpointSpecification, endpointSpec, createRecallEndpointControl, createEndpointLifecycle, } from 'zhin.js/adapter';
|
|
7
7
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
8
8
|
import { createNapCatEndpointManagement } from './endpoint-management.js';
|
|
9
|
+
import { createNapcatSelfProfile } from './self-profile.js';
|
|
9
10
|
import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
|
|
10
11
|
import { buildSendAction, buildWsConnectOptions, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
|
|
11
12
|
import { receiveNapCatSideEvent } from './side-event-dispatch.js';
|
|
@@ -32,6 +33,7 @@ export class NapCatWsEndpoint extends NapcatClient {
|
|
|
32
33
|
close: () => this.#close(), stop: () => this.#stop(),
|
|
33
34
|
send: request => this.#send(request),
|
|
34
35
|
management: this.#management, control: this.#control, content: this.#content,
|
|
36
|
+
profile: createNapcatSelfProfile(this, id => this[endpointSpec].reportSelfUserId(id)),
|
|
35
37
|
get transportState() { return readTransportState(); },
|
|
36
38
|
});
|
|
37
39
|
this.on('event', event => {
|
package/lib/wss-endpoint.js
CHANGED
|
@@ -5,6 +5,7 @@ import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
|
5
5
|
import { createEndpointSpecification, endpointSpec, createEndpointLifecycle, createRecallEndpointControl, } from 'zhin.js/adapter';
|
|
6
6
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
7
7
|
import { createNapCatEndpointManagement } from './endpoint-management.js';
|
|
8
|
+
import { createNapcatSelfProfile } from './self-profile.js';
|
|
8
9
|
import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
|
|
9
10
|
import { buildSendAction, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
|
|
10
11
|
import { receiveNapCatSideEvent } from './side-event-dispatch.js';
|
|
@@ -36,6 +37,7 @@ export class NapCatWssEndpoint extends NapcatClient {
|
|
|
36
37
|
close: () => this.#close(), stop: () => this.#stop(),
|
|
37
38
|
send: request => this.#send(request),
|
|
38
39
|
management: this.#management, control: this.#control, content: this.#content,
|
|
40
|
+
profile: createNapcatSelfProfile(this, id => this[endpointSpec].reportSelfUserId(id)),
|
|
39
41
|
get transportState() { return readTransportState(); },
|
|
40
42
|
});
|
|
41
43
|
this.on('event', event => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-napcat",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.21",
|
|
4
4
|
"description": "Zhin.js NapCat adapter for Plugin Runtime (WebSocket client, OneBot11 + NapCat extensions)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -45,19 +45,19 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"ws": "^8.21.1",
|
|
48
|
-
"@zhin.js/adapter": "1.1.
|
|
49
|
-
"@zhin.js/agent-feature": "1.1.
|
|
50
|
-
"@zhin.js/client": "1.1.
|
|
48
|
+
"@zhin.js/adapter": "1.1.27",
|
|
49
|
+
"@zhin.js/agent-feature": "1.1.12",
|
|
50
|
+
"@zhin.js/client": "1.1.23",
|
|
51
51
|
"@zhin.js/console-contract": "1.1.3",
|
|
52
|
-
"@zhin.js/core": "1.1.
|
|
53
|
-
"@zhin.js/feature-kit": "1.1.
|
|
54
|
-
"@zhin.js/host-http": "1.1.
|
|
55
|
-
"@zhin.js/im-contract": "1.1.
|
|
52
|
+
"@zhin.js/core": "1.1.55",
|
|
53
|
+
"@zhin.js/feature-kit": "1.1.11",
|
|
54
|
+
"@zhin.js/host-http": "1.1.13",
|
|
55
|
+
"@zhin.js/im-contract": "1.1.10",
|
|
56
56
|
"@zhin.js/logger": "1.1.2",
|
|
57
|
-
"@zhin.js/page": "1.1.
|
|
58
|
-
"@zhin.js/permission": "1.1.
|
|
59
|
-
"@zhin.js/skill": "1.1.
|
|
60
|
-
"@zhin.js/tool": "1.1.
|
|
57
|
+
"@zhin.js/page": "1.1.13",
|
|
58
|
+
"@zhin.js/permission": "1.1.11",
|
|
59
|
+
"@zhin.js/skill": "1.1.12",
|
|
60
|
+
"@zhin.js/tool": "1.1.14"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/node": "^26.1.2",
|
|
@@ -65,15 +65,15 @@
|
|
|
65
65
|
"typescript": "^6.0.3",
|
|
66
66
|
"vitest": "^4.1.10",
|
|
67
67
|
"zod": "^4.4.3",
|
|
68
|
-
"@zhin.js/host-http": "1.1.
|
|
69
|
-
"zhin.js": "1.1.
|
|
68
|
+
"@zhin.js/host-http": "1.1.13",
|
|
69
|
+
"zhin.js": "1.1.20"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
72
|
"zod": "^4.0.0",
|
|
73
|
-
"@zhin.js/adapter": "^1.1.
|
|
74
|
-
"@zhin.js/command": "^1.1.
|
|
75
|
-
"@zhin.js/core": "^1.1.
|
|
76
|
-
"zhin.js": "^1.1.
|
|
73
|
+
"@zhin.js/adapter": "^1.1.27",
|
|
74
|
+
"@zhin.js/command": "^1.1.13",
|
|
75
|
+
"@zhin.js/core": "^1.1.55",
|
|
76
|
+
"zhin.js": "^1.1.20"
|
|
77
77
|
},
|
|
78
78
|
"peerDependenciesMeta": {
|
|
79
79
|
"@zhin.js/command": {
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"features": [
|
|
108
108
|
{
|
|
109
109
|
"package": "@zhin.js/adapter",
|
|
110
|
-
"api": "^2.
|
|
110
|
+
"api": "^2.1.0"
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
113
|
"package": "@zhin.js/command",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
117
|
"package": "@zhin.js/tool",
|
|
118
|
-
"api": "^1.
|
|
118
|
+
"api": "^1.1.0"
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
"package": "@zhin.js/skill",
|
package/schema.json
CHANGED
|
@@ -132,6 +132,27 @@
|
|
|
132
132
|
"id": {
|
|
133
133
|
"type": "string",
|
|
134
134
|
"description": "NapCat bot name"
|
|
135
|
+
},
|
|
136
|
+
"nickname": {
|
|
137
|
+
"type": [
|
|
138
|
+
"string",
|
|
139
|
+
"null"
|
|
140
|
+
],
|
|
141
|
+
"description": "账号昵称兜底",
|
|
142
|
+
"x-i18n": {
|
|
143
|
+
"en": {
|
|
144
|
+
"description": "Fallback account nickname"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"profile": {
|
|
149
|
+
"$ref": "#/$defs/selfProfileFallback",
|
|
150
|
+
"description": "账号资料兜底",
|
|
151
|
+
"x-i18n": {
|
|
152
|
+
"en": {
|
|
153
|
+
"description": "Fallback account profile"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
135
156
|
}
|
|
136
157
|
},
|
|
137
158
|
"required": [
|
|
@@ -153,9 +174,93 @@
|
|
|
153
174
|
"description": "Command prefix. The default empty string allows unprefixed commands; '/' requires a leading slash. Each endpoints[i] can override it."
|
|
154
175
|
}
|
|
155
176
|
}
|
|
177
|
+
},
|
|
178
|
+
"nickname": {
|
|
179
|
+
"type": [
|
|
180
|
+
"string",
|
|
181
|
+
"null"
|
|
182
|
+
],
|
|
183
|
+
"description": "账号昵称兜底",
|
|
184
|
+
"x-i18n": {
|
|
185
|
+
"en": {
|
|
186
|
+
"description": "Fallback account nickname"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"profile": {
|
|
191
|
+
"$ref": "#/$defs/selfProfileFallback",
|
|
192
|
+
"description": "账号资料兜底",
|
|
193
|
+
"x-i18n": {
|
|
194
|
+
"en": {
|
|
195
|
+
"description": "Fallback account profile"
|
|
196
|
+
}
|
|
197
|
+
}
|
|
156
198
|
}
|
|
157
199
|
},
|
|
158
200
|
"required": [
|
|
159
201
|
"endpoints"
|
|
160
|
-
]
|
|
202
|
+
],
|
|
203
|
+
"$defs": {
|
|
204
|
+
"selfProfileFallback": {
|
|
205
|
+
"type": "object",
|
|
206
|
+
"additionalProperties": false,
|
|
207
|
+
"properties": {
|
|
208
|
+
"gender": {
|
|
209
|
+
"type": [
|
|
210
|
+
"string",
|
|
211
|
+
"null"
|
|
212
|
+
],
|
|
213
|
+
"enum": [
|
|
214
|
+
"male",
|
|
215
|
+
"female",
|
|
216
|
+
"other",
|
|
217
|
+
"unknown",
|
|
218
|
+
null
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
"age": {
|
|
222
|
+
"type": [
|
|
223
|
+
"integer",
|
|
224
|
+
"null"
|
|
225
|
+
],
|
|
226
|
+
"minimum": 0
|
|
227
|
+
},
|
|
228
|
+
"birthday": {
|
|
229
|
+
"type": [
|
|
230
|
+
"string",
|
|
231
|
+
"null"
|
|
232
|
+
],
|
|
233
|
+
"pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
|
|
234
|
+
},
|
|
235
|
+
"signature": {
|
|
236
|
+
"type": [
|
|
237
|
+
"string",
|
|
238
|
+
"null"
|
|
239
|
+
]
|
|
240
|
+
},
|
|
241
|
+
"avatar": {
|
|
242
|
+
"type": [
|
|
243
|
+
"string",
|
|
244
|
+
"null"
|
|
245
|
+
],
|
|
246
|
+
"pattern": "^(https?://[^\\s/?#]+(?:[/?#].*)?|)$"
|
|
247
|
+
},
|
|
248
|
+
"interests": {
|
|
249
|
+
"type": [
|
|
250
|
+
"array",
|
|
251
|
+
"null"
|
|
252
|
+
],
|
|
253
|
+
"items": {
|
|
254
|
+
"type": "string"
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
"bio": {
|
|
258
|
+
"type": [
|
|
259
|
+
"string",
|
|
260
|
+
"null"
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
161
266
|
}
|
package/src/client.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
2
|
import { callNapCatHttpAction, readNapCatActionResponse, type NapCatEvent, type ResolvedNapCatConfig } from './protocol.js';
|
|
3
3
|
import { createNapCatRpc, type NapCatRpc } from './ws-transport.js';
|
|
4
|
-
import { EndpointDeliveryError } from '@zhin.js/im-contract';
|
|
4
|
+
import { EndpointDeliveryError, type SelfProfile } from '@zhin.js/im-contract';
|
|
5
5
|
import type { PendingRpcRequest } from 'zhin.js/adapter';
|
|
6
6
|
import type { NapCatWsSocket } from './ws-types.js';
|
|
7
7
|
|
|
@@ -52,6 +52,12 @@ export class NapcatClient extends EventEmitter<Record<string, [NapCatEvent]>> {
|
|
|
52
52
|
protected readonly protocolConfig: ResolvedNapCatConfig;
|
|
53
53
|
readonly #httpAction: typeof callNapCatHttpAction;
|
|
54
54
|
readonly #executionSignal?: AbortSignal;
|
|
55
|
+
#selfAccountId?: string;
|
|
56
|
+
#selfProfile: Readonly<SelfProfile> = Object.freeze({});
|
|
57
|
+
#profileRevision = 0;
|
|
58
|
+
|
|
59
|
+
/** Confirmed native self facts shared by platform tools and the semantic profile port. */
|
|
60
|
+
get currentSelfProfile(): Readonly<SelfProfile> { return this.#selfProfile; }
|
|
55
61
|
|
|
56
62
|
constructor(options: NapcatClientOptions) {
|
|
57
63
|
super();
|
|
@@ -75,6 +81,7 @@ export class NapcatClient extends EventEmitter<Record<string, [NapCatEvent]>> {
|
|
|
75
81
|
|
|
76
82
|
/** A semantic operation supplies its caller lifetime to the same native protocol owner. */
|
|
77
83
|
async requestApi(request: PendingRpcRequest): Promise<unknown> {
|
|
84
|
+
const profileRevision = this.#profileRevision;
|
|
78
85
|
const signals = [this.#executionSignal, request.signal].filter((signal): signal is AbortSignal => !!signal);
|
|
79
86
|
const signal = signals.length ? AbortSignal.any(signals) : undefined;
|
|
80
87
|
if (signal?.aborted) throw new EndpointDeliveryError('endpoint_inactive', 'Endpoint operation ended', 'not_sent');
|
|
@@ -83,14 +90,71 @@ export class NapcatClient extends EventEmitter<Record<string, [NapCatEvent]>> {
|
|
|
83
90
|
const response = await this.#httpAction({ http_url: this.protocolConfig.http_url,
|
|
84
91
|
access_token: this.protocolConfig.access_token, signal }, request.action, request.params ?? {});
|
|
85
92
|
if (signal?.aborted) throw new EndpointDeliveryError('endpoint_inactive', 'Endpoint operation cancelled', 'unknown');
|
|
86
|
-
|
|
93
|
+
const data = readNapCatActionResponse(response).data;
|
|
94
|
+
this.#observeSelfProfile(request, data, profileRevision, signal);
|
|
95
|
+
return data;
|
|
87
96
|
} catch (cause) {
|
|
88
97
|
if (cause instanceof EndpointDeliveryError) throw cause;
|
|
89
98
|
throw new EndpointDeliveryError('delivery_unconfirmed', cause instanceof Error ? cause.message : String(cause), 'unknown', { cause });
|
|
90
99
|
}
|
|
91
100
|
}
|
|
92
101
|
const response = await this.rpc.call({ ...request, signal });
|
|
93
|
-
|
|
102
|
+
const data = readNapCatActionResponse(response).data;
|
|
103
|
+
this.#observeSelfProfile(request, data, profileRevision, signal);
|
|
104
|
+
return data;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Observe the one native action after its acknowledgement; never submit a second platform effect. */
|
|
108
|
+
#observeSelfProfile(request: PendingRpcRequest, data: unknown, revision: number, signal?: AbortSignal): void {
|
|
109
|
+
if (signal?.aborted) return;
|
|
110
|
+
const result = data && typeof data === 'object' ? data as Record<string, unknown> : undefined;
|
|
111
|
+
if (typeof result?.result === 'number' && result.result !== 0) return;
|
|
112
|
+
const params = request.params ?? {};
|
|
113
|
+
let facts: SelfProfile = {};
|
|
114
|
+
switch (request.action) {
|
|
115
|
+
case 'get_login_info': {
|
|
116
|
+
if (revision !== this.#profileRevision) return;
|
|
117
|
+
const id = result?.user_id;
|
|
118
|
+
if ((typeof id !== 'number' && typeof id !== 'string') || !String(id).trim() || id === 0 || id === '0') return;
|
|
119
|
+
if (this.#selfAccountId !== undefined && this.#selfAccountId !== String(id)) this.#selfProfile = Object.freeze({});
|
|
120
|
+
this.#selfAccountId = String(id);
|
|
121
|
+
if (typeof result?.nickname === 'string') facts = { nickname: result.nickname };
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case 'get_stranger_info': {
|
|
125
|
+
if (revision !== this.#profileRevision) return;
|
|
126
|
+
if (!this.#selfAccountId || String(params.user_id) !== this.#selfAccountId || String(result?.user_id) !== this.#selfAccountId) return;
|
|
127
|
+
facts = {
|
|
128
|
+
...(typeof result?.nickname === 'string' ? { nickname: result.nickname } : {}),
|
|
129
|
+
...(result?.sex === 'male' || result?.sex === 'female' || result?.sex === 'unknown' ? { gender: result.sex } : {}),
|
|
130
|
+
...(typeof result?.age === 'number' && Number.isFinite(result.age) ? { age: result.age } : {}),
|
|
131
|
+
...(typeof result?.long_nick === 'string' ? { signature: result.long_nick } : {}),
|
|
132
|
+
};
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case 'set_qq_profile': {
|
|
136
|
+
const gender = params.sex === 1 || params.sex === '1' ? 'male'
|
|
137
|
+
: params.sex === 2 || params.sex === '2' ? 'female'
|
|
138
|
+
: params.sex === 0 || params.sex === '0' ? 'unknown' : undefined;
|
|
139
|
+
facts = { ...(typeof params.nickname === 'string' ? { nickname: params.nickname } : {}),
|
|
140
|
+
...(typeof params.personal_note === 'string' ? { signature: params.personal_note } : {}),
|
|
141
|
+
...(gender ? { gender } : {}),
|
|
142
|
+
};
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case 'set_self_longnick':
|
|
146
|
+
if (typeof params.longNick === 'string') facts = { signature: params.longNick };
|
|
147
|
+
break;
|
|
148
|
+
case 'set_qq_avatar': {
|
|
149
|
+
const { avatar: _previousAvatar, ...rest } = this.#selfProfile;
|
|
150
|
+
this.#selfProfile = Object.freeze(rest);
|
|
151
|
+
this.#profileRevision += 1;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
default: return;
|
|
155
|
+
}
|
|
156
|
+
this.#selfProfile = Object.freeze({ ...this.#selfProfile, ...facts });
|
|
157
|
+
this.#profileRevision += 1;
|
|
94
158
|
}
|
|
95
159
|
|
|
96
160
|
async setTitle({ groupId, userId, title, duration = -1 }: NapcatMemberTitleRequest): Promise<boolean> {
|
package/src/http-endpoint.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
|
15
15
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
16
16
|
import type { CapabilityId } from 'zhin.js';
|
|
17
17
|
import { createNapCatEndpointManagement } from './endpoint-management.js';
|
|
18
|
+
import { createNapcatSelfProfile } from './self-profile.js';
|
|
18
19
|
import {
|
|
19
20
|
InboundMessageDeduper,
|
|
20
21
|
isNapCatBotMentioned,
|
|
@@ -73,6 +74,7 @@ export class NapCatHttpEndpoint extends NapcatClient implements Endpoint {
|
|
|
73
74
|
close: () => this.#close(), stop: () => this.#stop(),
|
|
74
75
|
send: request => this.#send(request),
|
|
75
76
|
management: this.#management, control: this.#control, content: this.#content,
|
|
77
|
+
profile: createNapcatSelfProfile(this, id => this[endpointSpec].reportSelfUserId(id)),
|
|
76
78
|
get transportState() { return readTransportState(); },
|
|
77
79
|
});
|
|
78
80
|
this.on('event', event => {
|
package/src/protocol.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EndpointProfileConfiguration } from 'zhin.js/adapter';
|
|
1
2
|
/**
|
|
2
3
|
* NapCat protocol helpers (OneBot 11 + NapCat extensions).
|
|
3
4
|
* No legacy Adapter/Endpoint / segment-mapper.
|
|
@@ -9,7 +10,7 @@ import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
|
9
10
|
const logger = getLogger('napcat');
|
|
10
11
|
|
|
11
12
|
/** One endpoint config after AdapterIndex expands `plugins.<instanceKey>.endpoints`. */
|
|
12
|
-
export interface NapCatEndpointConfig {
|
|
13
|
+
export interface NapCatEndpointConfig extends EndpointProfileConfiguration {
|
|
13
14
|
readonly connection?: 'ws' | 'wss' | 'http';
|
|
14
15
|
readonly id: string;
|
|
15
16
|
readonly access_token?: string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { EndpointProfile, PendingRpcRequest } from 'zhin.js/adapter';
|
|
2
|
+
import { EndpointDeliveryError, type SelfProfile } from '@zhin.js/im-contract';
|
|
3
|
+
|
|
4
|
+
interface NapcatSelfProfileClient {
|
|
5
|
+
readonly currentSelfProfile?: Readonly<SelfProfile>;
|
|
6
|
+
requestApi(request: PendingRpcRequest): Promise<unknown>;
|
|
7
|
+
}
|
|
8
|
+
interface NapcatAccount { readonly user_id?: number | string; readonly nickname?: string; }
|
|
9
|
+
interface NapcatUserProfile extends NapcatAccount {
|
|
10
|
+
readonly age?: number;
|
|
11
|
+
readonly sex?: 'male' | 'female' | 'unknown';
|
|
12
|
+
readonly long_nick?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** All three transports share the native action contract and their existing cancellation owner. */
|
|
16
|
+
export function createNapcatSelfProfile(client: NapcatSelfProfileClient, reportAccount?: (id: string) => void): EndpointProfile {
|
|
17
|
+
const account = async (signal: AbortSignal): Promise<NapcatAccount> => {
|
|
18
|
+
const value = await client.requestApi({ action: 'get_login_info', signal }) as NapcatAccount;
|
|
19
|
+
if (!value?.user_id || typeof value.nickname !== 'string') throw new Error('NapCat self account is unavailable');
|
|
20
|
+
reportAccount?.(String(value.user_id));
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
return Object.freeze<EndpointProfile>({
|
|
24
|
+
writableFields: Object.freeze(['nickname', 'gender', 'signature', 'avatar']),
|
|
25
|
+
get current() { return client.currentSelfProfile ?? {}; },
|
|
26
|
+
async read({ signal }) {
|
|
27
|
+
const self = await account(signal);
|
|
28
|
+
const raw = await client.requestApi({ action: 'get_stranger_info', params: { user_id: String(self.user_id), no_cache: true }, signal }) as NapcatUserProfile;
|
|
29
|
+
if (!raw || String(raw.user_id) !== String(self.user_id)) throw new Error('NapCat returned another account profile');
|
|
30
|
+
const profile: SelfProfile = {
|
|
31
|
+
...(typeof raw.nickname === 'string' ? { nickname: raw.nickname } : {}),
|
|
32
|
+
...(raw.sex === 'male' || raw.sex === 'female' || raw.sex === 'unknown' ? { gender: raw.sex } : {}),
|
|
33
|
+
...(typeof raw.age === 'number' && Number.isFinite(raw.age) ? { age: raw.age } : {}),
|
|
34
|
+
...(typeof raw.long_nick === 'string' ? { signature: raw.long_nick } : {}),
|
|
35
|
+
};
|
|
36
|
+
return profile;
|
|
37
|
+
},
|
|
38
|
+
async write({ change, signal }) {
|
|
39
|
+
let action: string;
|
|
40
|
+
let params: Record<string, unknown>;
|
|
41
|
+
switch (change.field) {
|
|
42
|
+
case 'nickname': action = 'set_qq_profile'; params = { nickname: change.value }; break;
|
|
43
|
+
case 'gender': {
|
|
44
|
+
if (change.value === 'other') return { status: 'rejected', message: 'NapCat supports male, female or unknown gender' };
|
|
45
|
+
// The native API requires nickname even when only changing sex.
|
|
46
|
+
let self: NapcatAccount;
|
|
47
|
+
try { self = await account(signal); }
|
|
48
|
+
catch (cause) {
|
|
49
|
+
throw new EndpointDeliveryError('endpoint_unavailable',
|
|
50
|
+
'NapCat profile change was not sent: self account lookup failed', 'not_sent', { cause });
|
|
51
|
+
}
|
|
52
|
+
action = 'set_qq_profile';
|
|
53
|
+
params = { nickname: self.nickname, sex: change.value === 'male' ? 1 : change.value === 'female' ? 2 : 0 };
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case 'signature': action = 'set_self_longnick'; params = { longNick: change.value }; break;
|
|
57
|
+
case 'avatar': {
|
|
58
|
+
if (change.value.kind === 'file') {
|
|
59
|
+
return { status: 'rejected', message: 'NapCat avatar requires resolved image bytes, URL or remote path' };
|
|
60
|
+
}
|
|
61
|
+
action = 'set_qq_avatar';
|
|
62
|
+
params = { file: change.value.kind === 'base64' ? `base64://${change.value.value}` : change.value.value };
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
default: return { status: 'rejected', message: `NapCat cannot safely update ${change.field}` };
|
|
66
|
+
}
|
|
67
|
+
const result = await client.requestApi({ action, params, signal }) as { result?: number; errMsg?: string } | null | undefined;
|
|
68
|
+
if (result && typeof result.result === 'number' && result.result !== 0) {
|
|
69
|
+
return { status: 'rejected', message: result.errMsg ?? 'NapCat rejected the profile change' };
|
|
70
|
+
}
|
|
71
|
+
return change.field === 'avatar' ? { status: 'updated' }
|
|
72
|
+
: { status: 'updated', profile: { [change.field]: change.value } };
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
package/src/ws-endpoint.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
18
18
|
import type { CapabilityId } from 'zhin.js';
|
|
19
19
|
import { createNapCatEndpointManagement } from './endpoint-management.js';
|
|
20
|
+
import { createNapcatSelfProfile } from './self-profile.js';
|
|
20
21
|
import {
|
|
21
22
|
InboundMessageDeduper,
|
|
22
23
|
isNapCatBotMentioned,
|
|
@@ -77,6 +78,7 @@ export class NapCatWsEndpoint extends NapcatClient implements Endpoint {
|
|
|
77
78
|
close: () => this.#close(), stop: () => this.#stop(),
|
|
78
79
|
send: request => this.#send(request),
|
|
79
80
|
management: this.#management, control: this.#control, content: this.#content,
|
|
81
|
+
profile: createNapcatSelfProfile(this, id => this[endpointSpec].reportSelfUserId(id)),
|
|
80
82
|
get transportState() { return readTransportState(); },
|
|
81
83
|
});
|
|
82
84
|
this.on('event', event => {
|
package/src/wss-endpoint.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { HttpHost, WsConnection } from '@zhin.js/host-http';
|
|
|
16
16
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
17
17
|
import type { CapabilityId } from 'zhin.js';
|
|
18
18
|
import { createNapCatEndpointManagement } from './endpoint-management.js';
|
|
19
|
+
import { createNapcatSelfProfile } from './self-profile.js';
|
|
19
20
|
import {
|
|
20
21
|
InboundMessageDeduper,
|
|
21
22
|
isNapCatBotMentioned,
|
|
@@ -76,6 +77,7 @@ export class NapCatWssEndpoint extends NapcatClient implements Endpoint {
|
|
|
76
77
|
close: () => this.#close(), stop: () => this.#stop(),
|
|
77
78
|
send: request => this.#send(request),
|
|
78
79
|
management: this.#management, control: this.#control, content: this.#content,
|
|
80
|
+
profile: createNapcatSelfProfile(this, id => this[endpointSpec].reportSelfUserId(id)),
|
|
79
81
|
get transportState() { return readTransportState(); },
|
|
80
82
|
});
|
|
81
83
|
this.on('event', event => {
|