diff --git a/packages/types/src/media-schemas.test.ts b/packages/types/src/media-schemas.test.ts
index 8841623..b721ced 100644
--- a/packages/types/src/media-schemas.test.ts
+++ b/packages/types/src/media-schemas.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { mountSchemas, playbackSchemas, scrapeSchemas } from "./media-schemas.js";
+import { danmakuSchemas, mountSchemas, playbackSchemas, scrapeSchemas } from "./media-schemas.js";
describe("mountSchemas.create", () => {
it("拒绝缺 baseUrl", () => {
@@ -38,3 +38,76 @@ describe("playbackSchemas.report", () => {
).toBe(false);
});
});
+
+describe("danmakuSchemas.settings", () => {
+ it("缺省时填充默认值", () => {
+ const r = danmakuSchemas.settings.safeParse({});
+ expect(r.success).toBe(true);
+ if (!r.success) return;
+ expect(r.data).toEqual({
+ enabled: true,
+ opacity: 0.8,
+ density: 1,
+ blockKeywords: [],
+ blockTypes: [],
+ });
+ });
+ it("blockTypes 只接受 scroll|top|bottom", () => {
+ expect(
+ danmakuSchemas.settings.safeParse({ blockTypes: ["scroll", "top", "bottom"] })
+ .success,
+ ).toBe(true);
+ expect(danmakuSchemas.settings.safeParse({ blockTypes: ["reverse"] }).success).toBe(
+ false,
+ );
+ });
+ it("opacity 越界拒绝", () => {
+ expect(danmakuSchemas.settings.safeParse({ opacity: 1.5 }).success).toBe(false);
+ });
+});
+
+describe("danmakuSchemas.import", () => {
+ const validXml = `hi`;
+
+ it("接受含 {
+ const r = danmakuSchemas.import.safeParse({ mediaItemId: "m1", xml: validXml });
+ expect(r.success).toBe(true);
+ });
+ it("拒绝空 xml", () => {
+ expect(danmakuSchemas.import.safeParse({ mediaItemId: "m1", xml: "" }).success).toBe(
+ false,
+ );
+ });
+ it("拒绝不含 {
+ expect(
+ danmakuSchemas.import.safeParse({ mediaItemId: "m1", xml: "no" })
+ .success,
+ ).toBe(false);
+ });
+ it("拒绝超过 20MB 的 xml", () => {
+ const huge = ` {
+ expect(
+ danmakuSchemas.import.safeParse({
+ mediaItemId: "m1",
+ xml: validXml,
+ fileName: "a".repeat(256),
+ }).success,
+ ).toBe(false);
+ });
+});
+
+describe("danmakuSchemas.selectMatch", () => {
+ it("要求正整数 episodeId", () => {
+ expect(
+ danmakuSchemas.selectMatch.safeParse({ mediaItemId: "m1", episodeId: 42 }).success,
+ ).toBe(true);
+ expect(
+ danmakuSchemas.selectMatch.safeParse({ mediaItemId: "m1", episodeId: 0 }).success,
+ ).toBe(false);
+ });
+});
diff --git a/packages/types/src/media-schemas.ts b/packages/types/src/media-schemas.ts
index 9b650f9..e4d8451 100644
--- a/packages/types/src/media-schemas.ts
+++ b/packages/types/src/media-schemas.ts
@@ -148,17 +148,29 @@ export const scrapeSchemas = {
export const danmakuSchemas = {
/** 开放网络:按媒体文件取弹幕 */
fetch: z.object({ mediaItemId: id }),
- /** 手动弹弹play 文件识别(match),命中后写刮削元数据 + 弹幕身份 */
+ /** 手动识别:返回候选列表,不自动绑 */
match: z.object({ mediaItemId: id }),
- /** 本地 XML:仅登记导入元数据(正文在客户端喂给播放器) */
- importMeta: z.object({
+ /** 手选绑定 episodeId */
+ selectMatch: z.object({
mediaItemId: id,
- byteSize: z.number().min(0).max(20_000_000),
+ episodeId: z.number().int().positive(),
+ }),
+ /** 导入本地 XML 正文(服务端持久化);粗检含弹幕 v.includes(";
/** 服务端是否已配置开放弹幕网络凭据(未配置时弹幕获取恒为空) */
openNetworkConfigured: boolean;
};