@zhin.js/adapter-onebot12 4.0.4 → 5.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 +43 -0
- package/adapters/onebot12.js +1 -1
- package/adapters/onebot12.ts +1 -1
- package/lib/onebot12-endpoint-commands.js +1 -1
- package/lib/onebot12-runtime-state.js +1 -1
- package/lib/protocol.d.ts +3 -2
- package/lib/protocol.js +29 -13
- 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 +7 -7
- package/plugin.js +1 -1
- package/src/onebot12-endpoint-commands.ts +1 -1
- package/src/onebot12-runtime-state.ts +1 -1
- package/src/protocol.ts +31 -14
- package/src/ws-endpoint.ts +5 -0
- package/src/wss-endpoint.ts +5 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @zhin.js/adapter-onebot12
|
|
2
2
|
|
|
3
|
+
## 5.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@6.0.0
|
|
26
|
+
|
|
27
|
+
## 4.0.5
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- d52f3c5: fix: QQ 适配器群聊误识别为私聊、game-kit 启动崩溃、全面补齐 recallMessage
|
|
32
|
+
|
|
33
|
+
- fix(cli): `resolveChannelType` 增加 `metadata.channelKind` 检查,修复 QQ/Discord/KOOK 群聊消息被误分类为私聊
|
|
34
|
+
- fix(game-kit): `createHostGameDb` 改用延迟代理模型,避免数据库启动前解析模型导致崩溃
|
|
35
|
+
- feat(qq): 实现 `recallMessage`,解析复合 messageId 路由到对应 SDK 撤回方法;`normalizeQqMessage` 增加 `message_type` 缺失时的回退检测
|
|
36
|
+
- feat(slack): 实现 `recallMessage`,利用已有 compound ref 和 `chat.delete` API
|
|
37
|
+
- feat(onebot11): 两个 endpoint 类实现 `recallMessage`(`delete_msg`)
|
|
38
|
+
- feat(onebot12): 两个 endpoint 类实现 `recallMessage`(`delete_message`)
|
|
39
|
+
- feat(napcat): 三个 endpoint 类实现 `recallMessage`(`delete_msg`)
|
|
40
|
+
- feat(discord): 两个 endpoint 类实现 `recallMessage`;`send()` 改为返回 `channelId:snowflake` 复合 ID
|
|
41
|
+
- feat(telegram): 实现 `recallMessage`(`deleteMessage`);`send()` 改为返回 `chatId:messageId` 复合 ID
|
|
42
|
+
- feat(kook): 两个 endpoint 类实现 `recallMessage`,利用 kook-client `recallMsg` API
|
|
43
|
+
- feat(lark): 实现 `recallMessage`(`DELETE /im/v1/messages/{id}`)
|
|
44
|
+
- feat(wecom): 实现 `recallMessage`(`POST /cgi-bin/message/recall`)
|
|
45
|
+
|
|
3
46
|
## 4.0.4
|
|
4
47
|
|
|
5
48
|
### Patch Changes
|
package/adapters/onebot12.js
CHANGED
|
@@ -24,7 +24,7 @@ export default defineAdapter({
|
|
|
24
24
|
create(context) {
|
|
25
25
|
const config = resolveOneBot12Config(context.config);
|
|
26
26
|
const gateway = context.use(messageGatewayToken);
|
|
27
|
-
// 注册到插件运行时状态(onebot12
|
|
27
|
+
// 注册到插件运行时状态(onebot12.endpoint list 的"运行中"数据源)
|
|
28
28
|
context.use(onebot12RuntimeStateToken).endpoints.set(config.name, {
|
|
29
29
|
name: config.name,
|
|
30
30
|
mode: config.connection,
|
package/adapters/onebot12.ts
CHANGED
|
@@ -32,7 +32,7 @@ export default defineAdapter<OneBot12AdapterConfig>({
|
|
|
32
32
|
create(context) {
|
|
33
33
|
const config = resolveOneBot12Config(context.config);
|
|
34
34
|
const gateway = context.use(messageGatewayToken);
|
|
35
|
-
// 注册到插件运行时状态(onebot12
|
|
35
|
+
// 注册到插件运行时状态(onebot12.endpoint list 的"运行中"数据源)
|
|
36
36
|
context.use(onebot12RuntimeStateToken).endpoints.set(config.name, {
|
|
37
37
|
name: config.name,
|
|
38
38
|
mode: config.connection,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `onebot12
|
|
2
|
+
* `onebot12.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
|
* OneBot12 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
-
* 由 plugin.ts setup() provide,adapter create 与 `onebot12
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `onebot12.endpoint` 命令共享(同一 owner generation)。
|
|
4
4
|
*/
|
|
5
5
|
import { defineEndpointRuntimeStateToken } from '@zhin.js/adapter';
|
|
6
6
|
export const onebot12RuntimeStateToken = defineEndpointRuntimeStateToken('onebot12');
|
package/lib/protocol.d.ts
CHANGED
|
@@ -137,13 +137,14 @@ export declare function isBotMentioned(ev: OneBot12Event): boolean;
|
|
|
137
137
|
* canonical MediaRef → OneBot 12 媒体字段(扩展字段降级形状)。
|
|
138
138
|
* spec 正式投递形状是 `file_id`(先 upload_file 物化,见
|
|
139
139
|
* {@link uploadOneBot12MediaSegments});上传失败时按常见扩展字段
|
|
140
|
-
* 降级输出:url → `url`、base64 → `data`、本地路径 → `path
|
|
140
|
+
* 降级输出:url → `url`、base64 → `data`、本地路径 → `path`;
|
|
141
|
+
* kind=file(平台不透明引用)直接按 `file_id` 复投。
|
|
141
142
|
*/
|
|
142
143
|
export declare function mediaRefToOneBot12Fields(media: MediaRef): Record<string, unknown>;
|
|
143
144
|
/**
|
|
144
145
|
* Wire-encode an already-rendered outbound payload into OneBot 12 message segments.
|
|
145
146
|
* 入参假定已经 core `normalizeOutboundPayload` 归一为 canonical Segment[];
|
|
146
|
-
*
|
|
147
|
+
* 媒体段只认 `data.media`(canonical MediaRef),无 MediaRef 的媒体段 warn 后丢弃。
|
|
147
148
|
*/
|
|
148
149
|
export declare function formatOutboundSegments(payload: unknown): OneBot12Segment[];
|
|
149
150
|
/** 端点动作调用签名(WS echo 请求 / webhook api_url HTTP)。 */
|
package/lib/protocol.js
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
* Spec: https://12.onebot.dev/
|
|
5
5
|
*/
|
|
6
|
-
import { isMediaRef
|
|
6
|
+
import { isMediaRef } from '@zhin.js/core';
|
|
7
|
+
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
8
|
+
const logger = getLogger('onebot12');
|
|
7
9
|
export function resolveOneBot12Config(config = {}) {
|
|
8
10
|
const entry = config.endpoints?.find((item) => item.context === 'onebot12');
|
|
9
11
|
const connection = config.connection
|
|
@@ -145,17 +147,19 @@ export function isBotMentioned(ev) {
|
|
|
145
147
|
}
|
|
146
148
|
/** OneBot 12 携带媒体的段类型。 */
|
|
147
149
|
const ONEBOT12_MEDIA_TYPES = new Set(['image', 'voice', 'audio', 'video', 'file']);
|
|
148
|
-
/** 媒体段 data 里的 canonical
|
|
149
|
-
const MEDIA_DATA_SKIP_KEYS = new Set([
|
|
150
|
-
'media', 'alt', 'mime_type', 'url', 'file', 'base64', 'data', 'path',
|
|
151
|
-
]);
|
|
150
|
+
/** 媒体段 data 里的 canonical 字段,归一后不重复进 wire。 */
|
|
151
|
+
const MEDIA_DATA_SKIP_KEYS = new Set(['media', 'alt', 'mime_type']);
|
|
152
152
|
/**
|
|
153
153
|
* canonical MediaRef → OneBot 12 媒体字段(扩展字段降级形状)。
|
|
154
154
|
* spec 正式投递形状是 `file_id`(先 upload_file 物化,见
|
|
155
155
|
* {@link uploadOneBot12MediaSegments});上传失败时按常见扩展字段
|
|
156
|
-
* 降级输出:url → `url`、base64 → `data`、本地路径 → `path
|
|
156
|
+
* 降级输出:url → `url`、base64 → `data`、本地路径 → `path`;
|
|
157
|
+
* kind=file(平台不透明引用)直接按 `file_id` 复投。
|
|
157
158
|
*/
|
|
158
159
|
export function mediaRefToOneBot12Fields(media) {
|
|
160
|
+
if (media.kind === 'file') {
|
|
161
|
+
return { file_id: media.value };
|
|
162
|
+
}
|
|
159
163
|
if (media.kind === 'base64') {
|
|
160
164
|
const value = media.value.startsWith('base64://')
|
|
161
165
|
? media.value.slice('base64://'.length)
|
|
@@ -179,16 +183,24 @@ function oneBot12MediaSegment(type, data) {
|
|
|
179
183
|
// 已物化为 file_id 的段是 spec 正式形状,原样透传。
|
|
180
184
|
if (typeof data.file_id === 'string' && data.file_id)
|
|
181
185
|
return { type, data };
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
186
|
+
// canonical MediaRef 唯一来源(中央 normalizeOutboundPayload 已保证 canonical)
|
|
187
|
+
const media = isMediaRef(data.media) ? data.media : undefined;
|
|
188
|
+
if (!media) {
|
|
189
|
+
logger.warn(formatCompact({
|
|
190
|
+
op: 'onebot12_outbound_media_dropped',
|
|
191
|
+
type,
|
|
192
|
+
reason: 'missing_media_ref',
|
|
193
|
+
}));
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
185
196
|
return { type, data: { ...oneBot12MediaExtra(data), ...mediaRefToOneBot12Fields(media) } };
|
|
186
197
|
}
|
|
187
198
|
/**
|
|
188
199
|
* canonical Segment → OneBot 12 数组段:
|
|
189
200
|
* - mention → mention(`user_id: target`,`target: 'all'` → mention_all);
|
|
190
201
|
* - reply(`message_id`)→ reply(`message_id`);
|
|
191
|
-
* - image / voice / audio / video / file 的 MediaRef → url/data/path
|
|
202
|
+
* - image / voice / audio / video / file 的 MediaRef → url/data/path/file_id 字段,
|
|
203
|
+
* 无 canonical MediaRef 的媒体段 warn 后丢弃(返回 null);
|
|
192
204
|
* - 其余(已是 wire 形状的段、平台扩展段)原样透传。
|
|
193
205
|
*/
|
|
194
206
|
function canonicalToOneBotSegment(segment) {
|
|
@@ -218,7 +230,7 @@ function canonicalToOneBotSegment(segment) {
|
|
|
218
230
|
/**
|
|
219
231
|
* Wire-encode an already-rendered outbound payload into OneBot 12 message segments.
|
|
220
232
|
* 入参假定已经 core `normalizeOutboundPayload` 归一为 canonical Segment[];
|
|
221
|
-
*
|
|
233
|
+
* 媒体段只认 `data.media`(canonical MediaRef),无 MediaRef 的媒体段 warn 后丢弃。
|
|
222
234
|
*/
|
|
223
235
|
export function formatOutboundSegments(payload) {
|
|
224
236
|
if (typeof payload === 'string') {
|
|
@@ -243,7 +255,9 @@ export function formatOutboundSegments(payload) {
|
|
|
243
255
|
segs.push({ type: 'text', data: { text: item } });
|
|
244
256
|
continue;
|
|
245
257
|
}
|
|
246
|
-
|
|
258
|
+
const seg = canonicalToOneBotSegment(item);
|
|
259
|
+
if (seg)
|
|
260
|
+
segs.push(seg);
|
|
247
261
|
}
|
|
248
262
|
return segs.length ? segs : [{ type: 'text', data: { text: '' } }];
|
|
249
263
|
}
|
|
@@ -296,7 +310,9 @@ async function uploadOneMediaSegment(item, callAction, onUploadFailed) {
|
|
|
296
310
|
const data = segment.data ?? {};
|
|
297
311
|
if (typeof data.file_id === 'string' && data.file_id)
|
|
298
312
|
return item;
|
|
299
|
-
|
|
313
|
+
// canonical MediaRef 唯一来源;无 MediaRef 的段原样保留,由
|
|
314
|
+
// formatOutboundSegments warn 后丢弃。
|
|
315
|
+
const media = isMediaRef(data.media) ? data.media : undefined;
|
|
300
316
|
if (!media)
|
|
301
317
|
return item;
|
|
302
318
|
// kind=file:平台不透明引用(OB12 file_id 复投),直接物化为 file_id。
|
package/lib/ws-endpoint.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare class OneBot12WsEndpoint implements EndpointInstance {
|
|
|
21
21
|
readonly target: string;
|
|
22
22
|
readonly payload: unknown;
|
|
23
23
|
}): Promise<string>;
|
|
24
|
+
recallMessage(messageId: string): Promise<void>;
|
|
24
25
|
/** Public API for management surface / callers. */
|
|
25
26
|
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
26
27
|
/** Test / internal: admit a parsed event when the endpoint is open. */
|
package/lib/ws-endpoint.js
CHANGED
|
@@ -89,6 +89,11 @@ export class OneBot12WsEndpoint {
|
|
|
89
89
|
}));
|
|
90
90
|
return messageId;
|
|
91
91
|
}
|
|
92
|
+
async recallMessage(messageId) {
|
|
93
|
+
if (!messageId)
|
|
94
|
+
return;
|
|
95
|
+
await this.#callAction('delete_message', { message_id: messageId });
|
|
96
|
+
}
|
|
92
97
|
/** Public API for management surface / callers. */
|
|
93
98
|
callApi(action, params = {}) {
|
|
94
99
|
return this.#callAction(action, params);
|
package/lib/wss-endpoint.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare class OneBot12WssEndpoint implements EndpointInstance {
|
|
|
21
21
|
readonly target: string;
|
|
22
22
|
readonly payload: unknown;
|
|
23
23
|
}): Promise<string>;
|
|
24
|
+
recallMessage(messageId: string): Promise<void>;
|
|
24
25
|
/** Public API for management surface / callers. */
|
|
25
26
|
callApi(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
26
27
|
admit(ev: OneBot12Event): void;
|
package/lib/wss-endpoint.js
CHANGED
|
@@ -88,6 +88,11 @@ export class OneBot12WssEndpoint {
|
|
|
88
88
|
const data = await this.#callAction('send_message', params);
|
|
89
89
|
return data?.message_id ?? '';
|
|
90
90
|
}
|
|
91
|
+
async recallMessage(messageId) {
|
|
92
|
+
if (!messageId)
|
|
93
|
+
return;
|
|
94
|
+
await this.#callAction('delete_message', { message_id: messageId });
|
|
95
|
+
}
|
|
91
96
|
/** Public API for management surface / callers. */
|
|
92
97
|
callApi(action, params = {}) {
|
|
93
98
|
return this.#callAction(action, params);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-onebot12",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Zhin.js OneBot 12 adapter for Plugin Runtime (WebSocket client)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"ws": "^8.21.1",
|
|
43
|
-
"@zhin.js/adapter": "1.1.
|
|
44
|
-
"@zhin.js/command": "1.0.
|
|
45
|
-
"@zhin.js/core": "1.
|
|
43
|
+
"@zhin.js/adapter": "1.1.3",
|
|
44
|
+
"@zhin.js/command": "1.0.5",
|
|
45
|
+
"@zhin.js/core": "1.5.0",
|
|
46
46
|
"@zhin.js/host-http": "1.0.4",
|
|
47
47
|
"@zhin.js/logger": "1.0.75",
|
|
48
48
|
"@zhin.js/plugin-runtime": "1.1.1"
|
|
@@ -55,10 +55,10 @@
|
|
|
55
55
|
"@zhin.js/host-http": "1.0.4"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
|
-
"@zhin.js/adapter": "1.1.
|
|
59
|
-
"@zhin.js/core": "1.
|
|
58
|
+
"@zhin.js/adapter": "1.1.3",
|
|
59
|
+
"@zhin.js/core": "1.5.0",
|
|
60
60
|
"@zhin.js/plugin-runtime": "1.1.1",
|
|
61
|
-
"zhin.js": "
|
|
61
|
+
"zhin.js": "6.0.0"
|
|
62
62
|
},
|
|
63
63
|
"peerDependenciesMeta": {
|
|
64
64
|
"zhin.js": {
|
package/plugin.js
CHANGED
|
@@ -8,7 +8,7 @@ export default definePlugin({
|
|
|
8
8
|
displayName: 'OneBot 12 Adapter',
|
|
9
9
|
},
|
|
10
10
|
setup(context) {
|
|
11
|
-
// 运行中 endpoint 注册表(onebot12
|
|
11
|
+
// 运行中 endpoint 注册表(onebot12.endpoint list 的"运行中"数据源)
|
|
12
12
|
context.resources.provide(onebot12RuntimeStateToken, createEndpointRuntimeState());
|
|
13
13
|
},
|
|
14
14
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `onebot12
|
|
2
|
+
* `onebot12.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
|
* OneBot12 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
-
* 由 plugin.ts setup() provide,adapter create 与 `onebot12
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `onebot12.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
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
* Spec: https://12.onebot.dev/
|
|
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('onebot12');
|
|
7
10
|
|
|
8
11
|
/** Transitional legacy endpoint row (`endpoints[]` with `context: onebot12`). */
|
|
9
12
|
export interface OneBot12LegacyEndpointRow {
|
|
@@ -277,18 +280,20 @@ export function isBotMentioned(ev: OneBot12Event): boolean {
|
|
|
277
280
|
/** OneBot 12 携带媒体的段类型。 */
|
|
278
281
|
const ONEBOT12_MEDIA_TYPES = new Set(['image', 'voice', 'audio', 'video', 'file']);
|
|
279
282
|
|
|
280
|
-
/** 媒体段 data 里的 canonical
|
|
281
|
-
const MEDIA_DATA_SKIP_KEYS = new Set([
|
|
282
|
-
'media', 'alt', 'mime_type', 'url', 'file', 'base64', 'data', 'path',
|
|
283
|
-
]);
|
|
283
|
+
/** 媒体段 data 里的 canonical 字段,归一后不重复进 wire。 */
|
|
284
|
+
const MEDIA_DATA_SKIP_KEYS = new Set(['media', 'alt', 'mime_type']);
|
|
284
285
|
|
|
285
286
|
/**
|
|
286
287
|
* canonical MediaRef → OneBot 12 媒体字段(扩展字段降级形状)。
|
|
287
288
|
* spec 正式投递形状是 `file_id`(先 upload_file 物化,见
|
|
288
289
|
* {@link uploadOneBot12MediaSegments});上传失败时按常见扩展字段
|
|
289
|
-
* 降级输出:url → `url`、base64 → `data`、本地路径 → `path
|
|
290
|
+
* 降级输出:url → `url`、base64 → `data`、本地路径 → `path`;
|
|
291
|
+
* kind=file(平台不透明引用)直接按 `file_id` 复投。
|
|
290
292
|
*/
|
|
291
293
|
export function mediaRefToOneBot12Fields(media: MediaRef): Record<string, unknown> {
|
|
294
|
+
if (media.kind === 'file') {
|
|
295
|
+
return { file_id: media.value };
|
|
296
|
+
}
|
|
292
297
|
if (media.kind === 'base64') {
|
|
293
298
|
const value = media.value.startsWith('base64://')
|
|
294
299
|
? media.value.slice('base64://'.length)
|
|
@@ -312,11 +317,19 @@ function oneBot12MediaExtra(data: Record<string, unknown>): Record<string, unkno
|
|
|
312
317
|
function oneBot12MediaSegment(
|
|
313
318
|
type: string,
|
|
314
319
|
data: Record<string, unknown>,
|
|
315
|
-
): OneBot12Segment {
|
|
320
|
+
): OneBot12Segment | null {
|
|
316
321
|
// 已物化为 file_id 的段是 spec 正式形状,原样透传。
|
|
317
322
|
if (typeof data.file_id === 'string' && data.file_id) return { type, data };
|
|
318
|
-
|
|
319
|
-
|
|
323
|
+
// canonical MediaRef 唯一来源(中央 normalizeOutboundPayload 已保证 canonical)
|
|
324
|
+
const media = isMediaRef(data.media) ? data.media : undefined;
|
|
325
|
+
if (!media) {
|
|
326
|
+
logger.warn(formatCompact({
|
|
327
|
+
op: 'onebot12_outbound_media_dropped',
|
|
328
|
+
type,
|
|
329
|
+
reason: 'missing_media_ref',
|
|
330
|
+
}));
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
320
333
|
return { type, data: { ...oneBot12MediaExtra(data), ...mediaRefToOneBot12Fields(media) } };
|
|
321
334
|
}
|
|
322
335
|
|
|
@@ -324,10 +337,11 @@ function oneBot12MediaSegment(
|
|
|
324
337
|
* canonical Segment → OneBot 12 数组段:
|
|
325
338
|
* - mention → mention(`user_id: target`,`target: 'all'` → mention_all);
|
|
326
339
|
* - reply(`message_id`)→ reply(`message_id`);
|
|
327
|
-
* - image / voice / audio / video / file 的 MediaRef → url/data/path
|
|
340
|
+
* - image / voice / audio / video / file 的 MediaRef → url/data/path/file_id 字段,
|
|
341
|
+
* 无 canonical MediaRef 的媒体段 warn 后丢弃(返回 null);
|
|
328
342
|
* - 其余(已是 wire 形状的段、平台扩展段)原样透传。
|
|
329
343
|
*/
|
|
330
|
-
function canonicalToOneBotSegment(segment: OneBot12WireSegment): OneBot12Segment {
|
|
344
|
+
function canonicalToOneBotSegment(segment: OneBot12WireSegment): OneBot12Segment | null {
|
|
331
345
|
const data = segment.data ?? {};
|
|
332
346
|
switch (segment.type) {
|
|
333
347
|
case 'mention': {
|
|
@@ -352,7 +366,7 @@ function canonicalToOneBotSegment(segment: OneBot12WireSegment): OneBot12Segment
|
|
|
352
366
|
/**
|
|
353
367
|
* Wire-encode an already-rendered outbound payload into OneBot 12 message segments.
|
|
354
368
|
* 入参假定已经 core `normalizeOutboundPayload` 归一为 canonical Segment[];
|
|
355
|
-
*
|
|
369
|
+
* 媒体段只认 `data.media`(canonical MediaRef),无 MediaRef 的媒体段 warn 后丢弃。
|
|
356
370
|
*/
|
|
357
371
|
export function formatOutboundSegments(payload: unknown): OneBot12Segment[] {
|
|
358
372
|
if (typeof payload === 'string') {
|
|
@@ -380,7 +394,8 @@ export function formatOutboundSegments(payload: unknown): OneBot12Segment[] {
|
|
|
380
394
|
segs.push({ type: 'text', data: { text: item } });
|
|
381
395
|
continue;
|
|
382
396
|
}
|
|
383
|
-
|
|
397
|
+
const seg = canonicalToOneBotSegment(item);
|
|
398
|
+
if (seg) segs.push(seg);
|
|
384
399
|
}
|
|
385
400
|
return segs.length ? segs : [{ type: 'text', data: { text: '' } }];
|
|
386
401
|
}
|
|
@@ -446,7 +461,9 @@ async function uploadOneMediaSegment(
|
|
|
446
461
|
if (typeof segment.type !== 'string' || !ONEBOT12_MEDIA_TYPES.has(segment.type)) return item;
|
|
447
462
|
const data = segment.data ?? {};
|
|
448
463
|
if (typeof data.file_id === 'string' && data.file_id) return item;
|
|
449
|
-
|
|
464
|
+
// canonical MediaRef 唯一来源;无 MediaRef 的段原样保留,由
|
|
465
|
+
// formatOutboundSegments warn 后丢弃。
|
|
466
|
+
const media = isMediaRef(data.media) ? data.media : undefined;
|
|
450
467
|
if (!media) return item;
|
|
451
468
|
// kind=file:平台不透明引用(OB12 file_id 复投),直接物化为 file_id。
|
|
452
469
|
if (media.kind === 'file') {
|
package/src/ws-endpoint.ts
CHANGED
|
@@ -140,6 +140,11 @@ export class OneBot12WsEndpoint implements EndpointInstance {
|
|
|
140
140
|
return messageId;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
async recallMessage(messageId: string): Promise<void> {
|
|
144
|
+
if (!messageId) return;
|
|
145
|
+
await this.#callAction('delete_message', { message_id: messageId });
|
|
146
|
+
}
|
|
147
|
+
|
|
143
148
|
/** Public API for management surface / callers. */
|
|
144
149
|
callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
|
|
145
150
|
return this.#callAction(action, params);
|
package/src/wss-endpoint.ts
CHANGED
|
@@ -128,6 +128,11 @@ export class OneBot12WssEndpoint implements EndpointInstance {
|
|
|
128
128
|
return data?.message_id ?? '';
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
async recallMessage(messageId: string): Promise<void> {
|
|
132
|
+
if (!messageId) return;
|
|
133
|
+
await this.#callAction('delete_message', { message_id: messageId });
|
|
134
|
+
}
|
|
135
|
+
|
|
131
136
|
/** Public API for management surface / callers. */
|
|
132
137
|
callApi(action: string, params: Record<string, unknown> = {}): Promise<unknown> {
|
|
133
138
|
return this.#callAction(action, params);
|