@zhin.js/adapter-napcat 1.1.18 → 1.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +8 -5
  3. package/adapters/napcat/index.js +2 -29
  4. package/adapters/napcat/index.ts +3 -35
  5. package/agents/napcat/skills/napcat-account/tools/set_profile/index.js +1 -1
  6. package/agents/napcat/skills/napcat-account/tools/set_profile/index.ts +1 -1
  7. package/agents/napcat/skills/napcat-group-files-history/tools/upload_group_file/index.js +1 -1
  8. package/agents/napcat/skills/napcat-group-files-history/tools/upload_group_file/index.ts +1 -1
  9. package/agents/napcat/skills/napcat-group-settings/tools/set_title/index.js +1 -1
  10. package/agents/napcat/skills/napcat-group-settings/tools/set_title/index.ts +1 -1
  11. package/agents/napcat/skills/napcat-media/tools/get_mini_app_ark/index.js +1 -1
  12. package/agents/napcat/skills/napcat-media/tools/get_mini_app_ark/index.ts +1 -1
  13. package/commands/napcat/endpoint/definition.js +0 -2
  14. package/commands/napcat/endpoint/definition.ts +0 -2
  15. package/lib/adapter.d.ts +23 -0
  16. package/lib/adapter.js +42 -0
  17. package/lib/client.d.ts +53 -16
  18. package/lib/client.js +53 -10
  19. package/lib/console.d.ts +1 -1
  20. package/lib/console.js +2 -5
  21. package/lib/endpoint-management.d.ts +2 -2
  22. package/lib/endpoint-management.js +13 -13
  23. package/lib/http-endpoint.d.ts +5 -15
  24. package/lib/http-endpoint.js +44 -36
  25. package/lib/identity.d.ts +3 -0
  26. package/lib/identity.js +32 -0
  27. package/lib/index.d.ts +3 -2
  28. package/lib/index.js +2 -2
  29. package/lib/onebot-get-msg.d.ts +2 -2
  30. package/lib/onebot-get-msg.js +2 -2
  31. package/lib/protocol.d.ts +5 -0
  32. package/lib/protocol.js +23 -11
  33. package/lib/ws-endpoint.d.ts +6 -16
  34. package/lib/ws-endpoint.js +57 -48
  35. package/lib/ws-transport.d.ts +18 -12
  36. package/lib/ws-transport.js +28 -72
  37. package/lib/ws-types.d.ts +0 -5
  38. package/lib/wss-endpoint.d.ts +7 -15
  39. package/lib/wss-endpoint.js +56 -47
  40. package/package.json +18 -18
  41. package/plugin.js +0 -3
  42. package/src/adapter.ts +45 -0
  43. package/src/client.ts +91 -19
  44. package/src/console.ts +2 -5
  45. package/src/endpoint-management.ts +19 -15
  46. package/src/http-endpoint.ts +47 -45
  47. package/src/identity.ts +29 -0
  48. package/src/index.ts +9 -5
  49. package/src/onebot-get-msg.ts +4 -4
  50. package/src/protocol.ts +26 -14
  51. package/src/ws-endpoint.ts +61 -54
  52. package/src/ws-transport.ts +40 -101
  53. package/src/ws-types.ts +0 -6
  54. package/src/wss-endpoint.ts +60 -54
  55. package/lib/napcat-runtime-state.d.ts +0 -1
  56. package/lib/napcat-runtime-state.js +0 -6
  57. package/src/napcat-runtime-state.ts +0 -7
@@ -1,7 +1,7 @@
1
1
  export function createNapCatEndpointManagement(endpoint) {
2
2
  return Object.freeze({
3
- async listFriends() {
4
- const data = await endpoint.callApi('get_friend_list');
3
+ async listFriends(request = {}) {
4
+ const data = await endpoint.requestApi({ action: 'get_friend_list', signal: request.signal });
5
5
  return toArray(data).map((value) => {
6
6
  const friend = asRecord(value);
7
7
  return {
@@ -11,8 +11,8 @@ export function createNapCatEndpointManagement(endpoint) {
11
11
  };
12
12
  });
13
13
  },
14
- async listGroups() {
15
- const data = await endpoint.callApi('get_group_list');
14
+ async listGroups(request = {}) {
15
+ const data = await endpoint.requestApi({ action: 'get_group_list', signal: request.signal });
16
16
  return toArray(data).map((value) => {
17
17
  const group = asRecord(value);
18
18
  return {
@@ -21,17 +21,17 @@ export function createNapCatEndpointManagement(endpoint) {
21
21
  };
22
22
  });
23
23
  },
24
- async listGroupMembers(groupId) {
25
- const data = await endpoint.callApi('get_group_member_list', {
26
- group_id: toNumberId(groupId, 'group_id'),
27
- });
24
+ async listGroupMembers({ groupId, signal }) {
25
+ const data = await endpoint.requestApi({ action: 'get_group_member_list', params: {
26
+ group_id: toNumberId(groupId, 'group_id'),
27
+ }, signal });
28
28
  return toArray(data);
29
29
  },
30
- async sendGroupNotice(groupId, content) {
31
- await endpoint.callApi('_send_group_notice', {
32
- group_id: toNumberId(groupId, 'group_id'),
33
- content,
34
- });
30
+ async sendGroupNotice({ groupId, content, signal }) {
31
+ await endpoint.requestApi({ action: '_send_group_notice', params: {
32
+ group_id: toNumberId(groupId, 'group_id'),
33
+ content,
34
+ }, signal });
35
35
  },
36
36
  });
37
37
  }
@@ -1,27 +1,17 @@
1
- import { Endpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest, type EndpointTransportState } from 'zhin.js/adapter';
1
+ import { endpointSpec, type Endpoint, type EndpointSpecification } from 'zhin.js/adapter';
2
2
  import type { HttpHost } from '@zhin.js/host-http';
3
3
  import type { CapabilityId } from 'zhin.js';
4
- import { callNapCatHttpAction, type NapCatEvent, type NapCatHttpConfig } from './protocol.js';
4
+ import { callNapCatHttpAction, type NapCatHttpConfig } from './protocol.js';
5
5
  import { NapcatClient } from './client.js';
6
6
  export interface NapCatHttpEndpointOptions {
7
7
  readonly id: CapabilityId;
8
+ readonly signal?: AbortSignal;
8
9
  readonly http: HttpHost;
9
10
  readonly config: NapCatHttpConfig;
10
11
  readonly callHttpAction?: typeof callNapCatHttpAction;
11
12
  }
12
- export declare class NapCatHttpEndpoint extends Endpoint<NapcatClient> {
13
+ export declare class NapCatHttpEndpoint extends NapcatClient implements Endpoint {
13
14
  #private;
14
- readonly client: NapcatClient;
15
- readonly management: EndpointManagement;
16
- readonly control: EndpointControl;
17
- readonly content: import("@zhin.js/adapter").EndpointContentPort;
15
+ readonly [endpointSpec]: EndpointSpecification;
18
16
  constructor(options: NapCatHttpEndpointOptions);
19
- get transportState(): EndpointTransportState;
20
- start(): Promise<void>;
21
- open(): void;
22
- close(): void;
23
- stop(): Promise<void>;
24
- send({ conversation, payload }: EndpointSendRequest): Promise<string>;
25
- recallMessage(messageId: string): Promise<void>;
26
- admit(ev: NapCatEvent): void;
27
17
  }
@@ -1,5 +1,5 @@
1
1
  import { EndpointDeliveryError } from '@zhin.js/im-contract';
2
- import { Endpoint, createRecallEndpointControl, } from 'zhin.js/adapter';
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
5
  import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
@@ -9,29 +9,41 @@ import { createNapCatContentPort } from './onebot-get-msg.js';
9
9
  import { NapcatClient } from './client.js';
10
10
  import { readRequestBody } from './webhook.js';
11
11
  import { verifyNapCatAccessToken } from './wss-auth.js';
12
- export class NapCatHttpEndpoint extends Endpoint {
13
- client = new NapcatClient((action, params) => this.#callApi(action, params));
12
+ export class NapCatHttpEndpoint extends NapcatClient {
13
+ [endpointSpec];
14
14
  #logger;
15
15
  #options;
16
16
  #inboundDeduper = new InboundMessageDeduper();
17
- management = createNapCatEndpointManagement(this.client);
18
- control = createRecallEndpointControl((id) => this.recallMessage(id));
19
- content = createNapCatContentPort((action, params) => this.client.callApi(action, params));
20
- #callHttpAction;
17
+ #management = createNapCatEndpointManagement(this);
18
+ #control = createRecallEndpointControl((id, signal) => this.#recallMessage(id, signal));
19
+ #content = createNapCatContentPort(request => this.requestApi(request));
21
20
  #routeReleases = [];
22
21
  #open = false;
23
22
  #started = false;
24
23
  #stopped = false;
25
24
  constructor(options) {
26
- super();
25
+ super({ config: options.config, httpAction: options.callHttpAction ?? callNapCatHttpAction, signal: options.signal });
27
26
  this.#logger = getAdapterLogger('napcat', options.config.id);
28
27
  this.#options = options;
29
- this.#callHttpAction = options.callHttpAction ?? callNapCatHttpAction;
28
+ const readTransportState = () => this.#transportState;
29
+ this[endpointSpec] = createEndpointSpecification(this, {
30
+ name: options.config.id,
31
+ start: () => this.#start(), open: () => this.#openEndpoint(),
32
+ close: () => this.#close(), stop: () => this.#stop(),
33
+ send: request => this.#send(request),
34
+ management: this.#management, control: this.#control, content: this.#content,
35
+ get transportState() { return readTransportState(); },
36
+ });
37
+ this.on('event', event => {
38
+ void this[endpointSpec].events.run(() => this.#admit(event)).catch((error) => {
39
+ this.#logger.debug(formatCompact({ op: 'napcat_receive_failed', error: error instanceof Error ? error.message : String(error) }));
40
+ });
41
+ });
30
42
  }
31
- get transportState() {
43
+ get #transportState() {
32
44
  return this.#stopped ? 'stopped' : this.#started ? 'open' : 'idle';
33
45
  }
34
- async start() {
46
+ async #start() {
35
47
  if (this.#started)
36
48
  return;
37
49
  this.#stopped = false;
@@ -44,13 +56,13 @@ export class NapCatHttpEndpoint extends Endpoint {
44
56
  path: this.#options.config.post_path,
45
57
  }));
46
58
  }
47
- open() {
59
+ #openEndpoint() {
48
60
  this.#open = true;
49
61
  }
50
- close() {
62
+ #close() {
51
63
  this.#open = false;
52
64
  }
53
- async stop() {
65
+ async #stop() {
54
66
  this.#stopped = true;
55
67
  this.#open = false;
56
68
  for (const release of this.#routeReleases.splice(0))
@@ -59,14 +71,10 @@ export class NapCatHttpEndpoint extends Endpoint {
59
71
  this.#started = false;
60
72
  this.#logger.debug(formatCompact({ op: 'disconnect' }));
61
73
  }
62
- async send({ conversation, payload }) {
74
+ async #send({ conversation, payload, signal }) {
63
75
  const message = formatOutboundSegments(payload);
64
76
  const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
65
- const resp = await this.#callHttpAction({
66
- http_url: this.#options.config.http_url,
67
- access_token: this.#options.config.access_token,
68
- }, action, params);
69
- const data = resp.data;
77
+ const data = await this.requestApi({ action, params, signal });
70
78
  const rawId = data?.message_id;
71
79
  if ((typeof rawId !== 'number' && typeof rawId !== 'string') || String(rawId).trim() === '' || (typeof rawId === 'number' && !Number.isFinite(rawId))) {
72
80
  throw new EndpointDeliveryError('delivery_unconfirmed', 'Message delivery is unconfirmed: platform returned no message_id', 'unknown');
@@ -80,28 +88,23 @@ export class NapCatHttpEndpoint extends Endpoint {
80
88
  }));
81
89
  return messageId;
82
90
  }
83
- async recallMessage(messageId) {
91
+ async #recallMessage(messageId, signal) {
84
92
  if (!messageId)
85
93
  return;
86
- await this.client.callApi('delete_msg', { message_id: Number(messageId) });
94
+ await this.requestApi({ action: 'delete_msg', params: { message_id: Number(messageId) }, signal });
87
95
  }
88
- #callApi(action, params = {}) {
89
- return this.#callHttpAction({
90
- http_url: this.#options.config.http_url,
91
- access_token: this.#options.config.access_token,
92
- }, action, params).then((resp) => resp.data);
93
- }
94
- admit(ev) {
95
- if (!this.#open)
96
+ #admit(ev) {
97
+ const nativeEvent = ev;
98
+ if (!this.#started)
96
99
  return;
97
- void this.emitPlatform(napCatPlatformEventName(ev), ev).catch((error) => {
100
+ void this[endpointSpec].events.platform(napCatPlatformEventName(ev), ev).catch((error) => {
98
101
  this.#logger.warn(formatCompact({
99
102
  op: 'napcat_platform_event_failed',
100
103
  error: error instanceof Error ? error.message : String(error),
101
104
  }));
102
105
  });
103
106
  if (!isMessageEvent(ev)) {
104
- receiveNapCatSideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, this.client, ev, this.#logger);
107
+ receiveNapCatSideEvent((name, payload) => this[endpointSpec].events.emit(name, payload), this.#options.config.id, this, ev, this.#logger);
105
108
  return;
106
109
  }
107
110
  if (isSelfMessage(ev))
@@ -112,10 +115,12 @@ export class NapCatHttpEndpoint extends Endpoint {
112
115
  if (Array.isArray(ev.message) || typeof ev.message === 'string') {
113
116
  ev = { ...ev, message: normalizeMessage(ev.message) };
114
117
  }
115
- const conversation = napcatInboundConversation(String(this.#options.id), ev);
118
+ const conversation = { ...napcatInboundConversation(String(this.#options.id), ev), endpoint: this[endpointSpec].identity };
116
119
  const nickname = senderNickname(ev);
117
120
  const mentioned = isNapCatBotMentioned(ev);
118
- void this.emit('message.receive', {
121
+ void this[endpointSpec].events.message({
122
+ event: nativeEvent,
123
+ ...(typeof nativeEvent.time === 'number' ? { platformSentAt: nativeEvent.time * 1000 } : {}),
119
124
  conversation,
120
125
  message: { conversation, id: msgId },
121
126
  content: formatInboundContent(ev),
@@ -166,8 +171,11 @@ export class NapCatHttpEndpoint extends Endpoint {
166
171
  response.end(JSON.stringify({ message: 'Invalid JSON' }));
167
172
  return;
168
173
  }
169
- if (this.#open)
170
- this.admit(ev);
174
+ if (this.#open) {
175
+ void this[endpointSpec].events.run(() => this.ingest(ev)).catch((error) => {
176
+ this.#logger.debug(formatCompact({ op: 'napcat_receive_failed', error: error instanceof Error ? error.message : String(error) }));
177
+ });
178
+ }
171
179
  response.writeHead(200, { 'Content-Type': 'application/json' });
172
180
  response.end(JSON.stringify({ status: 'ok' }));
173
181
  }
@@ -0,0 +1,3 @@
1
+ import { type NapCatWsConfig } from './protocol.js';
2
+ /** Separate read-only connection: never changes credentials or state of the running Endpoint. */
3
+ export declare function probeNapcatIdentity(config: NapCatWsConfig, signal: AbortSignal): Promise<string | undefined>;
@@ -0,0 +1,32 @@
1
+ import WebSocket from 'ws';
2
+ import { buildWsConnectOptions, readNapCatActionResponse } from './protocol.js';
3
+ import { createNapCatRpc } from './ws-transport.js';
4
+ /** Separate read-only connection: never changes credentials or state of the running Endpoint. */
5
+ export async function probeNapcatIdentity(config, signal) {
6
+ signal.throwIfAborted();
7
+ const { url, headers } = buildWsConnectOptions(config);
8
+ const socket = new WebSocket(url, { headers });
9
+ const rpc = createNapCatRpc({ endpointId: config.id, signal, socket: () => socket });
10
+ let rejectOpen = () => undefined;
11
+ const abort = () => { rejectOpen(signal.reason); rpc.disconnect(); socket.terminate(); };
12
+ signal.addEventListener('abort', abort, { once: true });
13
+ socket.on('message', data => rpc.receive(data, () => undefined));
14
+ socket.on('close', () => { rejectOpen(new Error('Identity connection closed')); rpc.disconnect(); });
15
+ try {
16
+ await new Promise((resolve, reject) => { rejectOpen = reject; socket.once('open', resolve); socket.on('error', reject); });
17
+ signal.throwIfAborted();
18
+ const response = await rpc.call({ action: 'get_login_info', signal });
19
+ readNapCatActionResponse(response);
20
+ signal.throwIfAborted();
21
+ const data = response.data;
22
+ if (!data || typeof data !== 'object' || !('user_id' in data)
23
+ || (typeof data.user_id !== 'string' && typeof data.user_id !== 'number') || !String(data.user_id).trim() || String(data.user_id) === '0')
24
+ return undefined;
25
+ return `qq:${data.user_id}`;
26
+ }
27
+ finally {
28
+ signal.removeEventListener('abort', abort);
29
+ rpc.dispose();
30
+ socket.terminate();
31
+ }
32
+ }
package/lib/index.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  export { buildSendAction, buildWsConnectOptions, callNapCatHttpAction, formatInboundContent, formatOutboundSegments, getChannelId, isMessageEvent, mediaRefToOneBotFile, napcatInboundConversation, napcatOutboundTarget, resolveNapCatConfig, senderNickname, senderUserId, type MessageSegment, type NapCatActionRequest, type NapCatActionResponse, type NapCatConfigBase, type NapCatEndpointConfig, type NapCatEvent, type NapCatHttpConfig, type NapCatMessageEvent, type NapCatSender, type NapCatWireSegment, type NapCatWsConfig, type NapCatWssConfig, type ParsedSendTarget, type ResolvedNapCatConfig, } from './protocol.js';
2
+ export { NapcatAdapter } from './adapter.js';
2
3
  export { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
3
- export { napcatClient, type NapcatClient, type NapcatClientEventMap, } from './client.js';
4
+ export { type NapcatClient, type NapcatClientEventMap, type NapcatClientOptions, type NapcatMemberTitleRequest, type NapcatProfileRequest, type NapcatGroupFileRequest, type NapcatMiniAppRequest, } from './client.js';
4
5
  export { parseOneBotGetMsgResponse } from './onebot-get-msg.js';
5
6
  export { NapCatWsEndpoint, type NapCatWsEndpointOptions, } from './ws-endpoint.js';
6
7
  export { NapCatWssEndpoint, type NapCatWssEndpointOptions, } from './wss-endpoint.js';
7
8
  export { NapCatHttpEndpoint, type NapCatHttpEndpointOptions, } from './http-endpoint.js';
8
9
  export type { NapCatWsSocket, NapCatWsCreateOptions } from './ws-types.js';
9
- export { callNapCatWsAction, decodeWsPayload, handleNapCatWsMessage, rejectAllPending, startNapCatHeartbeat, } from './ws-transport.js';
10
+ export { createNapCatRpc, decodeWsPayload, type NapCatRpc, type NapCatRpcOptions, } from './ws-transport.js';
10
11
  export { verifyNapCatAccessToken } from './wss-auth.js';
11
12
  export { readRequestBody } from './webhook.js';
package/lib/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  export { buildSendAction, buildWsConnectOptions, callNapCatHttpAction, formatInboundContent, formatOutboundSegments, getChannelId, isMessageEvent, mediaRefToOneBotFile, napcatInboundConversation, napcatOutboundTarget, resolveNapCatConfig, senderNickname, senderUserId, } from './protocol.js';
2
+ export { NapcatAdapter } from './adapter.js';
2
3
  export { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
3
- export { napcatClient, } from './client.js';
4
4
  export { parseOneBotGetMsgResponse } from './onebot-get-msg.js';
5
5
  export { NapCatWsEndpoint, } from './ws-endpoint.js';
6
6
  export { NapCatWssEndpoint, } from './wss-endpoint.js';
7
7
  export { NapCatHttpEndpoint, } from './http-endpoint.js';
8
- export { callNapCatWsAction, decodeWsPayload, handleNapCatWsMessage, rejectAllPending, startNapCatHeartbeat, } from './ws-transport.js';
8
+ export { createNapCatRpc, decodeWsPayload, } from './ws-transport.js';
9
9
  export { verifyNapCatAccessToken } from './wss-auth.js';
10
10
  export { readRequestBody } from './webhook.js';
@@ -12,5 +12,5 @@ export interface NapcatMessageLookup {
12
12
  time?: number;
13
13
  }
14
14
  export declare function parseOneBotGetMsgResponse(messageId: string, data: unknown): NapcatMessageLookup;
15
- export declare function createNapCatContentPort(callApi: (action: string, params?: Record<string, unknown>) => Promise<unknown>): EndpointContentPort;
16
- import type { EndpointContentPort } from 'zhin.js/adapter';
15
+ export declare function createNapCatContentPort(callApi: (request: PendingRpcRequest) => Promise<unknown>): EndpointContentPort;
16
+ import type { PendingRpcRequest, EndpointContentPort } from 'zhin.js/adapter';
@@ -35,7 +35,7 @@ export function createNapCatContentPort(callApi) {
35
35
  : Object.freeze({ status: 'resolved', reference, value: reference.media });
36
36
  }
37
37
  if (reference.kind === 'message') {
38
- const parsed = parseOneBotGetMsgResponse(reference.message.id, await callApi('get_msg', { message_id: reference.message.id }));
38
+ const parsed = parseOneBotGetMsgResponse(reference.message.id, await callApi({ action: 'get_msg', params: { message_id: reference.message.id }, signal: context.signal }));
39
39
  context.signal.throwIfAborted();
40
40
  const value = Object.freeze({
41
41
  ref: reference.message,
@@ -45,7 +45,7 @@ export function createNapCatContentPort(callApi) {
45
45
  });
46
46
  return Object.freeze({ status: 'resolved', reference, value });
47
47
  }
48
- const raw = await callApi('get_forward_msg', { message_id: reference.forwardId, id: reference.forwardId });
48
+ const raw = await callApi({ action: 'get_forward_msg', params: { message_id: reference.forwardId, id: reference.forwardId }, signal: context.signal });
49
49
  context.signal.throwIfAborted();
50
50
  const nodes = Array.isArray(raw.messages) ? raw.messages.slice(0, context.maxEntries) : [];
51
51
  const value = Object.freeze(nodes.map((node) => {
package/lib/protocol.d.ts CHANGED
@@ -88,6 +88,10 @@ export interface NapCatActionResponse {
88
88
  message?: string;
89
89
  wording?: string;
90
90
  }
91
+ /** OneBot 11: async/1 is submitted without a known result, not a rejection.
92
+ * https://github.com/botuniverse/onebot-11/blob/master/communication/http.md#响应
93
+ */
94
+ export declare function readNapCatActionResponse(value: unknown): NapCatActionResponse;
91
95
  export interface NapCatWireSegment {
92
96
  readonly type: string;
93
97
  readonly data?: Record<string, unknown>;
@@ -139,6 +143,7 @@ export declare function buildWsConnectOptions(config: NapCatWsConfig): {
139
143
  export interface NapCatHttpOptions {
140
144
  readonly http_url: string;
141
145
  readonly access_token?: string;
146
+ readonly signal?: AbortSignal;
142
147
  }
143
148
  /**
144
149
  * 向 NapCat HTTP API 发送动作请求:POST {http_url}/{action}。
package/lib/protocol.js CHANGED
@@ -6,6 +6,21 @@
6
6
  import { EndpointDeliveryError, isMediaRef } from '@zhin.js/im-contract';
7
7
  import { formatCompact, getLogger } from '@zhin.js/logger';
8
8
  const logger = getLogger('napcat');
9
+ /** OneBot 11: async/1 is submitted without a known result, not a rejection.
10
+ * https://github.com/botuniverse/onebot-11/blob/master/communication/http.md#响应
11
+ */
12
+ export function readNapCatActionResponse(value) {
13
+ if (!value || typeof value !== 'object' || !('status' in value) || !('retcode' in value)
14
+ || !Number.isInteger(value.retcode))
15
+ throw new EndpointDeliveryError('delivery_unconfirmed', 'NapCat returned an invalid action response', 'unknown');
16
+ const response = value;
17
+ if (response.status === 'failed' && response.retcode !== 0 && response.retcode !== 1) {
18
+ throw new EndpointDeliveryError('platform_rejected', `NapCat action failed [${response.retcode}]: ${response.message || response.wording || 'unknown'}`, 'rejected');
19
+ }
20
+ if (response.status !== 'ok' || response.retcode !== 0)
21
+ throw new EndpointDeliveryError('delivery_unconfirmed', 'NapCat did not confirm the action result', 'unknown');
22
+ return response;
23
+ }
9
24
  function normalizeConnection(connection) {
10
25
  if (connection === 'wss' || connection === 'http')
11
26
  return connection;
@@ -299,25 +314,22 @@ export async function callNapCatHttpAction(options, action, params = {}) {
299
314
  headers.Authorization = `Bearer ${options.access_token}`;
300
315
  }
301
316
  const res = await fetch(url, {
302
- signal: AbortSignal.timeout(30_000),
317
+ signal: options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(30_000)]) : AbortSignal.timeout(30_000),
303
318
  method: 'POST',
304
319
  headers,
305
320
  body: JSON.stringify(params),
306
321
  });
307
322
  const text = await res.text();
308
- if (res.status === 401)
309
- throw new EndpointDeliveryError('platform_rejected', `NapCat HTTP auth failed: ${text}`, 'rejected');
310
- if (res.status !== 200)
311
- throw new EndpointDeliveryError(res.status === 429 || res.status === 403 ? 'platform_rejected' : 'delivery_unconfirmed', `NapCat HTTP ${res.status}: ${text}`, res.status === 429 || res.status === 403 ? 'rejected' : 'unknown');
323
+ if (res.status !== 200) {
324
+ const rejected = [400, 401, 403, 404, 406, 429].includes(res.status);
325
+ throw new EndpointDeliveryError(rejected ? 'platform_rejected' : 'delivery_unconfirmed', `NapCat HTTP ${res.status}: ${text}`, rejected ? 'rejected' : 'unknown');
326
+ }
312
327
  let data;
313
328
  try {
314
329
  data = JSON.parse(text);
315
330
  }
316
- catch {
317
- throw new Error(`NapCat HTTP invalid response: ${text.slice(0, 200)}`);
318
- }
319
- if (data.status !== 'ok' && data.retcode !== 0) {
320
- throw new EndpointDeliveryError('platform_rejected', `NapCat HTTP action failed [${data.retcode}]: ${data.message || data.wording || 'unknown'}`, 'rejected');
331
+ catch (cause) {
332
+ throw new EndpointDeliveryError('delivery_unconfirmed', 'NapCat returned invalid JSON', 'unknown', { cause });
321
333
  }
322
- return data;
334
+ return readNapCatActionResponse(data);
323
335
  }
@@ -1,27 +1,17 @@
1
- import { Endpoint, type EndpointControl, type EndpointManagement, type EndpointSendRequest, type EndpointTransportState } from 'zhin.js/adapter';
1
+ import { endpointSpec, type Endpoint, type EndpointSpecification } from 'zhin.js/adapter';
2
2
  import type { CapabilityId } from 'zhin.js';
3
- import { type NapCatEvent, type NapCatWsConfig } from './protocol.js';
3
+ import { type NapCatWsConfig } from './protocol.js';
4
4
  import { type NapCatWsCreateOptions, type NapCatWsSocket } from './ws-types.js';
5
5
  import { NapcatClient } from './client.js';
6
6
  export interface NapCatWsEndpointOptions {
7
7
  readonly id: CapabilityId;
8
+ readonly signal?: AbortSignal;
8
9
  readonly config: NapCatWsConfig;
9
10
  readonly createWebSocket?: (url: string, options: NapCatWsCreateOptions) => NapCatWsSocket;
10
11
  }
11
- export declare class NapCatWsEndpoint extends Endpoint<NapcatClient> {
12
+ export declare class NapCatWsEndpoint extends NapcatClient implements Endpoint {
12
13
  #private;
13
- readonly client: NapcatClient;
14
- readonly management: EndpointManagement;
15
- readonly control: EndpointControl;
16
- readonly content: import("@zhin.js/adapter").EndpointContentPort;
14
+ readonly [endpointSpec]: EndpointSpecification;
17
15
  constructor(options: NapCatWsEndpointOptions);
18
- get transportState(): EndpointTransportState;
19
- start(): Promise<void>;
20
- open(): void;
21
- close(): void;
22
- stop(): Promise<void>;
23
- send({ conversation, payload }: EndpointSendRequest): Promise<string>;
24
- recallMessage(messageId: string): Promise<void>;
25
- /** Test / internal: admit a parsed event when the endpoint is open. */
26
- admit(ev: NapCatEvent): void;
16
+ protected get actionSocket(): NapCatWsSocket | undefined;
27
17
  }