v0.3.6: 恢复丢失的11个模块 + 接线基础设施

恢复内容:
- quark驱动拆解为7个子模块 (quark-api/auth/share/storage/cleanup/rename/ad-cleanup)
- 工具模块: utils/crypto, utils/logger, utils/proxy-agent
- 配置校验: config/startup-validator
- 接线: main.ts(checkStartup), credential.service.ts(加密Cookie), admin.routes.ts(代理测试)
- quark.driver.ts 从1533行巨兽瘦身到130行壳子
This commit is contained in:
2026-05-17 06:05:47 +08:00
parent 64b00661a2
commit 09be4c307e
22 changed files with 3802 additions and 1503 deletions

View File

@@ -0,0 +1,202 @@
// @ts-nocheck
const NOISE_CJK = '的了在是不有会可对所之也同与及但或如且乃而岂乎焉兮哉亦犹尚乃其若故盖诸焉欤' +
'么个着过把对为从以到说时要就这那和上人家下能出得发来年心开物力些长样吧啊哦嗯嚯哇咯呗哟嘿呵哈';
// ==================== Helpers ====================
/** Convert Chinese text to homophonic (substitute chars with same sound) */
function homophonicText(text) {
let result = '';
for (const ch of text) {
if (/[\u4e00-\u9fff]/.test(ch)) {
const homophone = HOMOPHONE_MAP[ch];
result += homophone || ch;
}
else {
result += ch;
}
}
return result;
}
/** Convert Chinese text to pinyin-initial-like string (each char → first pinyin letter or fallback) */
function pinyinLike(text) {
let result = '';
for (const ch of text) {
if (/[\u4e00-\u9fff]/.test(ch)) {
const homophone = HOMOPHONE_MAP[ch];
if (homophone) {
result += pinyinInitial(homophone);
}
else {
const code = ch.charCodeAt(0);
result += String.fromCharCode(97 + (code % 26));
}
}
else if (/[a-zA-Z0-9]/.test(ch)) {
result += ch;
}
else if (/[\s._-]/.test(ch)) {
result += '_';
}
}
return result.replace(/_+/g, '_').replace(/^_|_$/g, '');
}
/** Get pinyin initial (first letter of pinyin) for a Chinese character */
function pinyinInitial(ch) {
const code = ch.charCodeAt(0);
if (code >= 0x4E00 && code <= 0x9FFF) {
const initials = ['b', 'p', 'm', 'f', 'd', 't', 'n', 'l', 'g', 'k', 'h', 'j', 'q', 'x', 'zh', 'ch', 'sh', 'r', 'z', 'c', 's', 'y', 'w'];
const idx = Math.min(Math.floor((code - 0x4E00) / 700), initials.length - 1);
return initials[idx];
}
return ch.toLowerCase();
}
// ==================== Public API ====================
/**
* Anti-harmony rename for directories.
* 80%: light homophonic replacement, 20%: partial pinyin.
*/
export function magicRenameDir(dirName) {
const hash = crypto.createHash('md5').update(dirName + Date.now()).digest('hex').slice(0, 4);
let cleanName = dirName.trim().replace(/\s+/g, ' ');
if (!cleanName) {
return `media_${hash}`;
}
let baseName;
if (Math.random() < 0.2) {
// Partial pinyin: 30% of CJK chars → pinyin initial, 70% stay as-is
const chars = [...cleanName];
const result = [];
for (const ch of chars) {
if (/[\u4e00-\u9fff]/.test(ch) && Math.random() < 0.3) {
result.push(pinyinInitial(ch));
}
else {
result.push(ch);
}
}
baseName = result.join('');
}
else {
// Light homophonic: replace each CJK char, keep everything else as-is
const chars = [...cleanName];
const result = [];
for (const ch of chars) {
if (/[\u4e00-\u9fff]/.test(ch)) {
result.push(HOMOPHONE_MAP[ch] || ch);
}
else {
result.push(ch);
}
}
baseName = result.join('');
// Optional: insert 0-2 light noise chars (low probability)
const noiseCount = Math.random() < 0.3 ? (Math.random() < 0.5 ? 1 : 2) : 0;
for (let n = 0; n < noiseCount; n++) {
const pos = Math.floor(Math.random() * (baseName.length + 1));
const ink = NOISE_CJK[Math.floor(Math.random() * NOISE.length)];
baseName = baseName.slice(0, pos) + ink + baseName.slice(pos);
}
}
baseName = baseName.replace(/[^\u4e00-\u9fff\w]/g, '_');
baseName = baseName.replace(/_+/g, '_').replace(/^_|_$/g, '');
if (baseName.length > 30)
baseName = baseName.slice(0, 30);
return `${baseName}_${hash}`;
}
/**
* Anti-harmony rename for files.
* KEEPS: episode numbers, quality, language tags, original extension.
* REPLACES: Chinese title with homophonic/pinyin.
*/
export function magicRename(filename) {
const hash = crypto.createHash('md5').update(filename + Date.now()).digest('hex').slice(0, 8);
let ext = '';
const extMatch = filename.match(/\.[a-zA-Z0-9]+$/);
if (extMatch) {
ext = extMatch[0];
filename = filename.slice(0, -ext.length);
}
// Extract and REMEMBER: episode info, quality, language, year
const episodePatterns = [
{ regex: /第\s*(\d+)\s*[集话話話話话回章期]/, format: (m) => 'Ep' + m.replace(/[^\d]/g, '') },
{ regex: /Ep\d+|ep\d+/i, format: (m) => m.toUpperCase() },
{ regex: /Part\s*\d+/i, format: (m) => m.replace(/\s+/g, '') },
{ regex: /E\d{2,}/i, format: (m) => m.toUpperCase() },
];
let episodeTag = '';
for (const { regex, format } of episodePatterns) {
const m = filename.match(regex);
if (m) {
episodeTag = format(m[0]);
filename = filename.replace(m[0], '');
break;
}
}
// Extract and REMEMBER: quality tags
const qualityPattern = /\b(4k|1080p|1080P|2160p|720p|HD|BluRay|Blu-ray|HDR|WEB-DL|WEBRip|BDRip|REMUX|DV|Dovi|HEVC|x264|x265|H\.264|H\.265)\b/;
const qualityMatch = filename.match(qualityPattern);
const qualityTag = qualityMatch ? qualityMatch[0] : '';
if (qualityMatch)
filename = filename.replace(qualityMatch[0], '');
// Extract and REMEMBER: language tags
const langPattern = /\b(CHS|CHT|JP|EN|BIG5|GB|粤语|国语|日语|英语|中字|日字|英字|繁体中字)\b/;
const langMatch = filename.match(langPattern);
const langTag = langMatch ? langMatch[0] : '';
if (langMatch)
filename = filename.replace(langMatch[0], '');
// Extract and REMEMBER: year
const yearMatch = filename.match(/\b(20\d{2})\b/);
const yearTag = yearMatch ? yearMatch[0] : '';
if (yearMatch)
filename = filename.replace(yearMatch[0], '');
// Extract and REMEMBER: season info
const seasonMatch = filename.match(/第?\s*(\d+)\s*[季部期]/);
const seasonTag = seasonMatch ? `${seasonMatch[1]}` : '';
if (seasonMatch)
filename = filename.replace(seasonMatch[0], '');
// Now process the remaining name (mostly Chinese title)
filename = filename.replace(/[._\-【】\[\]()\s]+/g, '_').trim();
const useHomophonic = Math.random() > 0.5;
let titlePart;
if (useHomophonic) {
titlePart = homophonicText(filename);
titlePart = titlePart.replace(/[^\u4e00-\u9fff\wa-zA-Z0-9]/g, '_');
titlePart = titlePart.replace(/_+/g, '_').replace(/^_|_$/g, '');
if (titlePart.length > 15)
titlePart = titlePart.slice(0, 15);
}
else {
titlePart = pinyinLike(filename);
titlePart = titlePart.replace(/[^a-zA-Z0-9]/g, '_');
titlePart = titlePart.replace(/_+/g, '_').replace(/^_|_$/g, '');
if (titlePart.length > 15)
titlePart = titlePart.slice(0, 15);
}
// Remove sensitive keywords from title part
const sensitiveWords = /斗破|完美|凡人|仙逆|遮天|吞噬|大主宰|绝世|武动|星辰变|一念永恒|修罗|神墓|长生|剑来|诡秘|全职|斗罗|盘龙|雪鹰|莽荒纪|天珠变|神印王座|牧神记|沧元图|紫川|百炼成神|大王饶命|全球高考/ig;
titlePart = titlePart.replace(sensitiveWords, '');
titlePart = titlePart.replace(/_+/g, '_').replace(/^_|_$/g, '');
// Build preserved tags
const tags = [];
if (seasonTag)
tags.push(seasonTag);
if (episodeTag)
tags.push(episodeTag);
if (qualityTag)
tags.push(qualityTag.toUpperCase());
if (langTag)
tags.push(langTag);
if (yearTag)
tags.push(yearTag);
tags.push(hash); // Always add hash for uniqueness
const newExt = ext || '.bin';
const parts = [titlePart, ...tags].filter(Boolean);
let result = parts.join('_');
if (result.length > 80) {
result = result.slice(0, 80);
}
if (result.length < 10) {
const filler = crypto.randomBytes(4).toString('hex');
result = `${filler}_${result}`;
}
return result + newExt;
}