feat(ui): mail.ru linkleri için eşleştirme ve isim düzenlemesi eklendi
- Dosya eşleştirme arayüzü bağımsız `MatchModal` bileşenine taşındı - `Files.svelte` ve `Transfers.svelte` yeni bileşen kullanılarak güncellendi - Mail.ru indirmeleri için dizi adı, sezon ve bölüm eşleştirme özelliği eklendi - `POST /api/mailru/match` endpointi ile metadata eşleştirme backend desteği sağlandı - Dosya isimleri "DiziAdi.S01E01.mp4" formatında kaydedilmeye başlandı
This commit is contained in:
135
server/server.js
135
server/server.js
@@ -1188,6 +1188,29 @@ function sanitizeFileName(name) {
|
||||
return replaced || "mailru_video.mp4";
|
||||
}
|
||||
|
||||
function formatMailRuSeriesFilename(title, season, episode) {
|
||||
const rawTitle = String(title || "").trim();
|
||||
const parts = rawTitle
|
||||
.replace(/[\\/:*?"<>|]+/g, "")
|
||||
.replace(/[\s._-]+/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
.split(" ")
|
||||
.filter(Boolean);
|
||||
const titled = parts
|
||||
.map((word) => {
|
||||
if (!word) return "";
|
||||
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
||||
})
|
||||
.filter(Boolean)
|
||||
.join(".");
|
||||
const safeTitle = titled || "Anime";
|
||||
const seasonNum = Number(season) || 1;
|
||||
const episodeNum = Number(episode) || 1;
|
||||
const code = `S${String(seasonNum).padStart(2, "0")}xE${String(episodeNum).padStart(2, "0")}`;
|
||||
return sanitizeFileName(`${safeTitle}.${code}.mp4`);
|
||||
}
|
||||
|
||||
async function resolveMailRuDirectUrl(rawUrl) {
|
||||
const normalized = normalizeMailRuUrl(rawUrl);
|
||||
if (!normalized) return null;
|
||||
@@ -1411,11 +1434,11 @@ function finalizeMailRuJob(job, exitCode) {
|
||||
job.progress = 1;
|
||||
job.state = "completed";
|
||||
job.error = null;
|
||||
const relPath = path
|
||||
.join(job.folderId, job.fileName)
|
||||
.replace(/\\/g, "/");
|
||||
const relPath = job.savePath === DOWNLOAD_DIR
|
||||
? String(job.fileName || "")
|
||||
: path.join(job.folderId || "", job.fileName || "").replace(/\\/g, "/");
|
||||
attachMailRuThumbnail(job, filePath, relPath);
|
||||
broadcastFileUpdate(job.folderId);
|
||||
broadcastFileUpdate(relPath || "downloads");
|
||||
scheduleSnapshotBroadcast();
|
||||
broadcastDiskSpace();
|
||||
console.log(`✅ Mail.ru indirmesi tamamlandı: ${job.title}`);
|
||||
@@ -1493,13 +1516,13 @@ function launchMailRuJob(job) {
|
||||
async function startMailRuDownload(url) {
|
||||
const normalized = normalizeMailRuUrl(url);
|
||||
if (!normalized) return null;
|
||||
const folderId = `mailru_${Date.now().toString(36)}`;
|
||||
const savePath = path.join(DOWNLOAD_DIR, folderId);
|
||||
fs.mkdirSync(savePath, { recursive: true });
|
||||
const folderId = null;
|
||||
const savePath = DOWNLOAD_DIR;
|
||||
|
||||
const jobId = `mailru_${Date.now().toString(36)}`;
|
||||
const job = {
|
||||
id: folderId,
|
||||
infoHash: folderId,
|
||||
id: jobId,
|
||||
infoHash: jobId,
|
||||
type: "mailru",
|
||||
url: normalized,
|
||||
directUrl: null,
|
||||
@@ -1508,7 +1531,7 @@ async function startMailRuDownload(url) {
|
||||
added: Date.now(),
|
||||
title: null,
|
||||
fileName: null,
|
||||
state: "resolving",
|
||||
state: "awaiting_match",
|
||||
progress: 0,
|
||||
downloaded: 0,
|
||||
totalBytes: 0,
|
||||
@@ -1518,24 +1541,35 @@ async function startMailRuDownload(url) {
|
||||
thumbnail: null,
|
||||
process: null,
|
||||
error: null,
|
||||
debug: { binary: null, args: null, logs: [] }
|
||||
debug: { binary: null, args: null, logs: [] },
|
||||
match: null
|
||||
};
|
||||
|
||||
mailruJobs.set(job.id, job);
|
||||
scheduleSnapshotBroadcast();
|
||||
console.log(`▶️ Mail.ru indirmesi başlatıldı: ${job.url}`);
|
||||
console.log(`▶️ Mail.ru indirimi eşleştirme bekliyor: ${job.url}`);
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
async function beginMailRuDownload(job) {
|
||||
if (!job || job.state !== "awaiting_match") return false;
|
||||
job.state = "resolving";
|
||||
scheduleSnapshotBroadcast();
|
||||
try {
|
||||
const directUrl = await resolveMailRuDirectUrl(normalized);
|
||||
const directUrl = await resolveMailRuDirectUrl(job.url);
|
||||
if (!directUrl) {
|
||||
throw new Error("Mail.ru video URL'si çözümlenemedi");
|
||||
}
|
||||
job.directUrl = directUrl;
|
||||
const urlObj = new URL(directUrl);
|
||||
const filename = sanitizeFileName(path.basename(urlObj.pathname));
|
||||
job.fileName = filename || `mailru_${Date.now()}.mp4`;
|
||||
if (!job.fileName) {
|
||||
const urlObj = new URL(directUrl);
|
||||
const filename = sanitizeFileName(path.basename(urlObj.pathname));
|
||||
job.fileName = filename || `mailru_${Date.now()}.mp4`;
|
||||
}
|
||||
job.title = job.fileName;
|
||||
job.state = "downloading";
|
||||
scheduleSnapshotBroadcast();
|
||||
try {
|
||||
const headResp = await fetch(directUrl, { method: "HEAD" });
|
||||
const length = Number(headResp.headers.get("content-length")) || 0;
|
||||
@@ -1568,13 +1602,13 @@ async function startMailRuDownload(url) {
|
||||
}
|
||||
startMailRuProgressPolling(job);
|
||||
launchMailRuJob(job);
|
||||
return true;
|
||||
} catch (err) {
|
||||
job.state = "error";
|
||||
job.error = err?.message || "Mail.ru indirimi başlatılamadı";
|
||||
scheduleSnapshotBroadcast();
|
||||
return false;
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
function findYoutubeInfoJson(savePath) {
|
||||
@@ -1737,10 +1771,18 @@ function removeMailRuJob(jobId, { removeFiles = true } = {}) {
|
||||
}
|
||||
mailruJobs.delete(jobId);
|
||||
let filesRemoved = false;
|
||||
if (removeFiles && job.savePath && fs.existsSync(job.savePath)) {
|
||||
if (removeFiles) {
|
||||
try {
|
||||
fs.rmSync(job.savePath, { recursive: true, force: true });
|
||||
filesRemoved = true;
|
||||
if (job.savePath === DOWNLOAD_DIR && job.fileName) {
|
||||
const filePath = path.join(job.savePath, job.fileName);
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.rmSync(filePath, { force: true });
|
||||
filesRemoved = true;
|
||||
}
|
||||
} else if (job.savePath && fs.existsSync(job.savePath)) {
|
||||
fs.rmSync(job.savePath, { recursive: true, force: true });
|
||||
filesRemoved = true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("Mail.ru dosyası silinemedi:", err.message);
|
||||
}
|
||||
@@ -5746,7 +5788,7 @@ app.delete("/api/file", requireAuth, (req, res) => {
|
||||
const isDirectory = stats.isDirectory();
|
||||
const relWithinRoot = safePath.split(/[\\/]/).slice(1).join("/");
|
||||
let trashEntry = null;
|
||||
if (folderId && rootDir) {
|
||||
if (folderId && rootDir && fs.existsSync(rootDir) && fs.statSync(rootDir).isDirectory()) {
|
||||
const infoBeforeDelete = readInfoForRoot(folderId);
|
||||
mediaFlags = detectMediaFlagsForPath(
|
||||
infoBeforeDelete,
|
||||
@@ -5757,7 +5799,7 @@ app.delete("/api/file", requireAuth, (req, res) => {
|
||||
mediaFlags = { movies: false, tv: false };
|
||||
}
|
||||
|
||||
if (folderId && rootDir) {
|
||||
if (folderId && rootDir && fs.existsSync(rootDir) && fs.statSync(rootDir).isDirectory()) {
|
||||
trashEntry = addTrashEntry(folderId, {
|
||||
path: relWithinRoot,
|
||||
originalPath: safePath,
|
||||
@@ -6737,6 +6779,53 @@ app.post("/api/mailru/download", requireAuth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/mailru/match", requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { jobId, metadata, season, episode } = req.body || {};
|
||||
if (!jobId || !metadata) {
|
||||
return res.status(400).json({ ok: false, error: "jobId ve metadata gerekli." });
|
||||
}
|
||||
const job = mailruJobs.get(jobId);
|
||||
if (!job) {
|
||||
return res.status(404).json({ ok: false, error: "Mail.ru işi bulunamadı." });
|
||||
}
|
||||
if (job.state !== "awaiting_match") {
|
||||
if (job.match && job.fileName) {
|
||||
return res.json({
|
||||
ok: true,
|
||||
jobId: job.id,
|
||||
fileName: job.fileName,
|
||||
alreadyMatched: true
|
||||
});
|
||||
}
|
||||
return res.status(400).json({ ok: false, error: "Mail.ru işi eşleştirme beklemiyor." });
|
||||
}
|
||||
const safeSeason = Number(season) || 1;
|
||||
const safeEpisode = Number(episode) || 1;
|
||||
const title = metadata.title || metadata.name || "Anime";
|
||||
job.match = {
|
||||
id: metadata.id || null,
|
||||
title,
|
||||
season: safeSeason,
|
||||
episode: safeEpisode,
|
||||
matchedAt: Date.now()
|
||||
};
|
||||
job.fileName = formatMailRuSeriesFilename(title, safeSeason, safeEpisode);
|
||||
job.title = job.fileName;
|
||||
const started = await beginMailRuDownload(job);
|
||||
if (!started) {
|
||||
return res.status(500).json({ ok: false, error: job.error || "Mail.ru indirimi başlatılamadı." });
|
||||
}
|
||||
console.log(`✅ Mail.ru eşleştirme tamamlandı: ${job.fileName}`);
|
||||
res.json({ ok: true, jobId: job.id, fileName: job.fileName });
|
||||
} catch (err) {
|
||||
res.status(500).json({
|
||||
ok: false,
|
||||
error: err?.message || "Mail.ru eşleştirme başarısız oldu."
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// --- 🎫 YouTube cookies yönetimi ---
|
||||
app.get("/api/youtube/cookies", requireAuth, (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user