feat(types): danmaku match candidates, import payload, prefs filters
This commit is contained in:
parent
3467e397e7
commit
aab636eac7
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, expect, it } from "vitest";
|
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", () => {
|
describe("mountSchemas.create", () => {
|
||||||
it("拒绝缺 baseUrl", () => {
|
it("拒绝缺 baseUrl", () => {
|
||||||
|
|
@ -38,3 +38,76 @@ describe("playbackSchemas.report", () => {
|
||||||
).toBe(false);
|
).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 = `<?xml version="1.0"?><i><d p="0">hi</d></i>`;
|
||||||
|
|
||||||
|
it("接受含 <d 的弹幕 XML", () => {
|
||||||
|
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("拒绝不含 <d 的非弹幕内容", () => {
|
||||||
|
expect(
|
||||||
|
danmakuSchemas.import.safeParse({ mediaItemId: "m1", xml: "<html>no</html>" })
|
||||||
|
.success,
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
it("拒绝超过 20MB 的 xml", () => {
|
||||||
|
const huge = `<d ${"a".repeat(20_000_000)}`;
|
||||||
|
expect(danmakuSchemas.import.safeParse({ mediaItemId: "m1", xml: huge }).success).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it("fileName 超 255 拒绝", () => {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -148,17 +148,29 @@ export const scrapeSchemas = {
|
||||||
export const danmakuSchemas = {
|
export const danmakuSchemas = {
|
||||||
/** 开放网络:按媒体文件取弹幕 */
|
/** 开放网络:按媒体文件取弹幕 */
|
||||||
fetch: z.object({ mediaItemId: id }),
|
fetch: z.object({ mediaItemId: id }),
|
||||||
/** 手动弹弹play 文件识别(match),命中后写刮削元数据 + 弹幕身份 */
|
/** 手动识别:返回候选列表,不自动绑 */
|
||||||
match: z.object({ mediaItemId: id }),
|
match: z.object({ mediaItemId: id }),
|
||||||
/** 本地 XML:仅登记导入元数据(正文在客户端喂给播放器) */
|
/** 手选绑定 episodeId */
|
||||||
importMeta: z.object({
|
selectMatch: z.object({
|
||||||
mediaItemId: id,
|
mediaItemId: id,
|
||||||
byteSize: z.number().min(0).max(20_000_000),
|
episodeId: z.number().int().positive(),
|
||||||
|
}),
|
||||||
|
/** 导入本地 XML 正文(服务端持久化);粗检含弹幕 <d 节点 */
|
||||||
|
import: z.object({
|
||||||
|
mediaItemId: id,
|
||||||
|
xml: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(20_000_000)
|
||||||
|
.refine((v) => v.includes("<d "), "不是有效的弹幕 XML"),
|
||||||
|
fileName: z.string().max(255).optional(),
|
||||||
}),
|
}),
|
||||||
settings: z.object({
|
settings: z.object({
|
||||||
enabled: z.boolean().default(true),
|
enabled: z.boolean().default(true),
|
||||||
opacity: z.number().min(0).max(1).default(0.8),
|
opacity: z.number().min(0).max(1).default(0.8),
|
||||||
density: z.number().min(0.1).max(1).default(1),
|
density: z.number().min(0.1).max(1).default(1),
|
||||||
|
blockKeywords: z.array(z.string().max(50)).max(100).default([]),
|
||||||
|
blockTypes: z.array(z.enum(["scroll", "top", "bottom"])).default([]),
|
||||||
}),
|
}),
|
||||||
getSettings: z.object({}).optional(),
|
getSettings: z.object({}).optional(),
|
||||||
};
|
};
|
||||||
|
|
@ -183,10 +195,10 @@ export type BatchResult = {
|
||||||
};
|
};
|
||||||
export type DanmakuFetchOutput = {
|
export type DanmakuFetchOutput = {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
source: "open-network" | "cache" | "none";
|
source: "open-network" | "cache" | "local-xml" | "none";
|
||||||
/** Bilibili/弹弹play 兼容 XML 字符串;无弹幕则为 "" */
|
/** Bilibili/弹弹play 兼容 XML 字符串;无弹幕则为 "" */
|
||||||
xml: string;
|
xml: string;
|
||||||
message?: string;
|
message?: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 弹弹play match 命中的剧集(MatchResultV2 的刮削子集) */
|
/** 弹弹play match 命中的剧集(MatchResultV2 的刮削子集) */
|
||||||
|
|
@ -201,14 +213,17 @@ export type DandanMatchResult = {
|
||||||
export type DanmakuMatchOutput = {
|
export type DanmakuMatchOutput = {
|
||||||
ok: boolean;
|
ok: boolean;
|
||||||
matched: boolean;
|
matched: boolean;
|
||||||
message?: string;
|
message?: string | undefined;
|
||||||
result?: DandanMatchResult;
|
candidates: DandanMatchResult[];
|
||||||
|
highConfidence: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DanmakuSettingsOutput = {
|
export type DanmakuSettingsOutput = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
opacity: number;
|
opacity: number;
|
||||||
density: number;
|
density: number;
|
||||||
|
blockKeywords: string[];
|
||||||
|
blockTypes: Array<"scroll" | "top" | "bottom">;
|
||||||
/** 服务端是否已配置开放弹幕网络凭据(未配置时弹幕获取恒为空) */
|
/** 服务端是否已配置开放弹幕网络凭据(未配置时弹幕获取恒为空) */
|
||||||
openNetworkConfigured: boolean;
|
openNetworkConfigured: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue