Files
UHD_Media_Tool/apps/server/test/subtitles/subtitle-online.test.ts
T

123 lines
5.7 KiB
TypeScript

import {
SUBHD_DETAIL_PAGE,
SUBHD_SUB_PAGE,
ZIMUKU_CAPTCHA,
ZIMUKU_CAPTCHA_PAGE,
ZIMUKU_DETAIL_PAGE,
ZIMUKU_DL_PAGE,
ZIMUKU_SEARCH_PAGE,
ZIMUKU_SUBS_PAGE,
} from "@umt/testkit";
import { zipSync } from "fflate";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { downloadOnline, searchOnline } from "../../src/modules/subtitles/online/index.ts";
import { createHarness, type Harness, pgAvailable } from "../sources-identity/harness.ts";
// 在线字幕(docs/plan/subtitles.md 第 2 步):站点请求全部走注入的 fetch,页面是 2026-10-03 的真实截取;
// 字幕库先回验证码页,沙箱识别后带 security_verify_img 提交,再开搜索页;SubHD 走 prepare-download → down → api/sub/down
const pg = await pgAvailable();
const enc = (s: string) => new TextEncoder().encode(s);
describe.skipIf(!pg)("在线字幕:SubHD / 字幕库(注入 fetch)", () => {
let h: Harness;
const requests: string[] = [];
let verified = false;
const siteIds: Record<string, string> = {};
const html = (body: string, status = 200) =>
new Response(body, { status, headers: { "content-type": "text/html; charset=utf-8" } });
const zip = zipSync({
"The.Shawshank.Redemption.1994.chs&eng.ass": enc(
"[Script Info]\nDialogue: 0,0:00:01.00,0:00:02.00,Default,,0,0,0,,你好 Hello",
),
});
beforeAll(async () => {
h = await createHarness("subonline", {
fetch: async (input, init) => {
const u = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
const method = init?.method ?? "GET";
requests.push(`${method} ${u.host}${u.pathname}${u.search}`);
if (u.host === "zmk.pw") {
if (u.searchParams.has("security_verify_img")) {
const want = [...ZIMUKU_CAPTCHA.code].map((c) => c.charCodeAt(0).toString(16)).join("");
verified = u.searchParams.get("security_verify_img") === want;
return html("ok", 302);
}
if (!verified) return html(ZIMUKU_CAPTCHA_PAGE, 404);
if (u.pathname === "/search") return html(ZIMUKU_SEARCH_PAGE);
if (/^\/subs\/\d+\.html$/.test(u.pathname)) return html(ZIMUKU_SUBS_PAGE);
if (u.pathname === "/detail/203404.html") return html(ZIMUKU_DETAIL_PAGE);
if (u.pathname === "/dld/203404.html") return html(ZIMUKU_DL_PAGE);
if (u.pathname.startsWith("/download/"))
return new Response(zip, {
headers: {
"content-type": "application/octet-stream",
"content-disposition": 'attachment; filename="sub.zip"',
},
});
}
if (u.host === "subhd.tv") {
if (u.pathname === "/d/1292052") return html(SUBHD_DETAIL_PAGE);
if (u.pathname.startsWith("/a/")) return html(SUBHD_SUB_PAGE);
if (u.pathname === "/api/sub/prepare-download") return Response.json({ success: true, url: "/down/Fp90EG" });
if (u.pathname === "/down/Fp90EG") return html("<html></html>");
if (u.pathname === "/api/sub/down")
return Response.json({ success: true, pass: true, url: "https://dl.subhd.example/x/1.srt" });
}
if (u.host === "dl.subhd.example")
return new Response(enc("1\n00:00:01,000 --> 00:00:02,000\n你好\n"), {
headers: { "content-disposition": 'attachment; filename="Shawshank.chs.srt"' },
});
return html("not found", 404);
},
});
for (const [key, name, url] of [
["subhd", "SubHD", "https://subhd.tv/"],
["zimuku", "字幕库", "https://zmk.pw/"],
] as const) {
const r = await h.db
.insertInto("site")
.values({ key, name, kind: "subtitle", adapter: "subtitle", base_url: url } as never)
.returning("id")
.executeTakeFirstOrThrow();
siteIds[key] = r.id;
}
}, 120_000);
afterAll(async () => {
await h?.close();
});
// 字幕库样例是《葬送的芙莉莲》的搜索结果;SubHD 按豆瓣 ID 开条目页,不按标题过滤
const q = { keywords: ["葬送的芙莉莲"], doubanId: "1292052", season: null, episode: null };
it("搜索:SubHD 有豆瓣 ID 直接开条目页;字幕库先过验证码再搜;结果按语言与下载量排序", async () => {
const r = await searchOnline(h.ctx, h.rt, q);
expect(r.errors).toEqual([]);
expect(r.noSites).toBe(false);
expect(verified).toBe(true);
expect(requests).toContain("GET subhd.tv/d/1292052");
const zim = r.results.filter((x) => x.siteKey === "zimuku");
const sub = r.results.filter((x) => x.siteKey === "subhd");
expect(zim.length).toBeGreaterThan(0);
expect(sub.length).toBeGreaterThan(0);
// 双语排在前面
expect(r.results[0]!.languages).toContain("双语");
expect(zim.find((x) => x.ref === "/detail/203404.html")).toMatchObject({ siteName: "字幕库", downloads: 17000 });
});
it("下载:字幕库走下载页取文件;SubHD 走 prepare-download → api/sub/down", async () => {
const z = await downloadOnline(h.ctx, h.rt, siteIds.zimuku!, "/detail/203404.html");
expect(z).toMatchObject({ name: "sub.zip", siteKey: "zimuku", pageUrl: "https://zmk.pw/detail/203404.html" });
const s = await downloadOnline(h.ctx, h.rt, siteIds.subhd!, "/a/Fp90EG");
expect(s.name).toBe("Shawshank.chs.srt");
expect(requests).toContain("POST subhd.tv/api/sub/prepare-download");
});
it("字幕站停用:不搜;下载报错", async () => {
await h.db.updateTable("site").set({ enabled: false }).where("kind", "=", "subtitle").execute();
const r = await searchOnline(h.ctx, h.rt, q);
expect(r.noSites).toBe(true);
await expect(downloadOnline(h.ctx, h.rt, siteIds.subhd!, "/a/x")).rejects.toThrow(/已停用/);
});
});