Files

107 lines
3.6 KiB
JavaScript
Raw Permalink Normal View History

// dsh-plugin-mcn 视频/账号匹配工具(纯函数,拆分自 index.js)
import { homedir } from "node:os";
import { join } from "node:path";
import { readFileSync, existsSync } from "node:fs";
function normTitle(s) {
return String(s || "").toLowerCase().replace(/[\s#,。!?、,.!?·\-_~~::"“”()()【】]+/g, "");
}
function extractTopic(content) {
const s = String(content || "");
if (!s.trim()) return "";
const lines = s.replace(/\r\n/g, "\n").split("\n");
let start = -1;
for (let i = 0; i < lines.length; i++) {
const t = lines[i].trim();
if (/^#{1,4}\s*选题\s*$/.test(t)) { start = i + 1; break; }
}
if (start < 0) return "";
const out = [];
for (let i = start; i < lines.length; i++) {
const t = lines[i].trim();
if (/^#{1,6}\s/.test(t) && !/^#{1,6}\s*选题\s*$/.test(t)) break;
if (t) out.push(lines[i].trim());
}
return out.join("\n").trim();
}
function titleDistance(a, b) {
const m = a.length, n = b.length;
if (!m) return n;
if (!n) return m;
let prev = new Array(n + 1), curr = new Array(n + 1);
for (let j = 0; j <= n; j++) prev[j] = j;
for (let i = 1; i <= m; i++) {
curr[0] = i;
for (let j = 1; j <= n; j++) {
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
}
[prev, curr] = [curr, prev];
}
return prev[n];
}
function findVideoByFolder(d, accountId, vDir, vName) {
const rows = d.prepare(`SELECT id, aweme_id, video_title FROM account_videos WHERE account_id=?`).all(accountId);
const pick = (title) => {
const t = String(title || "").trim();
if (!t) return undefined;
const exact = rows.find((r) => String(r.video_title || "") === t);
if (exact) return exact;
const tn = normTitle(t);
if (!tn) return undefined;
let best = null;
for (const r of rows) {
const cn = normTitle(r.video_title);
if (!cn || cn.length < 6 || tn.length < 6) continue;
if (tn.includes(cn) || cn.includes(tn)) {
if (!best || tn.length > best.len) best = { id: r.id, aweme_id: r.aweme_id, len: tn.length };
}
}
if (best) return best;
let bestSim = null;
for (const r of rows) {
const cn = normTitle(r.video_title);
if (!cn) continue;
const ratio = 1 - titleDistance(tn, cn) / Math.max(tn.length, cn.length, 1);
if (ratio > 0.8 && (!bestSim || ratio > bestSim.ratio)) bestSim = { id: r.id, aweme_id: r.aweme_id, ratio };
}
return bestSim ? bestSim : undefined;
};
for (const jf of ["原视频解析.json", "content.json"]) {
const jp = join(vDir, jf);
if (!existsSync(jp)) continue;
try {
const jd = JSON.parse(readFileSync(jp, "utf8"));
const hit = pick(jd.title);
if (hit) return hit;
} catch (e) { /* 单个 json 解析失败不影响后续匹配 */ }
}
const first = String(vName || "").split("_")[0].trim();
if (first) {
const hit = pick(first);
if (hit) return hit;
}
return undefined;
}
function resolveAccountDirs(acc) {
const out = [];
if (acc && typeof acc.source_dir === "string" && acc.source_dir && existsSync(acc.source_dir)) out.push(acc.source_dir);
const legacy = acc && acc.account_name ? join(homedir(), "Desktop", "账号对标", acc.account_name) : null;
if (legacy && existsSync(legacy)) out.push(legacy);
return out;
}
function resolveAccountDir(acc) {
return resolveAccountDirs(acc)[0] || null;
}
/** 数值化(非法输入返回 0) */
function num(v) {
const n = Number(v);
return Number.isFinite(n) ? n : 0;
}
export { num, normTitle, extractTopic, titleDistance, findVideoByFolder, resolveAccountDirs, resolveAccountDir };