Files
UHD_Media_Tool/scripts/mp-sync/lib.mjs
T
rootandClaude Opus 5.5 d9f2af2f3c feat(adapters): port MoviePilot open site framework (7 spiders, 20 user parsers) + MovieClaw v0.27.0 site definitions + mp-sync tools (W1-3c)
- sites/spiders: Haidan/HDDolby/RousiPro/SunnyPT/TNode/TorrentLeech/Yema (+mTorrent wrapper), specialized SiteAdapter, indirect download resolved at download time
- sites/user-parsers: SiteParserBase + all 20 MP SiteSchema parsers (lxml XPath via xpath+xmldom), refreshSiteUserData with schema fallback
- sites/definitions: zod SiteDefinition schemas, 23 MovieClaw v0.27.0 (MIT) definitions via scripts/mp-sync/movieclaw-import.mjs, registry getSiteDefinition/listSiteDefinitions, NOTICE
- scripts/mp-sync: ledger.json (upstream sha256), check.mjs, build-ledger.mjs; docs/porting/mp-sync-ledger.md
- license-and-provenance: add copied-mit kind, re-anchor MovieClaw rows to v0.27.0

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-27 18:39:27 +08:00

107 lines
3.5 KiB
JavaScript

// mp-sync 公共函数(Node 24 / Deno 2 均可运行,无第三方依赖)
import { execFileSync } from "node:child_process";
import { createHash } from "node:crypto";
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import { fileURLToPath } from "node:url";
export const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
export const LEDGER_PATH = join(ROOT, "scripts/mp-sync/ledger.json");
export const MANUAL_PATH = join(ROOT, "scripts/mp-sync/ledger.manual.json");
export const DOC_PATH = join(ROOT, "docs/porting/mp-sync-ledger.md");
export const DEFAULT_MP_REPO = "/opt/moviepilot-plus/upstream/MoviePilot";
/** 需要监视"新增文件"的上游目录(MP 开源站点框架) */
export const WATCH_DIRS = ["app/modules/indexer/spider", "app/modules/indexer/parser"];
/** 扫描 @mp-source 标签的本项目目录 */
export const SCAN_DIRS = ["packages/adapters/src", "packages/testkit/src"];
export function parseArgs(argv) {
const out = { _: [] };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a.startsWith("--")) {
const k = a.slice(2);
const v = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : true;
out[k] = v;
} else out._.push(a);
}
return out;
}
export function git(repo, ...args) {
return execFileSync("git", ["-C", repo, ...args], {
encoding: "buffer",
maxBuffer: 256 * 1024 * 1024,
stdio: ["ignore", "pipe", "ignore"],
});
}
export function tryGit(repo, ...args) {
try {
return git(repo, ...args);
} catch {
return null;
}
}
export function sha256(buf) {
return createHash("sha256").update(buf).digest("hex");
}
export function resolveCommit(repo, ref) {
const out = tryGit(repo, "rev-parse", "--verify", `${ref}^{commit}`);
if (!out) throw new Error(`上游仓库 ${repo} 中找不到提交 ${ref}(浅克隆需先 git fetch --depth=1 origin <commit>)`);
return out.toString().trim();
}
/** 上游文件在某提交的内容 sha256;不存在返回 null */
export function upstreamHash(repo, commit, path) {
const buf = tryGit(repo, "show", `${commit}:${path}`);
return buf ? sha256(buf) : null;
}
export function listUpstreamDir(repo, commit, dir) {
const out = tryGit(repo, "ls-tree", "--name-only", `${commit}:${dir}`);
if (!out) return [];
return out
.toString()
.split("\n")
.filter((f) => f.endsWith(".py"))
.map((f) => `${dir}/${f}`);
}
function walk(dir, acc) {
for (const name of readdirSync(dir)) {
if (name === "node_modules") continue;
const p = join(dir, name);
if (statSync(p).isDirectory()) walk(p, acc);
else if (/\.(ts|mjs)$/.test(name)) acc.push(p);
}
return acc;
}
/** 扫描源码文件头 `@mp-source <path> @ <commit>` 标签 */
export function scanTags() {
const tags = [];
for (const d of SCAN_DIRS) {
for (const file of walk(join(ROOT, d), [])) {
const text = readFileSync(file, "utf8");
for (const m of text.matchAll(/@mp-source\s+(\S+)\s+@\s+([0-9a-f]{7,40})/g)) {
tags.push({ upstream: m[1], commit: m[2], ours: relative(ROOT, file) });
}
}
}
return tags;
}
/** 用仓库 biome 格式化(与 lint 一致,生成物重跑幂等);不可用时原样返回 */
export function biomeFormat(text, fakePath) {
const bin = join(ROOT, "node_modules/.bin/biome");
if (!existsSync(bin)) return text;
try {
return execFileSync(bin, ["format", `--stdin-file-path=${fakePath}`], { input: text, encoding: "utf8", cwd: ROOT });
} catch {
return text;
}
}