@zhin.js/adapter-napcat 6.0.14 → 7.0.1

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 (60) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +18 -3
  3. package/adapters/napcat.js +2 -8
  4. package/adapters/napcat.ts +2 -8
  5. package/agent/tools/ai_tts.ts +4 -6
  6. package/agent/tools/del_group_notice.ts +4 -6
  7. package/agent/tools/delete_essence_msg.ts +4 -6
  8. package/agent/tools/delete_friend.ts +4 -6
  9. package/agent/tools/download_file.ts +4 -6
  10. package/agent/tools/forward_single_msg.ts +4 -8
  11. package/agent/tools/get_ai_characters.ts +4 -6
  12. package/agent/tools/get_essence_list.ts +4 -6
  13. package/agent/tools/get_friend_msg_history.ts +4 -6
  14. package/agent/tools/get_group_file_url.ts +4 -6
  15. package/agent/tools/get_group_info_ex.ts +4 -6
  16. package/agent/tools/get_group_msg_history.ts +4 -6
  17. package/agent/tools/get_group_notice.ts +4 -6
  18. package/agent/tools/get_group_root_files.ts +4 -6
  19. package/agent/tools/get_group_shut_list.ts +4 -6
  20. package/agent/tools/get_mini_app_ark.ts +4 -6
  21. package/agent/tools/get_user_status.ts +4 -6
  22. package/agent/tools/group_sign.ts +4 -6
  23. package/agent/tools/mark_msg_as_read.ts +4 -6
  24. package/agent/tools/ocr_image.ts +4 -6
  25. package/agent/tools/send_forward_msg.ts +4 -8
  26. package/agent/tools/send_group_notice.ts +4 -6
  27. package/agent/tools/send_like.ts +4 -6
  28. package/agent/tools/send_poke.ts +4 -6
  29. package/agent/tools/set_avatar.ts +4 -6
  30. package/agent/tools/set_emoji_reaction.ts +4 -6
  31. package/agent/tools/set_essence_msg.ts +4 -6
  32. package/agent/tools/set_group_portrait.ts +4 -6
  33. package/agent/tools/set_online_status.ts +4 -6
  34. package/agent/tools/set_profile.ts +4 -6
  35. package/agent/tools/set_signature.ts +4 -6
  36. package/agent/tools/set_title.ts +4 -6
  37. package/agent/tools/translate.ts +4 -6
  38. package/agent/tools/upload_group_file.ts +4 -6
  39. package/lib/{napcat-agent-deps.d.ts → client.d.ts} +15 -14
  40. package/lib/client.js +62 -0
  41. package/lib/http-endpoint.d.ts +6 -6
  42. package/lib/http-endpoint.js +24 -12
  43. package/lib/index.d.ts +1 -1
  44. package/lib/index.js +1 -1
  45. package/lib/napcat-endpoint-commands.d.ts +1 -1
  46. package/lib/side-event-dispatch.d.ts +2 -2
  47. package/lib/side-event-dispatch.js +3 -3
  48. package/lib/ws-endpoint.d.ts +6 -41
  49. package/lib/ws-endpoint.js +26 -157
  50. package/lib/wss-endpoint.d.ts +6 -6
  51. package/lib/wss-endpoint.js +25 -13
  52. package/package.json +15 -14
  53. package/src/client.ts +79 -0
  54. package/src/http-endpoint.ts +31 -20
  55. package/src/index.ts +4 -7
  56. package/src/side-event-dispatch.ts +4 -4
  57. package/src/ws-endpoint.ts +29 -202
  58. package/src/wss-endpoint.ts +32 -21
  59. package/lib/napcat-agent-deps.js +0 -33
  60. package/src/napcat-agent-deps.ts +0 -95
package/src/client.ts ADDED
@@ -0,0 +1,79 @@
1
+ import { defineEndpointClient } from 'zhin.js/adapter';
2
+ import type { NapCatEvent } from './protocol.js';
3
+
4
+ export type NapcatApiCall = (
5
+ action: string,
6
+ params?: Record<string, unknown>,
7
+ ) => Promise<unknown>;
8
+
9
+ /** Transport-independent NapCat protocol Client produced by every Endpoint mode. */
10
+ export class NapcatClient {
11
+ constructor(readonly callApi: NapcatApiCall) {}
12
+
13
+ async setTitle(groupId: number, userId: number, title: string, duration = -1): Promise<boolean> {
14
+ await this.callApi('set_group_special_title', {
15
+ group_id: groupId,
16
+ user_id: userId,
17
+ special_title: title,
18
+ duration,
19
+ });
20
+ return true;
21
+ }
22
+
23
+ sendLike(userId: number, times = 1) { return this.callApi('send_like', { user_id: userId, times }); }
24
+ deleteFriend(userId: number) { return this.callApi('delete_friend', { user_id: userId }); }
25
+ markMsgAsRead(messageId: number) { return this.callApi('mark_msg_as_read', { message_id: messageId }); }
26
+ ocrImage(image: string) { return this.callApi('ocr_image', { image }); }
27
+ setQQProfile(nickname: string, company?: string, email?: string, college?: string, personalNote?: string) {
28
+ return this.callApi('set_qq_profile', { nickname, company, email, college, personal_note: personalNote });
29
+ }
30
+ setGroupPortrait(groupId: number, file: string) { return this.callApi('set_group_portrait', { group_id: groupId, file }); }
31
+ setEssenceMsg(messageId: number) { return this.callApi('set_essence_msg', { message_id: messageId }); }
32
+ deleteEssenceMsg(messageId: number) { return this.callApi('delete_essence_msg', { message_id: messageId }); }
33
+ getEssenceMsgList(groupId: number) { return this.callApi('get_essence_msg_list', { group_id: groupId }); }
34
+ sendGroupSign(groupId: number) { return this.callApi('send_group_sign', { group_id: groupId }); }
35
+ sendGroupNotice(groupId: number, content: string, image?: string) { return this.callApi('_send_group_notice', { group_id: groupId, content, image }); }
36
+ getGroupNotice(groupId: number) { return this.callApi('_get_group_notice', { group_id: groupId }); }
37
+ deleteGroupNotice(groupId: number, noticeId: string) { return this.callApi('_del_group_notice', { group_id: groupId, notice_id: noticeId }); }
38
+ uploadGroupFile(groupId: number, file: string, name: string, folder?: string) { return this.callApi('upload_group_file', { group_id: groupId, file, name, folder }); }
39
+ getGroupRootFiles(groupId: number) { return this.callApi('get_group_root_files', { group_id: groupId }); }
40
+ getGroupFileUrl(groupId: number, fileId: string, busid: number) { return this.callApi('get_group_file_url', { group_id: groupId, file_id: fileId, busid }); }
41
+ downloadFile(url: string, threadCount = 1, headers?: string[]) { return this.callApi('download_file', { url, thread_count: threadCount, headers }); }
42
+ setOnlineStatus(status: number, extStatus: number) { return this.callApi('set_online_status', { status, ext_status: extStatus }); }
43
+ setQQAvatar(file: string) { return this.callApi('set_qq_avatar', { file }); }
44
+ forwardFriendSingleMsg(userId: number, messageId: number) { return this.callApi('forward_friend_single_msg', { user_id: userId, message_id: messageId }); }
45
+ forwardGroupSingleMsg(groupId: number, messageId: number) { return this.callApi('forward_group_single_msg', { group_id: groupId, message_id: messageId }); }
46
+ translateEn2Zh(sourceText: string) { return this.callApi('translate_en2zh', { source_text: sourceText }); }
47
+ setMsgEmojiLike(messageId: number, emojiId: string) { return this.callApi('set_msg_emoji_like', { message_id: messageId, emoji_id: emojiId }); }
48
+ sendForwardMsg(messageType: 'private' | 'group', id: number, messages: unknown[]) {
49
+ return this.callApi('send_forward_msg', {
50
+ message_type: messageType,
51
+ [messageType === 'group' ? 'group_id' : 'user_id']: id,
52
+ messages,
53
+ });
54
+ }
55
+ getFriendMsgHistory(userId: number, messageSeq?: number, count?: number) { return this.callApi('get_friend_msg_history', { user_id: userId, message_seq: messageSeq, count }); }
56
+ getGroupMsgHistory(groupId: number, messageSeq?: number, count?: number) { return this.callApi('get_group_msg_history', { group_id: groupId, message_seq: messageSeq, count }); }
57
+ setSelfLongnick(longnick: string) { return this.callApi('set_self_longnick', { longNick: longnick }); }
58
+ getGroupInfoEx(groupId: number) { return this.callApi('get_group_info_ex', { group_id: groupId }); }
59
+ sendPoke(userId: number, groupId?: number) { return this.callApi('send_poke', { user_id: userId, group_id: groupId }); }
60
+ ncGetUserStatus(userId: number) { return this.callApi('nc_get_user_status', { user_id: userId }); }
61
+ getGroupShutList(groupId: number) { return this.callApi('get_group_shut_list', { group_id: groupId }); }
62
+ getMiniAppArk(type: string, title: string, desc: string, picUrl: string, jumpUrl: string) { return this.callApi('get_mini_app_ark', { type, title, desc, picUrl, jumpUrl }); }
63
+ getAiCharacters(groupId: number) { return this.callApi('get_ai_characters', { group_id: groupId }); }
64
+ sendGroupAiRecord(groupId: number, characterId: string, text: string) {
65
+ return this.callApi('send_group_ai_record', { group_id: groupId, character: characterId, text });
66
+ }
67
+ }
68
+ export type NapcatClientEventMap = Record<string, NapCatEvent>;
69
+
70
+ declare module '@zhin.js/feature-kit' {
71
+ interface AdapterClientRegistry {
72
+ readonly napcat: {
73
+ readonly client: NapcatClient;
74
+ readonly events: NapcatClientEventMap;
75
+ };
76
+ }
77
+ }
78
+
79
+ export const napcatClient = defineEndpointClient<NapcatClient, NapcatClientEventMap>('napcat');
@@ -1,14 +1,18 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
1
2
  /**
2
3
  * NapCat HTTP endpoint — POST inbound events + HTTP API outbound.
3
4
  */
4
5
  import type { IncomingMessage, ServerResponse } from 'node:http';
5
- import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
6
- import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
6
+ import {
7
+ createRecallEndpointControl,
8
+ type EndpointControl,
9
+ type EndpointManagement,
10
+ type EndpointSendRequest,
11
+ } from 'zhin.js/adapter';
7
12
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
8
13
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
9
14
  import type { CapabilityId } from 'zhin.js';
10
15
  import { createNapCatEndpointManagement } from './endpoint-management.js';
11
- import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
12
16
  import {
13
17
  InboundMessageDeduper,
14
18
  isNapCatBotMentioned,
@@ -30,33 +34,34 @@ import {
30
34
  } from './protocol.js';
31
35
  import { receiveNapCatSideEvent } from './side-event-dispatch.js';
32
36
  import { createNapCatContentPort } from './onebot-get-msg.js';
37
+ import { NapcatClient } from './client.js';
33
38
  import { readRequestBody } from './webhook.js';
34
39
  import { NapCatWsEndpoint } from './ws-endpoint.js';
35
40
  import { verifyNapCatAccessToken } from './wss-auth.js';
36
41
 
37
42
  export interface NapCatHttpEndpointOptions {
38
43
  readonly id: CapabilityId;
39
- readonly gateway: MessageGateway;
40
- readonly sideEvents?: SideEventGateway;
41
44
  readonly http: HttpHost;
42
45
  readonly config: NapCatHttpConfig;
43
46
  readonly callHttpAction?: typeof callNapCatHttpAction;
44
47
  }
45
48
 
46
- export class NapCatHttpEndpoint implements EndpointInstance {
49
+ export class NapCatHttpEndpoint extends Endpoint<NapcatClient> {
50
+ readonly client = new NapcatClient((action, params) => this.#callApi(action, params));
47
51
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
48
52
 
49
53
  readonly #options: NapCatHttpEndpointOptions;
50
54
  readonly #inboundDeduper = new InboundMessageDeduper();
51
- readonly management: EndpointManagement = createNapCatEndpointManagement(this);
52
- readonly content = createNapCatContentPort((action, params) => this.callApi(action, params));
55
+ readonly management: EndpointManagement = createNapCatEndpointManagement(this.client);
56
+ readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
57
+ readonly content = createNapCatContentPort((action, params) => this.client.callApi(action, params));
53
58
  readonly #callHttpAction: typeof callNapCatHttpAction;
54
59
  #routeReleases: HttpRouteRegistration[] = [];
55
60
  #open = false;
56
61
  #started = false;
57
- #unregisterAgent?: () => void;
58
62
 
59
63
  constructor(options: NapCatHttpEndpointOptions) {
64
+ super();
60
65
  this.#logger = getAdapterLogger('napcat', options.config.id);
61
66
  this.#options = options;
62
67
  this.#callHttpAction = options.callHttpAction ?? callNapCatHttpAction;
@@ -65,10 +70,6 @@ export class NapCatHttpEndpoint implements EndpointInstance {
65
70
  async start(): Promise<void> {
66
71
  if (this.#started) return;
67
72
  this.#started = true;
68
- this.#unregisterAgent = registerNapcatAgentEndpoint(
69
- this.#options.config.id,
70
- this as unknown as NapCatWsEndpoint,
71
- );
72
73
  this.#setupRoutes();
73
74
  this.#logger.info(formatCompact({
74
75
  op: 'listen',
@@ -89,8 +90,6 @@ export class NapCatHttpEndpoint implements EndpointInstance {
89
90
  async stop(): Promise<void> {
90
91
  this.#open = false;
91
92
  for (const release of this.#routeReleases.splice(0)) release();
92
- this.#unregisterAgent?.();
93
- this.#unregisterAgent = undefined;
94
93
  this.#inboundDeduper.clear();
95
94
  this.#started = false;
96
95
  this.#logger.debug(formatCompact({ op: 'disconnect' }));
@@ -120,10 +119,10 @@ export class NapCatHttpEndpoint implements EndpointInstance {
120
119
 
121
120
  async recallMessage(messageId: string): Promise<void> {
122
121
  if (!messageId) return;
123
- await this.callApi('delete_msg', { message_id: Number(messageId) });
122
+ await this.client.callApi('delete_msg', { message_id: Number(messageId) });
124
123
  }
125
124
 
126
- callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
125
+ #callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
127
126
  return this.#callHttpAction(
128
127
  {
129
128
  http_url: this.#options.config.http_url,
@@ -136,11 +135,17 @@ export class NapCatHttpEndpoint implements EndpointInstance {
136
135
 
137
136
  admit(ev: NapCatEvent): void {
138
137
  if (!this.#open) return;
138
+ void this.emitPlatform(napCatPlatformEventName(ev), ev).catch((error) => {
139
+ this.#logger.warn(formatCompact({
140
+ op: 'napcat_platform_event_failed',
141
+ error: error instanceof Error ? error.message : String(error),
142
+ }));
143
+ });
139
144
  if (!isMessageEvent(ev)) {
140
145
  receiveNapCatSideEvent(
141
- this.#options.sideEvents,
146
+ (name, payload) => this.emit(name, payload),
142
147
  this.#options.config.id,
143
- this,
148
+ this.client,
144
149
  ev,
145
150
  this.#logger,
146
151
  );
@@ -155,7 +160,7 @@ export class NapCatHttpEndpoint implements EndpointInstance {
155
160
  const conversation = napcatInboundConversation(String(this.#options.id), ev);
156
161
  const nickname = senderNickname(ev);
157
162
  const mentioned = isNapCatBotMentioned(ev);
158
- void this.#options.gateway.receive({
163
+ void this.emit('message.receive', {
159
164
  conversation,
160
165
  message: { conversation, id: msgId },
161
166
  content: formatInboundContent(ev),
@@ -221,3 +226,9 @@ export class NapCatHttpEndpoint implements EndpointInstance {
221
226
  }
222
227
  }
223
228
  }
229
+
230
+ function napCatPlatformEventName(ev: NapCatEvent): string {
231
+ return [ev.post_type, ev.message_type ?? ev.notice_type ?? ev.request_type, ev.sub_type]
232
+ .filter(Boolean)
233
+ .join('.');
234
+ }
package/src/index.ts CHANGED
@@ -37,13 +37,10 @@ export {
37
37
  } from './napcat-inbound.js';
38
38
 
39
39
  export {
40
- getEndpoint,
41
- getNapcatAgentDeps,
42
- registerNapcatAgentEndpoint,
43
- setNapcatAgentDeps,
44
- type NapcatAgentDeps,
45
- type NapcatAgentEndpoint,
46
- } from './napcat-agent-deps.js';
40
+ napcatClient,
41
+ type NapcatClient,
42
+ type NapcatClientEventMap,
43
+ } from './client.js';
47
44
 
48
45
  export { parseOneBotGetMsgResponse } from './onebot-get-msg.js';
49
46
 
@@ -1,5 +1,5 @@
1
1
  import { receiveOneBotLikeSideEvent } from '@zhin.js/core';
2
- import type { SideEventGateway } from '@zhin.js/core/runtime';
2
+ import type { EndpointEventEmitter } from 'zhin.js/adapter';
3
3
  import { formatCompact, type getAdapterLogger } from '@zhin.js/logger';
4
4
  import type { NapCatEvent } from './protocol.js';
5
5
 
@@ -8,19 +8,19 @@ export interface NapCatSideEventCaller {
8
8
  }
9
9
 
10
10
  export function receiveNapCatSideEvent(
11
- sideEvents: SideEventGateway | undefined,
11
+ emit: EndpointEventEmitter,
12
12
  endpointKey: string,
13
13
  caller: NapCatSideEventCaller,
14
14
  raw: NapCatEvent,
15
15
  logger: ReturnType<typeof getAdapterLogger>,
16
16
  ): void {
17
- if (!sideEvents) return;
17
+ if (!emit) return;
18
18
  const record = raw as Record<string, unknown>;
19
19
  const postType = String(record.post_type ?? '');
20
20
  const requestType = String(record.request_type ?? '');
21
21
  const isRequest = postType === 'request' || postType.startsWith('request.');
22
22
  const isFriend = requestType === 'friend' || postType.includes('friend');
23
- void receiveOneBotLikeSideEvent(sideEvents, {
23
+ void receiveOneBotLikeSideEvent(emit, {
24
24
  adapter: 'napcat',
25
25
  endpointKey,
26
26
  platform: 'napcat',
@@ -1,20 +1,20 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
1
2
  /**
2
3
  * NapCat WS client endpoint — outbound connect to NapCat.
3
4
  */
4
5
  import WebSocket from 'ws';
5
6
  import {
7
+ createRecallEndpointControl,
6
8
  createEndpointLifecycle,
7
9
  type EndpointConnectHandle,
8
- type EndpointInstance,
10
+ type EndpointControl,
9
11
  type EndpointLifecycle,
10
12
  type EndpointManagement,
11
13
  type EndpointSendRequest,
12
14
  } from 'zhin.js/adapter';
13
- import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
14
15
  import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
15
16
  import type { CapabilityId } from 'zhin.js';
16
17
  import { createNapCatEndpointManagement } from './endpoint-management.js';
17
- import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
18
18
  import {
19
19
  InboundMessageDeduper,
20
20
  isNapCatBotMentioned,
@@ -46,11 +46,10 @@ import {
46
46
  type NapCatWsSocket,
47
47
  } from './ws-types.js';
48
48
  import { createNapCatContentPort } from './onebot-get-msg.js';
49
+ import { NapcatClient } from './client.js';
49
50
 
50
51
  export interface NapCatWsEndpointOptions {
51
52
  readonly id: CapabilityId;
52
- readonly gateway: MessageGateway;
53
- readonly sideEvents?: SideEventGateway;
54
53
  readonly config: NapCatWsConfig;
55
54
  readonly createWebSocket?: (
56
55
  url: string,
@@ -58,21 +57,23 @@ export interface NapCatWsEndpointOptions {
58
57
  ) => NapCatWsSocket;
59
58
  }
60
59
 
61
- export class NapCatWsEndpoint implements EndpointInstance {
60
+ export class NapCatWsEndpoint extends Endpoint<NapcatClient> {
61
+ readonly client = new NapcatClient((action, params) => this.#callApi(action, params));
62
62
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
63
63
 
64
64
  readonly #options: NapCatWsEndpointOptions;
65
65
  readonly #inboundDeduper = new InboundMessageDeduper();
66
- readonly management: EndpointManagement = createNapCatEndpointManagement(this);
67
- readonly content = createNapCatContentPort((action, params) => this.callApi(action, params));
66
+ readonly management: EndpointManagement = createNapCatEndpointManagement(this.client);
67
+ readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
68
+ readonly content = createNapCatContentPort((action, params) => this.client.callApi(action, params));
68
69
  readonly #lifecycle: EndpointLifecycle;
69
70
  #ws?: NapCatWsSocket;
70
71
  #requestId = { value: 0 };
71
72
  #pending = new Map<string, NapCatPendingAction>();
72
73
  #open = false;
73
- #unregisterAgent?: () => void;
74
74
 
75
75
  constructor(options: NapCatWsEndpointOptions) {
76
+ super();
76
77
  this.#logger = getAdapterLogger('napcat', options.config.id);
77
78
  this.#options = options;
78
79
  this.#lifecycle = createEndpointLifecycle({
@@ -89,15 +90,7 @@ export class NapCatWsEndpoint implements EndpointInstance {
89
90
 
90
91
  async start(): Promise<void> {
91
92
  if (this.#lifecycle.started) return;
92
- this.#unregisterAgent = registerNapcatAgentEndpoint(this.#options.config.id, this);
93
- try {
94
- await this.#lifecycle.start((handle) => this.#connect(handle));
95
- } catch (err) {
96
- // start 失败复位由基座保证;agent 注册/反注册是适配器专有依赖,留在适配器侧
97
- this.#unregisterAgent?.();
98
- this.#unregisterAgent = undefined;
99
- throw err;
100
- }
93
+ await this.#lifecycle.start((handle) => this.#connect(handle));
101
94
  }
102
95
 
103
96
  open(): void {
@@ -111,8 +104,6 @@ export class NapCatWsEndpoint implements EndpointInstance {
111
104
  async stop(): Promise<void> {
112
105
  this.#open = false;
113
106
  await this.#lifecycle.stop();
114
- this.#unregisterAgent?.();
115
- this.#unregisterAgent = undefined;
116
107
  rejectAllPending(this.#pending);
117
108
  this.#inboundDeduper.clear();
118
109
  if (this.#ws) {
@@ -128,7 +119,7 @@ export class NapCatWsEndpoint implements EndpointInstance {
128
119
  async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
129
120
  const message = formatOutboundSegments(payload);
130
121
  const { action, params } = buildSendAction(napcatOutboundTarget(conversation), message);
131
- const data = await this.callApi(action, params) as { message_id?: number | string } | undefined;
122
+ const data = await this.client.callApi(action, params) as { message_id?: number | string } | undefined;
132
123
  const messageId = data?.message_id != null ? String(data.message_id) : '';
133
124
  this.#logger.debug(formatCompact({
134
125
  op: 'napcat_send',
@@ -141,197 +132,27 @@ export class NapCatWsEndpoint implements EndpointInstance {
141
132
 
142
133
  async recallMessage(messageId: string): Promise<void> {
143
134
  if (!messageId) return;
144
- await this.callApi('delete_msg', { message_id: Number(messageId) });
135
+ await this.client.callApi('delete_msg', { message_id: Number(messageId) });
145
136
  }
146
137
 
147
- callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
138
+ #callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
148
139
  return callNapCatWsAction(this.#ws, this.#pending, this.#requestId, action, params);
149
140
  }
150
141
 
151
- // ── Agent-facing API wrappers ─────────────────────────────────────
152
-
153
- async setTitle(groupId: number, userId: number, title: string, duration = -1): Promise<boolean> {
154
- await this.callApi('set_group_special_title', {
155
- group_id: groupId,
156
- user_id: userId,
157
- special_title: title,
158
- duration,
159
- });
160
- return true;
161
- }
162
-
163
- sendLike(userId: number, times = 1) {
164
- return this.callApi('send_like', { user_id: userId, times });
165
- }
166
-
167
- deleteFriend(userId: number) {
168
- return this.callApi('delete_friend', { user_id: userId });
169
- }
170
-
171
- markMsgAsRead(messageId: number) {
172
- return this.callApi('mark_msg_as_read', { message_id: messageId });
173
- }
174
-
175
- ocrImage(image: string) {
176
- return this.callApi('ocr_image', { image });
177
- }
178
-
179
- setQQProfile(
180
- nickname: string,
181
- company?: string,
182
- email?: string,
183
- college?: string,
184
- personalNote?: string,
185
- ) {
186
- return this.callApi('set_qq_profile', {
187
- nickname,
188
- company,
189
- email,
190
- college,
191
- personal_note: personalNote,
192
- });
193
- }
194
-
195
- setGroupPortrait(groupId: number, file: string) {
196
- return this.callApi('set_group_portrait', { group_id: groupId, file });
197
- }
198
-
199
- setEssenceMsg(messageId: number) {
200
- return this.callApi('set_essence_msg', { message_id: messageId });
201
- }
202
-
203
- deleteEssenceMsg(messageId: number) {
204
- return this.callApi('delete_essence_msg', { message_id: messageId });
205
- }
206
-
207
- getEssenceMsgList(groupId: number) {
208
- return this.callApi('get_essence_msg_list', { group_id: groupId });
209
- }
210
-
211
- sendGroupSign(groupId: number) {
212
- return this.callApi('send_group_sign', { group_id: groupId });
213
- }
214
-
215
- sendGroupNotice(groupId: number, content: string, image?: string) {
216
- return this.callApi('_send_group_notice', { group_id: groupId, content, image });
217
- }
218
-
219
- getGroupNotice(groupId: number) {
220
- return this.callApi('_get_group_notice', { group_id: groupId });
221
- }
222
-
223
- deleteGroupNotice(groupId: number, noticeId: string) {
224
- return this.callApi('_del_group_notice', { group_id: groupId, notice_id: noticeId });
225
- }
226
-
227
- uploadGroupFile(groupId: number, file: string, name: string, folder?: string) {
228
- return this.callApi('upload_group_file', { group_id: groupId, file, name, folder });
229
- }
230
-
231
- getGroupRootFiles(groupId: number) {
232
- return this.callApi('get_group_root_files', { group_id: groupId });
233
- }
234
-
235
- getGroupFileUrl(groupId: number, fileId: string, busid: number) {
236
- return this.callApi('get_group_file_url', { group_id: groupId, file_id: fileId, busid });
237
- }
238
-
239
- downloadFile(url: string, threadCount = 1, headers?: string[]) {
240
- return this.callApi('download_file', { url, thread_count: threadCount, headers });
241
- }
242
-
243
- setOnlineStatus(status: number, extStatus: number) {
244
- return this.callApi('set_online_status', { status, ext_status: extStatus });
245
- }
246
-
247
- setQQAvatar(file: string) {
248
- return this.callApi('set_qq_avatar', { file });
249
- }
250
-
251
- forwardFriendSingleMsg(userId: number, messageId: number) {
252
- return this.callApi('forward_friend_single_msg', { user_id: userId, message_id: messageId });
253
- }
254
-
255
- forwardGroupSingleMsg(groupId: number, messageId: number) {
256
- return this.callApi('forward_group_single_msg', { group_id: groupId, message_id: messageId });
257
- }
258
-
259
- translateEn2Zh(sourceText: string) {
260
- return this.callApi('translate_en2zh', { source_text: sourceText });
261
- }
262
-
263
- setMsgEmojiLike(messageId: number, emojiId: string) {
264
- return this.callApi('set_msg_emoji_like', { message_id: messageId, emoji_id: emojiId });
265
- }
266
-
267
- sendForwardMsg(messageType: 'private' | 'group', id: number, messages: unknown[]) {
268
- return this.callApi('send_forward_msg', {
269
- message_type: messageType,
270
- [messageType === 'group' ? 'group_id' : 'user_id']: id,
271
- messages,
272
- });
273
- }
274
-
275
- getFriendMsgHistory(userId: number, messageSeq?: number, count?: number) {
276
- return this.callApi('get_friend_msg_history', {
277
- user_id: userId,
278
- message_seq: messageSeq,
279
- count,
280
- });
281
- }
282
-
283
- getGroupMsgHistory(groupId: number, messageSeq?: number, count?: number) {
284
- return this.callApi('get_group_msg_history', {
285
- group_id: groupId,
286
- message_seq: messageSeq,
287
- count,
288
- });
289
- }
290
-
291
- setSelfLongnick(longnick: string) {
292
- return this.callApi('set_self_longnick', { longNick: longnick });
293
- }
294
-
295
- getGroupInfoEx(groupId: number) {
296
- return this.callApi('get_group_info_ex', { group_id: groupId });
297
- }
298
-
299
- sendPoke(userId: number, groupId?: number) {
300
- return this.callApi('send_poke', { user_id: userId, group_id: groupId });
301
- }
302
-
303
- ncGetUserStatus(userId: number) {
304
- return this.callApi('nc_get_user_status', { user_id: userId });
305
- }
306
-
307
- getGroupShutList(groupId: number) {
308
- return this.callApi('get_group_shut_list', { group_id: groupId });
309
- }
310
-
311
- getMiniAppArk(type: string, title: string, desc: string, picUrl: string, jumpUrl: string) {
312
- return this.callApi('get_mini_app_ark', { type, title, desc, picUrl, jumpUrl });
313
- }
314
-
315
- getAiCharacters(groupId: number) {
316
- return this.callApi('get_ai_characters', { group_id: groupId });
317
- }
318
-
319
- sendGroupAiRecord(groupId: number, characterId: string, text: string) {
320
- return this.callApi('send_group_ai_record', {
321
- group_id: groupId,
322
- character: characterId,
323
- text,
324
- });
325
- }
326
-
327
142
  /** Test / internal: admit a parsed event when the endpoint is open. */
328
143
  admit(ev: NapCatEvent): void {
329
144
  if (!this.#open) return;
145
+ void this.emitPlatform(napCatPlatformEventName(ev), ev).catch((error) => {
146
+ this.#logger.warn(formatCompact({
147
+ op: 'napcat_platform_event_failed',
148
+ error: error instanceof Error ? error.message : String(error),
149
+ }));
150
+ });
330
151
  if (!isMessageEvent(ev)) {
331
152
  receiveNapCatSideEvent(
332
- this.#options.sideEvents,
153
+ (name, payload) => this.emit(name, payload),
333
154
  this.#options.config.id,
334
- this,
155
+ this.client,
335
156
  ev,
336
157
  this.#logger,
337
158
  );
@@ -347,7 +168,7 @@ export class NapCatWsEndpoint implements EndpointInstance {
347
168
  const content = formatInboundContent(ev);
348
169
  const nickname = senderNickname(ev);
349
170
  const mentioned = isNapCatBotMentioned(ev);
350
- void this.#options.gateway.receive({
171
+ void this.emit('message.receive', {
351
172
  conversation,
352
173
  message: { conversation, id: msgId },
353
174
  content,
@@ -472,3 +293,9 @@ export class NapCatWsEndpoint implements EndpointInstance {
472
293
  });
473
294
  }
474
295
  }
296
+
297
+ function napCatPlatformEventName(ev: NapCatEvent): string {
298
+ return [ev.post_type, ev.message_type ?? ev.notice_type ?? ev.request_type, ev.sub_type]
299
+ .filter(Boolean)
300
+ .join('.');
301
+ }