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