@zhin.js/adapter-napcat 5.0.5 → 6.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 +44 -0
- package/adapters/napcat.js +4 -3
- package/adapters/napcat.ts +4 -3
- package/lib/http-endpoint.d.ts +1 -0
- package/lib/http-endpoint.js +5 -0
- package/lib/napcat-endpoint-commands.js +1 -1
- package/lib/napcat-runtime-state.js +1 -1
- package/lib/protocol.d.ts +3 -2
- package/lib/protocol.js +24 -9
- package/lib/ws-endpoint.d.ts +1 -0
- package/lib/ws-endpoint.js +5 -0
- package/lib/wss-endpoint.d.ts +1 -0
- package/lib/wss-endpoint.js +5 -0
- package/package.json +9 -9
- package/plugin.js +1 -1
- package/src/http-endpoint.ts +5 -0
- package/src/napcat-endpoint-commands.ts +1 -1
- package/src/napcat-runtime-state.ts +1 -1
- package/src/protocol.ts +26 -10
- package/src/ws-endpoint.ts +5 -0
- package/src/wss-endpoint.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# @zhin.js/adapter-napcat
|
|
2
2
|
|
|
3
|
+
## 6.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4fbff5d: feat!: 多模态双向 Segment 一贯制(BREAKING,无兼容层)
|
|
8
|
+
|
|
9
|
+
全框架唯一媒体表达统一为 canonical `Segment` + `MediaRef{kind: url|path|base64|file, value, mime_type?, file_name?, size?}`,新增 audio/video/file 段类型;所有第二形状(legacy `data.url/file/base64` 字段、`mediaRefFromLegacyData`/`mediaRefToLegacyFields` 桥、双写)全部删除。
|
|
10
|
+
|
|
11
|
+
- **core**:`SendContent` 一等支持 `Segment[]`;endpoint 出站载荷只含 canonical 段;`resolveOutboundMediaPolicy` 改为纯声明驱动(adapter definition `segments.outboundMedia`),内置策略表删除,未声明回退 `url-or-text`;`ImageContent` 旧桥删除。
|
|
12
|
+
- **ai**:新增 `MediaContentBlock`/`MediaBlockRef`(Segment 同构)与 `UserMessage.media`(当前 turn 媒体,**不持久化**——存储层自动剥离);`createUserMessage(text, media?)` 签名变更(`ImageContent` 删除);provider 边界序列化器 `filterMediaBlocksForProvider` + 能力表(缺省 image-only,不支持类型降级占位文本);ai-sdk 桥媒体块 → SDK image/file parts。
|
|
13
|
+
- **agent**:入站 turn 注入(`turn/inbound-media.ts`)——commMessage 媒体段 → 当前 turn `UserMessage.media`;图片 path 物化、音频默认 STT(`@zhin.js/speech` 可选,失败降级占位)、视频/文件占位;`publishOutboundElements` 产出 canonical Segment;`transcribeAudioPayload` 导出。
|
|
14
|
+
- **cli**:`bridgeRuntimeMessage` 回复链路媒体段透传,不再压平为文本(`$reply` 直达 normalize → adapter)。
|
|
15
|
+
- **全部 20 个平台适配器**:出站媒体只消费 `data.media`(url 直发 / base64 直发 / 平台上传 / 读盘),入站媒体产出 canonical `data.media`;`segments.outboundMedia` 声明与实际消费逐一核对修正;QQ 入站新增 canonical segments(image/audio/video/file/mention/face/reply),图片/语音/视频不再丢失。
|
|
16
|
+
|
|
17
|
+
迁移:适配器/插件产媒体一律用 `{ type, data: { media: MediaRef } }`;发送 legacy `data.url/file/base64` 形状的段会被 warn 丢弃。
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [7c1e63a]
|
|
20
|
+
- Updated dependencies [4fbff5d]
|
|
21
|
+
- Updated dependencies [5b94d9c]
|
|
22
|
+
- @zhin.js/command@1.0.5
|
|
23
|
+
- @zhin.js/adapter@1.1.3
|
|
24
|
+
- @zhin.js/core@1.5.0
|
|
25
|
+
- @zhin.js/agent@1.1.0
|
|
26
|
+
- zhin.js@6.0.0
|
|
27
|
+
|
|
28
|
+
## 5.0.6
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- d52f3c5: fix: QQ 适配器群聊误识别为私聊、game-kit 启动崩溃、全面补齐 recallMessage
|
|
33
|
+
|
|
34
|
+
- fix(cli): `resolveChannelType` 增加 `metadata.channelKind` 检查,修复 QQ/Discord/KOOK 群聊消息被误分类为私聊
|
|
35
|
+
- fix(game-kit): `createHostGameDb` 改用延迟代理模型,避免数据库启动前解析模型导致崩溃
|
|
36
|
+
- feat(qq): 实现 `recallMessage`,解析复合 messageId 路由到对应 SDK 撤回方法;`normalizeQqMessage` 增加 `message_type` 缺失时的回退检测
|
|
37
|
+
- feat(slack): 实现 `recallMessage`,利用已有 compound ref 和 `chat.delete` API
|
|
38
|
+
- feat(onebot11): 两个 endpoint 类实现 `recallMessage`(`delete_msg`)
|
|
39
|
+
- feat(onebot12): 两个 endpoint 类实现 `recallMessage`(`delete_message`)
|
|
40
|
+
- feat(napcat): 三个 endpoint 类实现 `recallMessage`(`delete_msg`)
|
|
41
|
+
- feat(discord): 两个 endpoint 类实现 `recallMessage`;`send()` 改为返回 `channelId:snowflake` 复合 ID
|
|
42
|
+
- feat(telegram): 实现 `recallMessage`(`deleteMessage`);`send()` 改为返回 `chatId:messageId` 复合 ID
|
|
43
|
+
- feat(kook): 两个 endpoint 类实现 `recallMessage`,利用 kook-client `recallMsg` API
|
|
44
|
+
- feat(lark): 实现 `recallMessage`(`DELETE /im/v1/messages/{id}`)
|
|
45
|
+
- feat(wecom): 实现 `recallMessage`(`POST /cgi-bin/message/recall`)
|
|
46
|
+
|
|
3
47
|
## 5.0.5
|
|
4
48
|
|
|
5
49
|
### Patch Changes
|
package/adapters/napcat.js
CHANGED
|
@@ -15,15 +15,16 @@ 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
|
-
// OneBot file 参数原生消费 url / base64://
|
|
18
|
+
// OneBot file 参数原生消费 url / base64:// 媒体,file:// 本地路径由 NapCat 侧读盘;
|
|
19
|
+
// 无卡片交互面,交互段降级纯文本。
|
|
19
20
|
segments: {
|
|
20
|
-
outboundMedia: ['url', 'base64'],
|
|
21
|
+
outboundMedia: ['url', 'base64', 'path'],
|
|
21
22
|
interactive: 'text',
|
|
22
23
|
},
|
|
23
24
|
create(context) {
|
|
24
25
|
const config = resolveNapCatConfig(context.config);
|
|
25
26
|
const gateway = context.use(messageGatewayToken);
|
|
26
|
-
// 注册到插件运行时状态(napcat
|
|
27
|
+
// 注册到插件运行时状态(napcat.endpoint list 的"运行中"数据源)
|
|
27
28
|
context.use(napcatRuntimeStateToken).endpoints.set(config.name, {
|
|
28
29
|
name: config.name,
|
|
29
30
|
mode: config.connection,
|
package/adapters/napcat.ts
CHANGED
|
@@ -26,15 +26,16 @@ export type { NapCatWsSocket, NapCatWsCreateOptions } from '../src/ws-types.js';
|
|
|
26
26
|
|
|
27
27
|
export default defineAdapter<NapCatAdapterConfig>({
|
|
28
28
|
capabilities: ['inbound', 'outbound'],
|
|
29
|
-
// OneBot file 参数原生消费 url / base64://
|
|
29
|
+
// OneBot file 参数原生消费 url / base64:// 媒体,file:// 本地路径由 NapCat 侧读盘;
|
|
30
|
+
// 无卡片交互面,交互段降级纯文本。
|
|
30
31
|
segments: {
|
|
31
|
-
outboundMedia: ['url', 'base64'],
|
|
32
|
+
outboundMedia: ['url', 'base64', 'path'],
|
|
32
33
|
interactive: 'text',
|
|
33
34
|
},
|
|
34
35
|
create(context) {
|
|
35
36
|
const config = resolveNapCatConfig(context.config);
|
|
36
37
|
const gateway = context.use(messageGatewayToken);
|
|
37
|
-
// 注册到插件运行时状态(napcat
|
|
38
|
+
// 注册到插件运行时状态(napcat.endpoint list 的"运行中"数据源)
|
|
38
39
|
context.use(napcatRuntimeStateToken).endpoints.set(config.name, {
|
|
39
40
|
name: config.name,
|
|
40
41
|
mode: config.connection,
|
package/lib/http-endpoint.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare class NapCatHttpEndpoint implements EndpointInstance {
|
|
|
22
22
|
readonly target: string;
|
|
23
23
|
readonly payload: unknown;
|
|
24
24
|
}): Promise<string>;
|
|
25
|
+
recallMessage(messageId: string): Promise<void>;
|
|
25
26
|
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
26
27
|
admit(ev: NapCatEvent): void;
|
|
27
28
|
}
|
package/lib/http-endpoint.js
CHANGED
|
@@ -65,6 +65,11 @@ export class NapCatHttpEndpoint {
|
|
|
65
65
|
}));
|
|
66
66
|
return messageId;
|
|
67
67
|
}
|
|
68
|
+
async recallMessage(messageId) {
|
|
69
|
+
if (!messageId)
|
|
70
|
+
return;
|
|
71
|
+
await this.callApi('delete_msg', { message_id: Number(messageId) });
|
|
72
|
+
}
|
|
68
73
|
callApi(action, params = {}) {
|
|
69
74
|
return this.#callHttpAction({
|
|
70
75
|
http_url: this.#options.config.http_url,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `napcat
|
|
2
|
+
* `napcat.endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
|
|
3
3
|
* commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
|
|
4
4
|
*/
|
|
5
5
|
import { createEndpointCommands } from '@zhin.js/adapter';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NapCat 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
-
* 由 plugin.ts setup() provide,adapter create 与 `napcat
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `napcat.endpoint` 命令共享(同一 owner generation)。
|
|
4
4
|
*/
|
|
5
5
|
import { defineEndpointRuntimeStateToken } from '@zhin.js/adapter';
|
|
6
6
|
export const napcatRuntimeStateToken = defineEndpointRuntimeStateToken('napcat');
|
package/lib/protocol.d.ts
CHANGED
|
@@ -128,13 +128,14 @@ export declare function senderUserId(ev: NapCatEvent): string;
|
|
|
128
128
|
export declare function senderNickname(ev: NapCatEvent): string | undefined;
|
|
129
129
|
/**
|
|
130
130
|
* canonical MediaRef → OneBot `file` 参数:url 直传、base64 → `base64://`、
|
|
131
|
-
* 本地路径 → `file://`(路径在 OneBot
|
|
131
|
+
* 本地路径 → `file://`(路径在 OneBot 实现侧解析)、kind=file 不透明引用原样透传。
|
|
132
132
|
*/
|
|
133
133
|
export declare function mediaRefToOneBotFile(media: MediaRef): string;
|
|
134
134
|
/**
|
|
135
135
|
* Wire-encode an already-rendered outbound payload into OneBot message segments.
|
|
136
136
|
* 入参假定已经 core `normalizeOutboundPayload` 归一为 canonical Segment[];
|
|
137
|
-
*
|
|
137
|
+
* 媒体段(image/audio/video)仅认 canonical `data.media`,缺失则 warn + 丢弃;
|
|
138
|
+
* 非媒体的 wire 段(at 等)原样透传。
|
|
138
139
|
*/
|
|
139
140
|
export declare function formatOutboundSegments(payload: unknown): MessageSegment[];
|
|
140
141
|
export declare function buildSendAction(target: string, message: MessageSegment[]): {
|
package/lib/protocol.js
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* No legacy Adapter/Endpoint / segment-mapper.
|
|
4
4
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
5
5
|
*/
|
|
6
|
-
import { isMediaRef
|
|
6
|
+
import { isMediaRef } from '@zhin.js/core';
|
|
7
|
+
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
8
|
+
const logger = getLogger('napcat');
|
|
7
9
|
function normalizeConnection(connection) {
|
|
8
10
|
if (connection === 'wss' || connection === 'http')
|
|
9
11
|
return connection;
|
|
@@ -121,7 +123,7 @@ export function senderNickname(ev) {
|
|
|
121
123
|
}
|
|
122
124
|
/**
|
|
123
125
|
* canonical MediaRef → OneBot `file` 参数:url 直传、base64 → `base64://`、
|
|
124
|
-
* 本地路径 → `file://`(路径在 OneBot
|
|
126
|
+
* 本地路径 → `file://`(路径在 OneBot 实现侧解析)、kind=file 不透明引用原样透传。
|
|
125
127
|
*/
|
|
126
128
|
export function mediaRefToOneBotFile(media) {
|
|
127
129
|
if (media.kind === 'base64') {
|
|
@@ -134,22 +136,32 @@ export function mediaRefToOneBotFile(media) {
|
|
|
134
136
|
}
|
|
135
137
|
/** 媒体段 data 里的 canonical-only 字段,不进 OneBot wire。 */
|
|
136
138
|
const MEDIA_DATA_SKIP_KEYS = new Set(['media', 'alt', 'url', 'base64', 'file', 'mime_type']);
|
|
139
|
+
/**
|
|
140
|
+
* 媒体段 → OneBot wire:媒体来源唯一认 canonical `data.media`(MediaRef-only,
|
|
141
|
+
* 无 legacy 字段回读)。缺失或形状非法时 warn + 丢弃(返回 null)。
|
|
142
|
+
*/
|
|
137
143
|
function oneBotMediaSegment(type, data) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
144
|
+
if (!isMediaRef(data.media)) {
|
|
145
|
+
logger.warn(formatCompact({
|
|
146
|
+
op: 'napcat_outbound_media_dropped',
|
|
147
|
+
type,
|
|
148
|
+
reason: 'missing_media_ref',
|
|
149
|
+
}));
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
141
152
|
const extra = {};
|
|
142
153
|
for (const [key, value] of Object.entries(data)) {
|
|
143
154
|
if (!MEDIA_DATA_SKIP_KEYS.has(key))
|
|
144
155
|
extra[key] = value;
|
|
145
156
|
}
|
|
146
|
-
return { type, data: { ...extra, file: mediaRefToOneBotFile(media) } };
|
|
157
|
+
return { type, data: { ...extra, file: mediaRefToOneBotFile(data.media) } };
|
|
147
158
|
}
|
|
148
159
|
/**
|
|
149
160
|
* canonical Segment → OneBot 11 数组段:
|
|
150
161
|
* - mention → at(`qq: target`);
|
|
151
162
|
* - reply(`message_id`)→ reply(`id`);
|
|
152
|
-
* - image / audio→record / video 的 MediaRef → `file`(url / base64:// / file
|
|
163
|
+
* - image / audio→record / video 的 MediaRef → `file`(url / base64:// / file://),
|
|
164
|
+
* 无 canonical `data.media` 的媒体段丢弃(返回 null);
|
|
153
165
|
* - face 取 `id`;
|
|
154
166
|
* - 其余(NapCat 扩展段、已是 wire 形状的段)原样透传。
|
|
155
167
|
*/
|
|
@@ -186,7 +198,8 @@ function canonicalToOneBotSegment(segment) {
|
|
|
186
198
|
/**
|
|
187
199
|
* Wire-encode an already-rendered outbound payload into OneBot message segments.
|
|
188
200
|
* 入参假定已经 core `normalizeOutboundPayload` 归一为 canonical Segment[];
|
|
189
|
-
*
|
|
201
|
+
* 媒体段(image/audio/video)仅认 canonical `data.media`,缺失则 warn + 丢弃;
|
|
202
|
+
* 非媒体的 wire 段(at 等)原样透传。
|
|
190
203
|
*/
|
|
191
204
|
export function formatOutboundSegments(payload) {
|
|
192
205
|
if (typeof payload === 'string') {
|
|
@@ -211,7 +224,9 @@ export function formatOutboundSegments(payload) {
|
|
|
211
224
|
segs.push({ type: 'text', data: { text: item } });
|
|
212
225
|
continue;
|
|
213
226
|
}
|
|
214
|
-
|
|
227
|
+
const seg = canonicalToOneBotSegment(item);
|
|
228
|
+
if (seg)
|
|
229
|
+
segs.push(seg);
|
|
215
230
|
}
|
|
216
231
|
return segs.length ? segs : [{ type: 'text', data: { text: '' } }];
|
|
217
232
|
}
|
package/lib/ws-endpoint.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare class NapCatWsEndpoint implements EndpointInstance {
|
|
|
21
21
|
readonly target: string;
|
|
22
22
|
readonly payload: unknown;
|
|
23
23
|
}): Promise<string>;
|
|
24
|
+
recallMessage(messageId: string): Promise<void>;
|
|
24
25
|
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
25
26
|
setTitle(groupId: number, userId: number, title: string, duration?: number): Promise<boolean>;
|
|
26
27
|
sendLike(userId: number, times?: number): Promise<unknown>;
|
package/lib/ws-endpoint.js
CHANGED
|
@@ -83,6 +83,11 @@ export class NapCatWsEndpoint {
|
|
|
83
83
|
}));
|
|
84
84
|
return messageId;
|
|
85
85
|
}
|
|
86
|
+
async recallMessage(messageId) {
|
|
87
|
+
if (!messageId)
|
|
88
|
+
return;
|
|
89
|
+
await this.callApi('delete_msg', { message_id: Number(messageId) });
|
|
90
|
+
}
|
|
86
91
|
callApi(action, params = {}) {
|
|
87
92
|
return callNapCatWsAction(this.#ws, this.#pending, this.#requestId, action, params);
|
|
88
93
|
}
|
package/lib/wss-endpoint.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare class NapCatWssEndpoint implements EndpointInstance {
|
|
|
21
21
|
readonly target: string;
|
|
22
22
|
readonly payload: unknown;
|
|
23
23
|
}): Promise<string>;
|
|
24
|
+
recallMessage(messageId: string): Promise<void>;
|
|
24
25
|
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
25
26
|
admit(ev: NapCatEvent): void;
|
|
26
27
|
}
|
package/lib/wss-endpoint.js
CHANGED
|
@@ -76,6 +76,11 @@ export class NapCatWssEndpoint {
|
|
|
76
76
|
const data = await this.callApi(action, params);
|
|
77
77
|
return data?.message_id != null ? String(data.message_id) : '';
|
|
78
78
|
}
|
|
79
|
+
async recallMessage(messageId) {
|
|
80
|
+
if (!messageId)
|
|
81
|
+
return;
|
|
82
|
+
await this.callApi('delete_msg', { message_id: Number(messageId) });
|
|
83
|
+
}
|
|
79
84
|
callApi(action, params = {}) {
|
|
80
85
|
return callNapCatWsAction(this.#ws, this.#pending, this.#requestId, action, params);
|
|
81
86
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-napcat",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.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,9 +43,9 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"ws": "^8.21.1",
|
|
46
|
-
"@zhin.js/adapter": "1.1.
|
|
47
|
-
"@zhin.js/command": "1.0.
|
|
48
|
-
"@zhin.js/core": "1.
|
|
46
|
+
"@zhin.js/adapter": "1.1.3",
|
|
47
|
+
"@zhin.js/command": "1.0.5",
|
|
48
|
+
"@zhin.js/core": "1.5.0",
|
|
49
49
|
"@zhin.js/host-http": "1.0.4",
|
|
50
50
|
"@zhin.js/logger": "1.0.75",
|
|
51
51
|
"@zhin.js/plugin-runtime": "1.1.1"
|
|
@@ -56,16 +56,16 @@
|
|
|
56
56
|
"typescript": "^6.0.3",
|
|
57
57
|
"vitest": "^4.1.10",
|
|
58
58
|
"zod": "^4.4.3",
|
|
59
|
-
"@zhin.js/agent": "1.0
|
|
59
|
+
"@zhin.js/agent": "1.1.0",
|
|
60
60
|
"@zhin.js/host-http": "1.0.4"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"zod": "^4.0.0",
|
|
64
|
-
"@zhin.js/adapter": "1.1.
|
|
65
|
-
"@zhin.js/agent": "1.0
|
|
66
|
-
"@zhin.js/core": "1.
|
|
64
|
+
"@zhin.js/adapter": "1.1.3",
|
|
65
|
+
"@zhin.js/agent": "1.1.0",
|
|
66
|
+
"@zhin.js/core": "1.5.0",
|
|
67
67
|
"@zhin.js/plugin-runtime": "1.1.1",
|
|
68
|
-
"zhin.js": "
|
|
68
|
+
"zhin.js": "6.0.0"
|
|
69
69
|
},
|
|
70
70
|
"peerDependenciesMeta": {
|
|
71
71
|
"zhin.js": {
|
package/plugin.js
CHANGED
|
@@ -9,7 +9,7 @@ export default definePlugin({
|
|
|
9
9
|
displayName: 'NapCat Adapter',
|
|
10
10
|
},
|
|
11
11
|
setup(context) {
|
|
12
|
-
// 运行中 endpoint 注册表(napcat
|
|
12
|
+
// 运行中 endpoint 注册表(napcat.endpoint list 的"运行中"数据源)
|
|
13
13
|
context.resources.provide(napcatRuntimeStateToken, createEndpointRuntimeState());
|
|
14
14
|
// 平台权限门禁:scene_admin / scene_owner 由 sender role 判定(见各 endpoint admit metadata)
|
|
15
15
|
return registerDefaultScenePlatformPermitChecker('napcat');
|
package/src/http-endpoint.ts
CHANGED
|
@@ -112,6 +112,11 @@ export class NapCatHttpEndpoint implements EndpointInstance {
|
|
|
112
112
|
return messageId;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
async recallMessage(messageId: string): Promise<void> {
|
|
116
|
+
if (!messageId) return;
|
|
117
|
+
await this.callApi('delete_msg', { message_id: Number(messageId) });
|
|
118
|
+
}
|
|
119
|
+
|
|
115
120
|
callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
|
|
116
121
|
return this.#callHttpAction(
|
|
117
122
|
{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `napcat
|
|
2
|
+
* `napcat.endpoint` 命令族:由 @zhin.js/adapter 的 createEndpointCommands 套件生成。
|
|
3
3
|
* commands/endpoint/ 下的 list / add / remove 直接默认导出这三项。
|
|
4
4
|
*/
|
|
5
5
|
import { createEndpointCommands } from '@zhin.js/adapter';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NapCat 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
-
* 由 plugin.ts setup() provide,adapter create 与 `napcat
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `napcat.endpoint` 命令共享(同一 owner generation)。
|
|
4
4
|
*/
|
|
5
5
|
import { defineEndpointRuntimeStateToken } from '@zhin.js/adapter';
|
|
6
6
|
|
package/src/protocol.ts
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
* No legacy Adapter/Endpoint / segment-mapper.
|
|
4
4
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
5
5
|
*/
|
|
6
|
-
import { isMediaRef,
|
|
6
|
+
import { isMediaRef, type MediaRef } from '@zhin.js/core';
|
|
7
|
+
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
8
|
+
|
|
9
|
+
const logger = getLogger('napcat');
|
|
7
10
|
|
|
8
11
|
/** Transitional legacy endpoint row (`endpoints[]` with `context: napcat`). */
|
|
9
12
|
export interface NapCatLegacyEndpointRow {
|
|
@@ -256,7 +259,7 @@ export function senderNickname(ev: NapCatEvent): string | undefined {
|
|
|
256
259
|
|
|
257
260
|
/**
|
|
258
261
|
* canonical MediaRef → OneBot `file` 参数:url 直传、base64 → `base64://`、
|
|
259
|
-
* 本地路径 → `file://`(路径在 OneBot
|
|
262
|
+
* 本地路径 → `file://`(路径在 OneBot 实现侧解析)、kind=file 不透明引用原样透传。
|
|
260
263
|
*/
|
|
261
264
|
export function mediaRefToOneBotFile(media: MediaRef): string {
|
|
262
265
|
if (media.kind === 'base64') {
|
|
@@ -271,28 +274,39 @@ export function mediaRefToOneBotFile(media: MediaRef): string {
|
|
|
271
274
|
/** 媒体段 data 里的 canonical-only 字段,不进 OneBot wire。 */
|
|
272
275
|
const MEDIA_DATA_SKIP_KEYS = new Set(['media', 'alt', 'url', 'base64', 'file', 'mime_type']);
|
|
273
276
|
|
|
277
|
+
/**
|
|
278
|
+
* 媒体段 → OneBot wire:媒体来源唯一认 canonical `data.media`(MediaRef-only,
|
|
279
|
+
* 无 legacy 字段回读)。缺失或形状非法时 warn + 丢弃(返回 null)。
|
|
280
|
+
*/
|
|
274
281
|
function oneBotMediaSegment(
|
|
275
282
|
type: 'image' | 'record' | 'video',
|
|
276
283
|
data: Record<string, unknown>,
|
|
277
|
-
): MessageSegment {
|
|
278
|
-
|
|
279
|
-
|
|
284
|
+
): MessageSegment | null {
|
|
285
|
+
if (!isMediaRef(data.media)) {
|
|
286
|
+
logger.warn(formatCompact({
|
|
287
|
+
op: 'napcat_outbound_media_dropped',
|
|
288
|
+
type,
|
|
289
|
+
reason: 'missing_media_ref',
|
|
290
|
+
}));
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
280
293
|
const extra: Record<string, unknown> = {};
|
|
281
294
|
for (const [key, value] of Object.entries(data)) {
|
|
282
295
|
if (!MEDIA_DATA_SKIP_KEYS.has(key)) extra[key] = value;
|
|
283
296
|
}
|
|
284
|
-
return { type, data: { ...extra, file: mediaRefToOneBotFile(media) } };
|
|
297
|
+
return { type, data: { ...extra, file: mediaRefToOneBotFile(data.media) } };
|
|
285
298
|
}
|
|
286
299
|
|
|
287
300
|
/**
|
|
288
301
|
* canonical Segment → OneBot 11 数组段:
|
|
289
302
|
* - mention → at(`qq: target`);
|
|
290
303
|
* - reply(`message_id`)→ reply(`id`);
|
|
291
|
-
* - image / audio→record / video 的 MediaRef → `file`(url / base64:// / file
|
|
304
|
+
* - image / audio→record / video 的 MediaRef → `file`(url / base64:// / file://),
|
|
305
|
+
* 无 canonical `data.media` 的媒体段丢弃(返回 null);
|
|
292
306
|
* - face 取 `id`;
|
|
293
307
|
* - 其余(NapCat 扩展段、已是 wire 形状的段)原样透传。
|
|
294
308
|
*/
|
|
295
|
-
function canonicalToOneBotSegment(segment: NapCatWireSegment): MessageSegment {
|
|
309
|
+
function canonicalToOneBotSegment(segment: NapCatWireSegment): MessageSegment | null {
|
|
296
310
|
const data = segment.data ?? {};
|
|
297
311
|
switch (segment.type) {
|
|
298
312
|
case 'mention': {
|
|
@@ -323,7 +337,8 @@ function canonicalToOneBotSegment(segment: NapCatWireSegment): MessageSegment {
|
|
|
323
337
|
/**
|
|
324
338
|
* Wire-encode an already-rendered outbound payload into OneBot message segments.
|
|
325
339
|
* 入参假定已经 core `normalizeOutboundPayload` 归一为 canonical Segment[];
|
|
326
|
-
*
|
|
340
|
+
* 媒体段(image/audio/video)仅认 canonical `data.media`,缺失则 warn + 丢弃;
|
|
341
|
+
* 非媒体的 wire 段(at 等)原样透传。
|
|
327
342
|
*/
|
|
328
343
|
export function formatOutboundSegments(payload: unknown): MessageSegment[] {
|
|
329
344
|
if (typeof payload === 'string') {
|
|
@@ -351,7 +366,8 @@ export function formatOutboundSegments(payload: unknown): MessageSegment[] {
|
|
|
351
366
|
segs.push({ type: 'text', data: { text: item } });
|
|
352
367
|
continue;
|
|
353
368
|
}
|
|
354
|
-
|
|
369
|
+
const seg = canonicalToOneBotSegment(item);
|
|
370
|
+
if (seg) segs.push(seg);
|
|
355
371
|
}
|
|
356
372
|
return segs.length ? segs : [{ type: 'text', data: { text: '' } }];
|
|
357
373
|
}
|
package/src/ws-endpoint.ts
CHANGED
|
@@ -132,6 +132,11 @@ export class NapCatWsEndpoint implements EndpointInstance {
|
|
|
132
132
|
return messageId;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
async recallMessage(messageId: string): Promise<void> {
|
|
136
|
+
if (!messageId) return;
|
|
137
|
+
await this.callApi('delete_msg', { message_id: Number(messageId) });
|
|
138
|
+
}
|
|
139
|
+
|
|
135
140
|
callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
|
|
136
141
|
return callNapCatWsAction(this.#ws, this.#pending, this.#requestId, action, params);
|
|
137
142
|
}
|
package/src/wss-endpoint.ts
CHANGED
|
@@ -122,6 +122,11 @@ export class NapCatWssEndpoint implements EndpointInstance {
|
|
|
122
122
|
return data?.message_id != null ? String(data.message_id) : '';
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
async recallMessage(messageId: string): Promise<void> {
|
|
126
|
+
if (!messageId) return;
|
|
127
|
+
await this.callApi('delete_msg', { message_id: Number(messageId) });
|
|
128
|
+
}
|
|
129
|
+
|
|
125
130
|
callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
|
|
126
131
|
return callNapCatWsAction(this.#ws, this.#pending, this.#requestId, action, params);
|
|
127
132
|
}
|