@zhin.js/adapter-napcat 6.0.13 → 7.0.0
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 +48 -0
- package/README.md +1 -1
- package/adapters/napcat.js +18 -4
- package/adapters/napcat.ts +24 -4
- package/lib/http-endpoint.d.ts +5 -2
- package/lib/http-endpoint.js +10 -1
- package/lib/onebot-get-msg.d.ts +4 -2
- package/lib/onebot-get-msg.js +53 -0
- package/lib/protocol.js +4 -4
- package/lib/side-event-dispatch.d.ts +7 -0
- package/lib/side-event-dispatch.js +36 -0
- package/lib/ws-endpoint.d.ts +5 -2
- package/lib/ws-endpoint.js +10 -2
- package/lib/wss-endpoint.d.ts +5 -2
- package/lib/wss-endpoint.js +10 -1
- package/package.json +14 -14
- package/src/http-endpoint.ts +24 -3
- package/src/onebot-get-msg.ts +63 -5
- package/src/protocol.ts +4 -3
- package/src/side-event-dispatch.ts +48 -0
- package/src/ws-endpoint.ts +19 -2
- package/src/wss-endpoint.ts +24 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @zhin.js/adapter-napcat
|
|
2
2
|
|
|
3
|
+
## 7.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f2c532f: Expose exact per-Endpoint message operations through one validated Adapter capability model, route Core control calls through declared active capabilities, and connect existing recall, edit, reaction, and typing implementations across platform adapters.
|
|
8
|
+
- Updated dependencies [b10d058]
|
|
9
|
+
- Updated dependencies [f2c532f]
|
|
10
|
+
- Updated dependencies [3dbf990]
|
|
11
|
+
- @zhin.js/host-http@1.0.12
|
|
12
|
+
- @zhin.js/adapter@1.2.0
|
|
13
|
+
- @zhin.js/core@1.5.13
|
|
14
|
+
- @zhin.js/agent@1.1.15
|
|
15
|
+
- zhin.js@6.0.13
|
|
16
|
+
|
|
17
|
+
## 6.0.14
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- fccdbd7: Add canonical scoped message and merged-forward resolution through NapCat APIs.
|
|
22
|
+
- 5969c5b: Add SideEventGateway so adapters forward notice/request/system into HandlerIndex. HandlerContext now exposes only generation-safe capabilities and prompt ports; live Endpoint escape hatches are removed.
|
|
23
|
+
- Updated dependencies [5969c5b]
|
|
24
|
+
- Updated dependencies [d336a3f]
|
|
25
|
+
- Updated dependencies [0c82a7e]
|
|
26
|
+
- Updated dependencies [b9217e4]
|
|
27
|
+
- Updated dependencies [5969c5b]
|
|
28
|
+
- Updated dependencies [5969c5b]
|
|
29
|
+
- Updated dependencies [974772e]
|
|
30
|
+
- Updated dependencies [5969c5b]
|
|
31
|
+
- Updated dependencies [5969c5b]
|
|
32
|
+
- Updated dependencies [2f786bd]
|
|
33
|
+
- Updated dependencies [63d89f9]
|
|
34
|
+
- Updated dependencies [71c7cdd]
|
|
35
|
+
- Updated dependencies [3cca0ea]
|
|
36
|
+
- Updated dependencies [1312ca0]
|
|
37
|
+
- Updated dependencies [985fa22]
|
|
38
|
+
- Updated dependencies [04b861d]
|
|
39
|
+
- Updated dependencies [a23d544]
|
|
40
|
+
- Updated dependencies [8cddabf]
|
|
41
|
+
- Updated dependencies [dbe5081]
|
|
42
|
+
- @zhin.js/im-contract@1.0.4
|
|
43
|
+
- @zhin.js/core@1.5.12
|
|
44
|
+
- @zhin.js/adapter@1.1.11
|
|
45
|
+
- @zhin.js/agent@1.1.14
|
|
46
|
+
- @zhin.js/host-http@1.0.11
|
|
47
|
+
- @zhin.js/command@1.0.15
|
|
48
|
+
- zhin.js@6.0.12
|
|
49
|
+
- @zhin.js/permission@1.0.3
|
|
50
|
+
|
|
3
51
|
## 6.0.13
|
|
4
52
|
|
|
5
53
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ plugins:
|
|
|
67
67
|
|
|
68
68
|
## 迁移说明(Plugin Runtime)
|
|
69
69
|
|
|
70
|
-
- **notice / request
|
|
70
|
+
- **notice / request / meta 侧事件**:经 `sideEventGatewayToken` 归一后分发到 `handlers`(`notice.receive` / `request.receive` / `system.receive`),请求带 `$approve` / `$reject`。消息仍走 `messageGatewayToken`。
|
|
71
71
|
- **群管工具暂未迁移**:旧 Adapter 经 `createSceneManagementTools` 注册踢人 / 禁言 / 群名片等成套 agent 工具;迁移后 `agent/tools/` 仅覆盖 NapCat 扩展 API,其余群管能力可通过 `callApi`(如 `set_group_kick`、`set_group_ban`)作为逃生舱调用。
|
|
72
72
|
- **平台权限门禁**:`plugin.ts` setup 已注册 `registerDefaultScenePlatformPermitChecker('napcat')`,`scene_admin` / `scene_owner` 依据入站 metadata 中的 sender `role`(owner / admin)判定。
|
|
73
73
|
|
package/adapters/napcat.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Convention entry: discover `adapters/napcat.ts` → defineAdapter.
|
|
4
4
|
*/
|
|
5
5
|
import { defineAdapter } from 'zhin.js/adapter';
|
|
6
|
-
import { messageGatewayToken } from '@zhin.js/core/runtime';
|
|
6
|
+
import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
|
|
7
7
|
import { httpHostToken } from '@zhin.js/host-http';
|
|
8
8
|
import { NapCatHttpEndpoint } from "../lib/http-endpoint.js";
|
|
9
9
|
import { resolveNapCatConfig } from "../lib/protocol.js";
|
|
@@ -15,6 +15,7 @@ export { NapCatWsEndpoint, } from "../lib/ws-endpoint.js";
|
|
|
15
15
|
export { NapCatWssEndpoint, } from "../lib/wss-endpoint.js";
|
|
16
16
|
export default defineAdapter({
|
|
17
17
|
capabilities: ['inbound', 'outbound'],
|
|
18
|
+
operations: ['recall'],
|
|
18
19
|
// OneBot file 参数原生消费 url / base64:// 媒体,file:// 本地路径由 NapCat 侧读盘;
|
|
19
20
|
// 无卡片交互面,交互段降级纯文本。
|
|
20
21
|
segments: {
|
|
@@ -24,17 +25,30 @@ export default defineAdapter({
|
|
|
24
25
|
create(context) {
|
|
25
26
|
const config = resolveNapCatConfig(context.config);
|
|
26
27
|
const gateway = context.use(messageGatewayToken);
|
|
28
|
+
const sideEvents = context.use(sideEventGatewayToken);
|
|
27
29
|
// 注册到插件运行时状态(napcat.endpoint list 的"运行中"数据源)
|
|
28
30
|
context.use(napcatRuntimeStateToken).endpoints.set(config.id, {
|
|
29
31
|
id: config.id,
|
|
30
32
|
mode: config.connection,
|
|
31
33
|
});
|
|
32
34
|
if (config.connection === 'wss') {
|
|
33
|
-
return new NapCatWssEndpoint({
|
|
35
|
+
return new NapCatWssEndpoint({
|
|
36
|
+
id: context.id,
|
|
37
|
+
gateway,
|
|
38
|
+
sideEvents,
|
|
39
|
+
http: context.use(httpHostToken),
|
|
40
|
+
config,
|
|
41
|
+
});
|
|
34
42
|
}
|
|
35
43
|
if (config.connection === 'http') {
|
|
36
|
-
return new NapCatHttpEndpoint({
|
|
44
|
+
return new NapCatHttpEndpoint({
|
|
45
|
+
id: context.id,
|
|
46
|
+
gateway,
|
|
47
|
+
sideEvents,
|
|
48
|
+
http: context.use(httpHostToken),
|
|
49
|
+
config,
|
|
50
|
+
});
|
|
37
51
|
}
|
|
38
|
-
return new NapCatWsEndpoint({ id: context.id, gateway, config });
|
|
52
|
+
return new NapCatWsEndpoint({ id: context.id, gateway, sideEvents, config });
|
|
39
53
|
},
|
|
40
54
|
});
|
package/adapters/napcat.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Convention entry: discover `adapters/napcat.ts` → defineAdapter.
|
|
3
3
|
*/
|
|
4
4
|
import { defineAdapter } from 'zhin.js/adapter';
|
|
5
|
-
import { messageGatewayToken } from '@zhin.js/core/runtime';
|
|
5
|
+
import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
|
|
6
6
|
import { httpHostToken } from '@zhin.js/host-http';
|
|
7
7
|
import { NapCatHttpEndpoint } from '../src/http-endpoint.js';
|
|
8
8
|
import { resolveNapCatConfig, type NapCatAdapterConfig } from '../src/protocol.js';
|
|
@@ -24,8 +24,15 @@ export {
|
|
|
24
24
|
} from '../src/wss-endpoint.js';
|
|
25
25
|
export type { NapCatWsSocket, NapCatWsCreateOptions } from '../src/ws-types.js';
|
|
26
26
|
|
|
27
|
+
declare module '@zhin.js/core' {
|
|
28
|
+
interface AdapterEndpoints {
|
|
29
|
+
napcat: import('../src/ws-endpoint.js').NapCatWsEndpoint;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
export default defineAdapter<NapCatAdapterConfig>({
|
|
28
34
|
capabilities: ['inbound', 'outbound'],
|
|
35
|
+
operations: ['recall'],
|
|
29
36
|
// OneBot file 参数原生消费 url / base64:// 媒体,file:// 本地路径由 NapCat 侧读盘;
|
|
30
37
|
// 无卡片交互面,交互段降级纯文本。
|
|
31
38
|
segments: {
|
|
@@ -35,17 +42,30 @@ export default defineAdapter<NapCatAdapterConfig>({
|
|
|
35
42
|
create(context) {
|
|
36
43
|
const config = resolveNapCatConfig(context.config);
|
|
37
44
|
const gateway = context.use(messageGatewayToken);
|
|
45
|
+
const sideEvents = context.use(sideEventGatewayToken);
|
|
38
46
|
// 注册到插件运行时状态(napcat.endpoint list 的"运行中"数据源)
|
|
39
47
|
context.use(napcatRuntimeStateToken).endpoints.set(config.id, {
|
|
40
48
|
id: config.id,
|
|
41
49
|
mode: config.connection,
|
|
42
50
|
});
|
|
43
51
|
if (config.connection === 'wss') {
|
|
44
|
-
return new NapCatWssEndpoint({
|
|
52
|
+
return new NapCatWssEndpoint({
|
|
53
|
+
id: context.id,
|
|
54
|
+
gateway,
|
|
55
|
+
sideEvents,
|
|
56
|
+
http: context.use(httpHostToken),
|
|
57
|
+
config,
|
|
58
|
+
});
|
|
45
59
|
}
|
|
46
60
|
if (config.connection === 'http') {
|
|
47
|
-
return new NapCatHttpEndpoint({
|
|
61
|
+
return new NapCatHttpEndpoint({
|
|
62
|
+
id: context.id,
|
|
63
|
+
gateway,
|
|
64
|
+
sideEvents,
|
|
65
|
+
http: context.use(httpHostToken),
|
|
66
|
+
config,
|
|
67
|
+
});
|
|
48
68
|
}
|
|
49
|
-
return new NapCatWsEndpoint({ id: context.id, gateway, config });
|
|
69
|
+
return new NapCatWsEndpoint({ id: context.id, gateway, sideEvents, config });
|
|
50
70
|
},
|
|
51
71
|
});
|
package/lib/http-endpoint.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
1
|
+
import { type EndpointControl, type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
2
|
+
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
4
4
|
import type { CapabilityId } from 'zhin.js';
|
|
5
5
|
import { callNapCatHttpAction, type NapCatEvent, type NapCatHttpConfig } from './protocol.js';
|
|
6
6
|
export interface NapCatHttpEndpointOptions {
|
|
7
7
|
readonly id: CapabilityId;
|
|
8
8
|
readonly gateway: MessageGateway;
|
|
9
|
+
readonly sideEvents?: SideEventGateway;
|
|
9
10
|
readonly http: HttpHost;
|
|
10
11
|
readonly config: NapCatHttpConfig;
|
|
11
12
|
readonly callHttpAction?: typeof callNapCatHttpAction;
|
|
@@ -13,6 +14,8 @@ export interface NapCatHttpEndpointOptions {
|
|
|
13
14
|
export declare class NapCatHttpEndpoint implements EndpointInstance {
|
|
14
15
|
#private;
|
|
15
16
|
readonly management: EndpointManagement;
|
|
17
|
+
readonly control: EndpointControl;
|
|
18
|
+
readonly content: import("@zhin.js/adapter").EndpointContentPort;
|
|
16
19
|
constructor(options: NapCatHttpEndpointOptions);
|
|
17
20
|
start(): Promise<void>;
|
|
18
21
|
open(): void;
|
package/lib/http-endpoint.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { createRecallEndpointControl, } from 'zhin.js/adapter';
|
|
1
2
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
2
3
|
import { createNapCatEndpointManagement } from './endpoint-management.js';
|
|
3
4
|
import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
|
|
4
5
|
import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
|
|
5
6
|
import { buildSendAction, callNapCatHttpAction, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
|
|
7
|
+
import { receiveNapCatSideEvent } from './side-event-dispatch.js';
|
|
8
|
+
import { createNapCatContentPort } from './onebot-get-msg.js';
|
|
6
9
|
import { readRequestBody } from './webhook.js';
|
|
7
10
|
import { verifyNapCatAccessToken } from './wss-auth.js';
|
|
8
11
|
export class NapCatHttpEndpoint {
|
|
@@ -10,6 +13,8 @@ export class NapCatHttpEndpoint {
|
|
|
10
13
|
#options;
|
|
11
14
|
#inboundDeduper = new InboundMessageDeduper();
|
|
12
15
|
management = createNapCatEndpointManagement(this);
|
|
16
|
+
control = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
17
|
+
content = createNapCatContentPort((action, params) => this.callApi(action, params));
|
|
13
18
|
#callHttpAction;
|
|
14
19
|
#routeReleases = [];
|
|
15
20
|
#open = false;
|
|
@@ -78,8 +83,12 @@ export class NapCatHttpEndpoint {
|
|
|
78
83
|
}, action, params).then((resp) => resp.data);
|
|
79
84
|
}
|
|
80
85
|
admit(ev) {
|
|
81
|
-
if (!this.#open
|
|
86
|
+
if (!this.#open)
|
|
82
87
|
return;
|
|
88
|
+
if (!isMessageEvent(ev)) {
|
|
89
|
+
receiveNapCatSideEvent(this.#options.sideEvents, this.#options.config.id, this, ev, this.#logger);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
83
92
|
if (isSelfMessage(ev))
|
|
84
93
|
return;
|
|
85
94
|
const msgId = String(ev.message_id);
|
package/lib/onebot-get-msg.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface NapcatMessageLookup {
|
|
2
2
|
messageId: string;
|
|
3
3
|
sender?: {
|
|
4
4
|
id: string;
|
|
@@ -11,4 +11,6 @@ export interface QuotedMessagePayload {
|
|
|
11
11
|
raw?: string;
|
|
12
12
|
time?: number;
|
|
13
13
|
}
|
|
14
|
-
export declare function parseOneBotGetMsgResponse(messageId: string, data: unknown):
|
|
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';
|
package/lib/onebot-get-msg.js
CHANGED
|
@@ -24,3 +24,56 @@ export function parseOneBotGetMsgResponse(messageId, data) {
|
|
|
24
24
|
time: typeof record.time === 'number' ? record.time : undefined,
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
+
export function createNapCatContentPort(callApi) {
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
async resolve(reference, context) {
|
|
30
|
+
try {
|
|
31
|
+
context.signal.throwIfAborted();
|
|
32
|
+
if (reference.kind === 'media') {
|
|
33
|
+
return reference.media.kind === 'file'
|
|
34
|
+
? Object.freeze({ status: 'unsupported', code: 'napcat_opaque_media_requires_platform_file_metadata' })
|
|
35
|
+
: Object.freeze({ status: 'resolved', reference, value: reference.media });
|
|
36
|
+
}
|
|
37
|
+
if (reference.kind === 'message') {
|
|
38
|
+
const parsed = parseOneBotGetMsgResponse(reference.message.id, await callApi('get_msg', { message_id: reference.message.id }));
|
|
39
|
+
context.signal.throwIfAborted();
|
|
40
|
+
const value = Object.freeze({
|
|
41
|
+
ref: reference.message,
|
|
42
|
+
...(parsed.sender?.id ? { actor: Object.freeze({ id: parsed.sender.id, ...(parsed.sender.name ? { displayName: parsed.sender.name } : {}) }) } : {}),
|
|
43
|
+
segments: canonicalSegments(parsed.content),
|
|
44
|
+
timestamp: toMillis(parsed.time),
|
|
45
|
+
});
|
|
46
|
+
return Object.freeze({ status: 'resolved', reference, value });
|
|
47
|
+
}
|
|
48
|
+
const raw = await callApi('get_forward_msg', { message_id: reference.forwardId, id: reference.forwardId });
|
|
49
|
+
context.signal.throwIfAborted();
|
|
50
|
+
const nodes = Array.isArray(raw.messages) ? raw.messages.slice(0, context.maxEntries) : [];
|
|
51
|
+
const value = Object.freeze(nodes.map((node) => {
|
|
52
|
+
const row = node;
|
|
53
|
+
const sender = row.sender;
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
...(sender?.user_id != null ? { actor: Object.freeze({ id: String(sender.user_id), ...(sender.nickname ? { displayName: String(sender.nickname) } : {}) }) } : {}),
|
|
56
|
+
segments: canonicalSegments(Array.isArray(row.content) ? row.content : []),
|
|
57
|
+
timestamp: toMillis(row.time),
|
|
58
|
+
});
|
|
59
|
+
}));
|
|
60
|
+
return Object.freeze({ status: 'resolved', reference, value });
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (context.signal.aborted)
|
|
64
|
+
return Object.freeze({ status: 'expired', code: 'turn_aborted' });
|
|
65
|
+
return Object.freeze({ status: 'failed', code: 'napcat_content_resolution_failed', message: error instanceof Error ? error.message : String(error) });
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function canonicalSegments(content) {
|
|
71
|
+
return Object.freeze(content.map((segment) => Object.freeze({
|
|
72
|
+
type: segment.type,
|
|
73
|
+
data: Object.freeze(segment.data ?? {}),
|
|
74
|
+
})));
|
|
75
|
+
}
|
|
76
|
+
function toMillis(value) {
|
|
77
|
+
const time = Number(value);
|
|
78
|
+
return Number.isFinite(time) ? (time < 10_000_000_000 ? time * 1000 : time) : Date.now();
|
|
79
|
+
}
|
package/lib/protocol.js
CHANGED
|
@@ -106,16 +106,16 @@ export function napcatOutboundTarget(conversation) {
|
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
export function formatInboundContent(ev) {
|
|
109
|
-
if (typeof ev.raw_message === 'string' && ev.raw_message)
|
|
110
|
-
return ev.raw_message;
|
|
111
109
|
if (Array.isArray(ev.message)) {
|
|
112
110
|
return ev.message
|
|
113
111
|
.map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
|
|
114
112
|
.join('');
|
|
115
113
|
}
|
|
116
114
|
if (typeof ev.message === 'string')
|
|
117
|
-
return ev.message;
|
|
118
|
-
return ''
|
|
115
|
+
return ev.message.replace(/\[CQ:[^\]]+\]/g, '').trim();
|
|
116
|
+
return typeof ev.raw_message === 'string'
|
|
117
|
+
? ev.raw_message.replace(/\[CQ:[^\]]+\]/g, '').trim()
|
|
118
|
+
: '';
|
|
119
119
|
}
|
|
120
120
|
/**
|
|
121
121
|
* 入站 sender 语义:必须是用户 ID(Runtime Message contract)。
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SideEventGateway } from '@zhin.js/core/runtime';
|
|
2
|
+
import { type getAdapterLogger } from '@zhin.js/logger';
|
|
3
|
+
import type { NapCatEvent } from './protocol.js';
|
|
4
|
+
export interface NapCatSideEventCaller {
|
|
5
|
+
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
6
|
+
}
|
|
7
|
+
export declare function receiveNapCatSideEvent(sideEvents: SideEventGateway | undefined, endpointKey: string, caller: NapCatSideEventCaller, raw: NapCatEvent, logger: ReturnType<typeof getAdapterLogger>): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { receiveOneBotLikeSideEvent } from '@zhin.js/core';
|
|
2
|
+
import { formatCompact } from '@zhin.js/logger';
|
|
3
|
+
export function receiveNapCatSideEvent(sideEvents, endpointKey, caller, raw, logger) {
|
|
4
|
+
if (!sideEvents)
|
|
5
|
+
return;
|
|
6
|
+
const record = raw;
|
|
7
|
+
const postType = String(record.post_type ?? '');
|
|
8
|
+
const requestType = String(record.request_type ?? '');
|
|
9
|
+
const isRequest = postType === 'request' || postType.startsWith('request.');
|
|
10
|
+
const isFriend = requestType === 'friend' || postType.includes('friend');
|
|
11
|
+
void receiveOneBotLikeSideEvent(sideEvents, {
|
|
12
|
+
adapter: 'napcat',
|
|
13
|
+
endpointKey,
|
|
14
|
+
platform: 'napcat',
|
|
15
|
+
raw: record,
|
|
16
|
+
...(isRequest ? {
|
|
17
|
+
approve: async (flag, remark) => {
|
|
18
|
+
await (isFriend
|
|
19
|
+
? caller.callApi('set_friend_add_request', { flag, approve: true, remark })
|
|
20
|
+
: caller.callApi('set_group_add_request', { flag, approve: true, reason: remark }));
|
|
21
|
+
},
|
|
22
|
+
reject: async (flag, reason) => {
|
|
23
|
+
await (isFriend
|
|
24
|
+
? caller.callApi('set_friend_add_request', { flag, approve: false })
|
|
25
|
+
: caller.callApi('set_group_add_request', { flag, approve: false, reason }));
|
|
26
|
+
},
|
|
27
|
+
} : {}),
|
|
28
|
+
}).catch((err) => {
|
|
29
|
+
logger.warn(formatCompact({
|
|
30
|
+
op: 'napcat_side_event_failed',
|
|
31
|
+
endpoint: endpointKey,
|
|
32
|
+
post_type: postType || undefined,
|
|
33
|
+
error: err instanceof Error ? err.message : String(err),
|
|
34
|
+
}));
|
|
35
|
+
});
|
|
36
|
+
}
|
package/lib/ws-endpoint.d.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import { type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
2
|
-
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
1
|
+
import { type EndpointControl, type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
2
|
+
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { CapabilityId } from 'zhin.js';
|
|
4
4
|
import { type NapCatEvent, type NapCatWsConfig } from './protocol.js';
|
|
5
5
|
import { type NapCatWsCreateOptions, type NapCatWsSocket } from './ws-types.js';
|
|
6
6
|
export interface NapCatWsEndpointOptions {
|
|
7
7
|
readonly id: CapabilityId;
|
|
8
8
|
readonly gateway: MessageGateway;
|
|
9
|
+
readonly sideEvents?: SideEventGateway;
|
|
9
10
|
readonly config: NapCatWsConfig;
|
|
10
11
|
readonly createWebSocket?: (url: string, options: NapCatWsCreateOptions) => NapCatWsSocket;
|
|
11
12
|
}
|
|
12
13
|
export declare class NapCatWsEndpoint implements EndpointInstance {
|
|
13
14
|
#private;
|
|
14
15
|
readonly management: EndpointManagement;
|
|
16
|
+
readonly control: EndpointControl;
|
|
17
|
+
readonly content: import("@zhin.js/adapter").EndpointContentPort;
|
|
15
18
|
constructor(options: NapCatWsEndpointOptions);
|
|
16
19
|
start(): Promise<void>;
|
|
17
20
|
open(): void;
|
package/lib/ws-endpoint.js
CHANGED
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
* NapCat WS client endpoint — outbound connect to NapCat.
|
|
3
3
|
*/
|
|
4
4
|
import WebSocket from 'ws';
|
|
5
|
-
import { createEndpointLifecycle, } from 'zhin.js/adapter';
|
|
5
|
+
import { createRecallEndpointControl, createEndpointLifecycle, } from 'zhin.js/adapter';
|
|
6
6
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
7
7
|
import { createNapCatEndpointManagement } from './endpoint-management.js';
|
|
8
8
|
import { registerNapcatAgentEndpoint } from './napcat-agent-deps.js';
|
|
9
9
|
import { InboundMessageDeduper, isNapCatBotMentioned, isSelfMessage, normalizeMessage, } from './napcat-inbound.js';
|
|
10
10
|
import { buildSendAction, buildWsConnectOptions, formatInboundContent, formatOutboundSegments, isMessageEvent, napcatInboundConversation, napcatOutboundTarget, senderNickname, senderUserId, } from './protocol.js';
|
|
11
|
+
import { receiveNapCatSideEvent } from './side-event-dispatch.js';
|
|
11
12
|
import { callNapCatWsAction, handleNapCatWsMessage, rejectAllPending, } from './ws-transport.js';
|
|
13
|
+
import { createNapCatContentPort } from './onebot-get-msg.js';
|
|
12
14
|
export class NapCatWsEndpoint {
|
|
13
15
|
#logger;
|
|
14
16
|
#options;
|
|
15
17
|
#inboundDeduper = new InboundMessageDeduper();
|
|
16
18
|
management = createNapCatEndpointManagement(this);
|
|
19
|
+
control = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
20
|
+
content = createNapCatContentPort((action, params) => this.callApi(action, params));
|
|
17
21
|
#lifecycle;
|
|
18
22
|
#ws;
|
|
19
23
|
#requestId = { value: 0 };
|
|
@@ -228,8 +232,12 @@ export class NapCatWsEndpoint {
|
|
|
228
232
|
}
|
|
229
233
|
/** Test / internal: admit a parsed event when the endpoint is open. */
|
|
230
234
|
admit(ev) {
|
|
231
|
-
if (!this.#open
|
|
235
|
+
if (!this.#open)
|
|
232
236
|
return;
|
|
237
|
+
if (!isMessageEvent(ev)) {
|
|
238
|
+
receiveNapCatSideEvent(this.#options.sideEvents, this.#options.config.id, this, ev, this.#logger);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
233
241
|
if (isSelfMessage(ev))
|
|
234
242
|
return;
|
|
235
243
|
const msgId = String(ev.message_id);
|
package/lib/wss-endpoint.d.ts
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
1
|
+
import { type EndpointControl, type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
|
|
2
|
+
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
4
4
|
import type { CapabilityId } from 'zhin.js';
|
|
5
5
|
import { type NapCatEvent, type NapCatWssConfig } from './protocol.js';
|
|
6
6
|
export interface NapCatWssEndpointOptions {
|
|
7
7
|
readonly id: CapabilityId;
|
|
8
8
|
readonly gateway: MessageGateway;
|
|
9
|
+
readonly sideEvents?: SideEventGateway;
|
|
9
10
|
readonly http: HttpHost;
|
|
10
11
|
readonly config: NapCatWssConfig;
|
|
11
12
|
}
|
|
12
13
|
export declare class NapCatWssEndpoint implements EndpointInstance {
|
|
13
14
|
#private;
|
|
14
15
|
readonly management: EndpointManagement;
|
|
16
|
+
readonly control: EndpointControl;
|
|
17
|
+
readonly content: import("@zhin.js/adapter").EndpointContentPort;
|
|
15
18
|
constructor(options: NapCatWssEndpointOptions);
|
|
16
19
|
start(): Promise<void>;
|
|
17
20
|
open(): void;
|
package/lib/wss-endpoint.js
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
* NapCat reverse WSS endpoint — accepts inbound WebSocket from NapCat.
|
|
3
3
|
*/
|
|
4
4
|
import { clearInterval } from 'node:timers';
|
|
5
|
+
import { createRecallEndpointControl, } from 'zhin.js/adapter';
|
|
5
6
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
6
7
|
import { createNapCatEndpointManagement } from './endpoint-management.js';
|
|
7
8
|
import { registerNapcatAgentEndpoint } from './napcat-agent-deps.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';
|
|
11
|
+
import { receiveNapCatSideEvent } from './side-event-dispatch.js';
|
|
12
|
+
import { createNapCatContentPort } from './onebot-get-msg.js';
|
|
10
13
|
import { callNapCatWsAction, handleNapCatWsMessage, rejectAllPending, startNapCatHeartbeat, } from './ws-transport.js';
|
|
11
14
|
import { verifyNapCatAccessToken } from './wss-auth.js';
|
|
12
15
|
export class NapCatWssEndpoint {
|
|
@@ -14,6 +17,8 @@ export class NapCatWssEndpoint {
|
|
|
14
17
|
#options;
|
|
15
18
|
#inboundDeduper = new InboundMessageDeduper();
|
|
16
19
|
management = createNapCatEndpointManagement(this);
|
|
20
|
+
control = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
21
|
+
content = createNapCatContentPort((action, params) => this.callApi(action, params));
|
|
17
22
|
#ws;
|
|
18
23
|
#wsRelease;
|
|
19
24
|
#heartbeatTimer;
|
|
@@ -86,8 +91,12 @@ export class NapCatWssEndpoint {
|
|
|
86
91
|
return callNapCatWsAction(this.#ws, this.#pending, this.#requestId, action, params);
|
|
87
92
|
}
|
|
88
93
|
admit(ev) {
|
|
89
|
-
if (!this.#open
|
|
94
|
+
if (!this.#open)
|
|
90
95
|
return;
|
|
96
|
+
if (!isMessageEvent(ev)) {
|
|
97
|
+
receiveNapCatSideEvent(this.#options.sideEvents, this.#options.config.id, this, ev, this.#logger);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
91
100
|
if (isSelfMessage(ev))
|
|
92
101
|
return;
|
|
93
102
|
const msgId = String(ev.message_id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-napcat",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0",
|
|
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",
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"ws": "^8.21.1",
|
|
46
|
-
"@zhin.js/adapter": "1.
|
|
47
|
-
"@zhin.js/core": "1.5.
|
|
48
|
-
"@zhin.js/host-http": "1.0.
|
|
49
|
-
"@zhin.js/im-contract": "1.0.
|
|
46
|
+
"@zhin.js/adapter": "1.2.0",
|
|
47
|
+
"@zhin.js/core": "1.5.13",
|
|
48
|
+
"@zhin.js/host-http": "1.0.12",
|
|
49
|
+
"@zhin.js/im-contract": "1.0.4",
|
|
50
50
|
"@zhin.js/logger": "1.0.76",
|
|
51
|
-
"@zhin.js/permission": "1.0.
|
|
51
|
+
"@zhin.js/permission": "1.0.3"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^26.1.2",
|
|
@@ -56,17 +56,17 @@
|
|
|
56
56
|
"typescript": "^6.0.3",
|
|
57
57
|
"vitest": "^4.1.10",
|
|
58
58
|
"zod": "^4.4.3",
|
|
59
|
-
"@zhin.js/agent": "1.1.
|
|
60
|
-
"@zhin.js/host-http": "1.0.
|
|
61
|
-
"zhin.js": "6.0.
|
|
59
|
+
"@zhin.js/agent": "1.1.15",
|
|
60
|
+
"@zhin.js/host-http": "1.0.12",
|
|
61
|
+
"zhin.js": "6.0.13"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
64
|
"zod": "^4.0.0",
|
|
65
|
-
"@zhin.js/adapter": "1.
|
|
66
|
-
"@zhin.js/agent": "1.1.
|
|
67
|
-
"@zhin.js/command": "1.0.
|
|
68
|
-
"@zhin.js/core": "1.5.
|
|
69
|
-
"zhin.js": "6.0.
|
|
65
|
+
"@zhin.js/adapter": "1.2.0",
|
|
66
|
+
"@zhin.js/agent": "1.1.15",
|
|
67
|
+
"@zhin.js/command": "1.0.15",
|
|
68
|
+
"@zhin.js/core": "1.5.13",
|
|
69
|
+
"zhin.js": "6.0.13"
|
|
70
70
|
},
|
|
71
71
|
"peerDependenciesMeta": {
|
|
72
72
|
"@zhin.js/agent": {
|
package/src/http-endpoint.ts
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
* NapCat HTTP endpoint — POST inbound events + HTTP API outbound.
|
|
3
3
|
*/
|
|
4
4
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
5
|
-
import
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
createRecallEndpointControl,
|
|
7
|
+
type EndpointControl,
|
|
8
|
+
type EndpointInstance,
|
|
9
|
+
type EndpointManagement,
|
|
10
|
+
type EndpointSendRequest,
|
|
11
|
+
} from 'zhin.js/adapter';
|
|
12
|
+
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
7
13
|
import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
|
|
8
14
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
9
15
|
import type { CapabilityId } from 'zhin.js';
|
|
@@ -28,6 +34,8 @@ import {
|
|
|
28
34
|
type NapCatEvent,
|
|
29
35
|
type NapCatHttpConfig,
|
|
30
36
|
} from './protocol.js';
|
|
37
|
+
import { receiveNapCatSideEvent } from './side-event-dispatch.js';
|
|
38
|
+
import { createNapCatContentPort } from './onebot-get-msg.js';
|
|
31
39
|
import { readRequestBody } from './webhook.js';
|
|
32
40
|
import { NapCatWsEndpoint } from './ws-endpoint.js';
|
|
33
41
|
import { verifyNapCatAccessToken } from './wss-auth.js';
|
|
@@ -35,6 +43,7 @@ import { verifyNapCatAccessToken } from './wss-auth.js';
|
|
|
35
43
|
export interface NapCatHttpEndpointOptions {
|
|
36
44
|
readonly id: CapabilityId;
|
|
37
45
|
readonly gateway: MessageGateway;
|
|
46
|
+
readonly sideEvents?: SideEventGateway;
|
|
38
47
|
readonly http: HttpHost;
|
|
39
48
|
readonly config: NapCatHttpConfig;
|
|
40
49
|
readonly callHttpAction?: typeof callNapCatHttpAction;
|
|
@@ -46,6 +55,8 @@ export class NapCatHttpEndpoint implements EndpointInstance {
|
|
|
46
55
|
readonly #options: NapCatHttpEndpointOptions;
|
|
47
56
|
readonly #inboundDeduper = new InboundMessageDeduper();
|
|
48
57
|
readonly management: EndpointManagement = createNapCatEndpointManagement(this);
|
|
58
|
+
readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
59
|
+
readonly content = createNapCatContentPort((action, params) => this.callApi(action, params));
|
|
49
60
|
readonly #callHttpAction: typeof callNapCatHttpAction;
|
|
50
61
|
#routeReleases: HttpRouteRegistration[] = [];
|
|
51
62
|
#open = false;
|
|
@@ -131,7 +142,17 @@ export class NapCatHttpEndpoint implements EndpointInstance {
|
|
|
131
142
|
}
|
|
132
143
|
|
|
133
144
|
admit(ev: NapCatEvent): void {
|
|
134
|
-
if (!this.#open
|
|
145
|
+
if (!this.#open) return;
|
|
146
|
+
if (!isMessageEvent(ev)) {
|
|
147
|
+
receiveNapCatSideEvent(
|
|
148
|
+
this.#options.sideEvents,
|
|
149
|
+
this.#options.config.id,
|
|
150
|
+
this,
|
|
151
|
+
ev,
|
|
152
|
+
this.#logger,
|
|
153
|
+
);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
135
156
|
if (isSelfMessage(ev)) return;
|
|
136
157
|
const msgId = String(ev.message_id);
|
|
137
158
|
if (!this.#inboundDeduper.shouldProcess(msgId)) return;
|
package/src/onebot-get-msg.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface NapcatMessageLookup {
|
|
2
2
|
messageId: string;
|
|
3
3
|
sender?: { id: string; name: string };
|
|
4
4
|
content: Array<{ type: string; data?: Record<string, unknown> }>;
|
|
@@ -9,18 +9,18 @@ export interface QuotedMessagePayload {
|
|
|
9
9
|
export function parseOneBotGetMsgResponse(
|
|
10
10
|
messageId: string,
|
|
11
11
|
data: unknown,
|
|
12
|
-
):
|
|
12
|
+
): NapcatMessageLookup {
|
|
13
13
|
const record =
|
|
14
14
|
data && typeof data === 'object' ? (data as Record<string, unknown>) : {};
|
|
15
|
-
let content:
|
|
15
|
+
let content: NapcatMessageLookup['content'] = [];
|
|
16
16
|
if (Array.isArray(record.message)) {
|
|
17
|
-
content = record.message as
|
|
17
|
+
content = record.message as NapcatMessageLookup['content'];
|
|
18
18
|
} else if (typeof record.raw_message === 'string' && record.raw_message) {
|
|
19
19
|
content = [{ type: 'text', data: { text: record.raw_message } }];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const senderRaw = record.sender;
|
|
23
|
-
let sender:
|
|
23
|
+
let sender: NapcatMessageLookup['sender'];
|
|
24
24
|
if (senderRaw && typeof senderRaw === 'object') {
|
|
25
25
|
const s = senderRaw as Record<string, unknown>;
|
|
26
26
|
sender = {
|
|
@@ -37,3 +37,61 @@ export function parseOneBotGetMsgResponse(
|
|
|
37
37
|
time: typeof record.time === 'number' ? record.time : undefined,
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
export function createNapCatContentPort(
|
|
42
|
+
callApi: (action: string, params?: Record<string, unknown>) => Promise<unknown>,
|
|
43
|
+
): EndpointContentPort {
|
|
44
|
+
return Object.freeze({
|
|
45
|
+
async resolve(reference: ConversationReference, context: EndpointContentResolveContext) {
|
|
46
|
+
try {
|
|
47
|
+
context.signal.throwIfAborted();
|
|
48
|
+
if (reference.kind === 'media') {
|
|
49
|
+
return reference.media.kind === 'file'
|
|
50
|
+
? Object.freeze({ status: 'unsupported' as const, code: 'napcat_opaque_media_requires_platform_file_metadata' })
|
|
51
|
+
: Object.freeze({ status: 'resolved' as const, reference, value: reference.media });
|
|
52
|
+
}
|
|
53
|
+
if (reference.kind === 'message') {
|
|
54
|
+
const parsed = parseOneBotGetMsgResponse(reference.message.id, await callApi('get_msg', { message_id: reference.message.id }));
|
|
55
|
+
context.signal.throwIfAborted();
|
|
56
|
+
const value: ConversationMessage = Object.freeze({
|
|
57
|
+
ref: reference.message,
|
|
58
|
+
...(parsed.sender?.id ? { actor: Object.freeze({ id: parsed.sender.id, ...(parsed.sender.name ? { displayName: parsed.sender.name } : {}) }) } : {}),
|
|
59
|
+
segments: canonicalSegments(parsed.content),
|
|
60
|
+
timestamp: toMillis(parsed.time),
|
|
61
|
+
});
|
|
62
|
+
return Object.freeze({ status: 'resolved' as const, reference, value });
|
|
63
|
+
}
|
|
64
|
+
const raw = await callApi('get_forward_msg', { message_id: reference.forwardId, id: reference.forwardId }) as Record<string, unknown>;
|
|
65
|
+
context.signal.throwIfAborted();
|
|
66
|
+
const nodes = Array.isArray(raw.messages) ? raw.messages.slice(0, context.maxEntries) : [];
|
|
67
|
+
const value: readonly ForwardEntry[] = Object.freeze(nodes.map((node) => {
|
|
68
|
+
const row = node as Record<string, unknown>;
|
|
69
|
+
const sender = row.sender as Record<string, unknown> | undefined;
|
|
70
|
+
return Object.freeze({
|
|
71
|
+
...(sender?.user_id != null ? { actor: Object.freeze({ id: String(sender.user_id), ...(sender.nickname ? { displayName: String(sender.nickname) } : {}) }) } : {}),
|
|
72
|
+
segments: canonicalSegments(Array.isArray(row.content) ? row.content as never[] : []),
|
|
73
|
+
timestamp: toMillis(row.time),
|
|
74
|
+
});
|
|
75
|
+
}));
|
|
76
|
+
return Object.freeze({ status: 'resolved' as const, reference, value });
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (context.signal.aborted) return Object.freeze({ status: 'expired' as const, code: 'turn_aborted' });
|
|
79
|
+
return Object.freeze({ status: 'failed' as const, code: 'napcat_content_resolution_failed', message: error instanceof Error ? error.message : String(error) });
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function canonicalSegments(content: Array<{ type: string; data?: Record<string, unknown> }>) {
|
|
86
|
+
return Object.freeze(content.map((segment) => Object.freeze({
|
|
87
|
+
type: segment.type,
|
|
88
|
+
data: Object.freeze(segment.data ?? {}),
|
|
89
|
+
})));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function toMillis(value: unknown): number {
|
|
93
|
+
const time = Number(value);
|
|
94
|
+
return Number.isFinite(time) ? (time < 10_000_000_000 ? time * 1000 : time) : Date.now();
|
|
95
|
+
}
|
|
96
|
+
import type { EndpointContentPort, EndpointContentResolveContext } from 'zhin.js/adapter';
|
|
97
|
+
import type { ConversationMessage, ConversationReference, ForwardEntry } from '@zhin.js/im-contract';
|
package/src/protocol.ts
CHANGED
|
@@ -244,14 +244,15 @@ export function napcatOutboundTarget(conversation: ConversationRef): ParsedSendT
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
export function formatInboundContent(ev: NapCatEvent): string {
|
|
247
|
-
if (typeof ev.raw_message === 'string' && ev.raw_message) return ev.raw_message;
|
|
248
247
|
if (Array.isArray(ev.message)) {
|
|
249
248
|
return ev.message
|
|
250
249
|
.map((seg) => (seg.type === 'text' ? String(seg.data?.text ?? '') : ''))
|
|
251
250
|
.join('');
|
|
252
251
|
}
|
|
253
|
-
if (typeof ev.message === 'string') return ev.message;
|
|
254
|
-
return ''
|
|
252
|
+
if (typeof ev.message === 'string') return ev.message.replace(/\[CQ:[^\]]+\]/g, '').trim();
|
|
253
|
+
return typeof ev.raw_message === 'string'
|
|
254
|
+
? ev.raw_message.replace(/\[CQ:[^\]]+\]/g, '').trim()
|
|
255
|
+
: '';
|
|
255
256
|
}
|
|
256
257
|
|
|
257
258
|
/**
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { receiveOneBotLikeSideEvent } from '@zhin.js/core';
|
|
2
|
+
import type { SideEventGateway } from '@zhin.js/core/runtime';
|
|
3
|
+
import { formatCompact, type getAdapterLogger } from '@zhin.js/logger';
|
|
4
|
+
import type { NapCatEvent } from './protocol.js';
|
|
5
|
+
|
|
6
|
+
export interface NapCatSideEventCaller {
|
|
7
|
+
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function receiveNapCatSideEvent(
|
|
11
|
+
sideEvents: SideEventGateway | undefined,
|
|
12
|
+
endpointKey: string,
|
|
13
|
+
caller: NapCatSideEventCaller,
|
|
14
|
+
raw: NapCatEvent,
|
|
15
|
+
logger: ReturnType<typeof getAdapterLogger>,
|
|
16
|
+
): void {
|
|
17
|
+
if (!sideEvents) return;
|
|
18
|
+
const record = raw as Record<string, unknown>;
|
|
19
|
+
const postType = String(record.post_type ?? '');
|
|
20
|
+
const requestType = String(record.request_type ?? '');
|
|
21
|
+
const isRequest = postType === 'request' || postType.startsWith('request.');
|
|
22
|
+
const isFriend = requestType === 'friend' || postType.includes('friend');
|
|
23
|
+
void receiveOneBotLikeSideEvent(sideEvents, {
|
|
24
|
+
adapter: 'napcat',
|
|
25
|
+
endpointKey,
|
|
26
|
+
platform: 'napcat',
|
|
27
|
+
raw: record,
|
|
28
|
+
...(isRequest ? {
|
|
29
|
+
approve: async (flag, remark) => {
|
|
30
|
+
await (isFriend
|
|
31
|
+
? caller.callApi('set_friend_add_request', { flag, approve: true, remark })
|
|
32
|
+
: caller.callApi('set_group_add_request', { flag, approve: true, reason: remark }));
|
|
33
|
+
},
|
|
34
|
+
reject: async (flag, reason) => {
|
|
35
|
+
await (isFriend
|
|
36
|
+
? caller.callApi('set_friend_add_request', { flag, approve: false })
|
|
37
|
+
: caller.callApi('set_group_add_request', { flag, approve: false, reason }));
|
|
38
|
+
},
|
|
39
|
+
} : {}),
|
|
40
|
+
}).catch((err) => {
|
|
41
|
+
logger.warn(formatCompact({
|
|
42
|
+
op: 'napcat_side_event_failed',
|
|
43
|
+
endpoint: endpointKey,
|
|
44
|
+
post_type: postType || undefined,
|
|
45
|
+
error: err instanceof Error ? err.message : String(err),
|
|
46
|
+
}));
|
|
47
|
+
});
|
|
48
|
+
}
|
package/src/ws-endpoint.ts
CHANGED
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import WebSocket from 'ws';
|
|
5
5
|
import {
|
|
6
|
+
createRecallEndpointControl,
|
|
6
7
|
createEndpointLifecycle,
|
|
7
8
|
type EndpointConnectHandle,
|
|
9
|
+
type EndpointControl,
|
|
8
10
|
type EndpointInstance,
|
|
9
11
|
type EndpointLifecycle,
|
|
10
12
|
type EndpointManagement,
|
|
11
13
|
type EndpointSendRequest,
|
|
12
14
|
} from 'zhin.js/adapter';
|
|
13
|
-
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
15
|
+
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
14
16
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
15
17
|
import type { CapabilityId } from 'zhin.js';
|
|
16
18
|
import { createNapCatEndpointManagement } from './endpoint-management.js';
|
|
@@ -34,6 +36,7 @@ import {
|
|
|
34
36
|
type NapCatEvent,
|
|
35
37
|
type NapCatWsConfig,
|
|
36
38
|
} from './protocol.js';
|
|
39
|
+
import { receiveNapCatSideEvent } from './side-event-dispatch.js';
|
|
37
40
|
import {
|
|
38
41
|
callNapCatWsAction,
|
|
39
42
|
handleNapCatWsMessage,
|
|
@@ -44,10 +47,12 @@ import {
|
|
|
44
47
|
type NapCatWsCreateOptions,
|
|
45
48
|
type NapCatWsSocket,
|
|
46
49
|
} from './ws-types.js';
|
|
50
|
+
import { createNapCatContentPort } from './onebot-get-msg.js';
|
|
47
51
|
|
|
48
52
|
export interface NapCatWsEndpointOptions {
|
|
49
53
|
readonly id: CapabilityId;
|
|
50
54
|
readonly gateway: MessageGateway;
|
|
55
|
+
readonly sideEvents?: SideEventGateway;
|
|
51
56
|
readonly config: NapCatWsConfig;
|
|
52
57
|
readonly createWebSocket?: (
|
|
53
58
|
url: string,
|
|
@@ -61,6 +66,8 @@ export class NapCatWsEndpoint implements EndpointInstance {
|
|
|
61
66
|
readonly #options: NapCatWsEndpointOptions;
|
|
62
67
|
readonly #inboundDeduper = new InboundMessageDeduper();
|
|
63
68
|
readonly management: EndpointManagement = createNapCatEndpointManagement(this);
|
|
69
|
+
readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
70
|
+
readonly content = createNapCatContentPort((action, params) => this.callApi(action, params));
|
|
64
71
|
readonly #lifecycle: EndpointLifecycle;
|
|
65
72
|
#ws?: NapCatWsSocket;
|
|
66
73
|
#requestId = { value: 0 };
|
|
@@ -322,7 +329,17 @@ export class NapCatWsEndpoint implements EndpointInstance {
|
|
|
322
329
|
|
|
323
330
|
/** Test / internal: admit a parsed event when the endpoint is open. */
|
|
324
331
|
admit(ev: NapCatEvent): void {
|
|
325
|
-
if (!this.#open
|
|
332
|
+
if (!this.#open) return;
|
|
333
|
+
if (!isMessageEvent(ev)) {
|
|
334
|
+
receiveNapCatSideEvent(
|
|
335
|
+
this.#options.sideEvents,
|
|
336
|
+
this.#options.config.id,
|
|
337
|
+
this,
|
|
338
|
+
ev,
|
|
339
|
+
this.#logger,
|
|
340
|
+
);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
326
343
|
if (isSelfMessage(ev)) return;
|
|
327
344
|
const msgId = String(ev.message_id);
|
|
328
345
|
if (!this.#inboundDeduper.shouldProcess(msgId)) return;
|
package/src/wss-endpoint.ts
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
* NapCat reverse WSS endpoint — accepts inbound WebSocket from NapCat.
|
|
3
3
|
*/
|
|
4
4
|
import { clearInterval } from 'node:timers';
|
|
5
|
-
import
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
createRecallEndpointControl,
|
|
7
|
+
type EndpointControl,
|
|
8
|
+
type EndpointInstance,
|
|
9
|
+
type EndpointManagement,
|
|
10
|
+
type EndpointSendRequest,
|
|
11
|
+
} from 'zhin.js/adapter';
|
|
12
|
+
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
7
13
|
import type { HttpHost, WsConnection } from '@zhin.js/host-http';
|
|
8
14
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
9
15
|
import type { CapabilityId } from 'zhin.js';
|
|
@@ -27,6 +33,8 @@ import {
|
|
|
27
33
|
type NapCatEvent,
|
|
28
34
|
type NapCatWssConfig,
|
|
29
35
|
} from './protocol.js';
|
|
36
|
+
import { receiveNapCatSideEvent } from './side-event-dispatch.js';
|
|
37
|
+
import { createNapCatContentPort } from './onebot-get-msg.js';
|
|
30
38
|
import {
|
|
31
39
|
callNapCatWsAction,
|
|
32
40
|
handleNapCatWsMessage,
|
|
@@ -43,6 +51,7 @@ import { verifyNapCatAccessToken } from './wss-auth.js';
|
|
|
43
51
|
export interface NapCatWssEndpointOptions {
|
|
44
52
|
readonly id: CapabilityId;
|
|
45
53
|
readonly gateway: MessageGateway;
|
|
54
|
+
readonly sideEvents?: SideEventGateway;
|
|
46
55
|
readonly http: HttpHost;
|
|
47
56
|
readonly config: NapCatWssConfig;
|
|
48
57
|
}
|
|
@@ -53,6 +62,8 @@ export class NapCatWssEndpoint implements EndpointInstance {
|
|
|
53
62
|
readonly #options: NapCatWssEndpointOptions;
|
|
54
63
|
readonly #inboundDeduper = new InboundMessageDeduper();
|
|
55
64
|
readonly management: EndpointManagement = createNapCatEndpointManagement(this);
|
|
65
|
+
readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
|
|
66
|
+
readonly content = createNapCatContentPort((action, params) => this.callApi(action, params));
|
|
56
67
|
#ws?: NapCatWsSocket;
|
|
57
68
|
#wsRelease?: () => void;
|
|
58
69
|
#heartbeatTimer?: NodeJS.Timeout;
|
|
@@ -134,7 +145,17 @@ export class NapCatWssEndpoint implements EndpointInstance {
|
|
|
134
145
|
}
|
|
135
146
|
|
|
136
147
|
admit(ev: NapCatEvent): void {
|
|
137
|
-
if (!this.#open
|
|
148
|
+
if (!this.#open) return;
|
|
149
|
+
if (!isMessageEvent(ev)) {
|
|
150
|
+
receiveNapCatSideEvent(
|
|
151
|
+
this.#options.sideEvents,
|
|
152
|
+
this.#options.config.id,
|
|
153
|
+
this,
|
|
154
|
+
ev,
|
|
155
|
+
this.#logger,
|
|
156
|
+
);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
138
159
|
if (isSelfMessage(ev)) return;
|
|
139
160
|
const msgId = String(ev.message_id);
|
|
140
161
|
if (!this.#inboundDeduper.shouldProcess(msgId)) return;
|