Dosya taşıma ve "Full Rescan" özelliği eklendi.
This commit is contained in:
517
server/server.js
517
server/server.js
@@ -2370,6 +2370,125 @@ function pruneInfoForDirectory(rootFolder, relativeDir) {
|
||||
}
|
||||
}
|
||||
|
||||
function writeInfoForRoot(rootFolder, info) {
|
||||
if (!rootFolder || !info) return;
|
||||
const safe = sanitizeRelative(rootFolder);
|
||||
if (!safe) return;
|
||||
const target = path.join(DOWNLOAD_DIR, safe, INFO_FILENAME);
|
||||
try {
|
||||
info.updatedAt = Date.now();
|
||||
fs.writeFileSync(target, JSON.stringify(info, null, 2), "utf-8");
|
||||
} catch (err) {
|
||||
console.warn(`⚠️ info.json güncellenemedi (${target}): ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function moveInfoDataBetweenRoots(oldRoot, newRoot, oldRel, newRel, isDirectory) {
|
||||
if (!oldRoot || !newRoot) return false;
|
||||
const oldInfo = readInfoForRoot(oldRoot);
|
||||
if (!oldInfo) return false;
|
||||
|
||||
let newInfo = readInfoForRoot(newRoot);
|
||||
if (!newInfo) {
|
||||
const rootDir = path.join(DOWNLOAD_DIR, sanitizeRelative(newRoot));
|
||||
newInfo =
|
||||
upsertInfoFile(rootDir, {
|
||||
folder: newRoot,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now()
|
||||
}) || readInfoForRoot(newRoot) || { folder: newRoot };
|
||||
}
|
||||
|
||||
const normalizedOldRel = normalizeTrashPath(oldRel);
|
||||
const normalizedNewRel = normalizeTrashPath(newRel);
|
||||
const shouldMove = (normalizedKey) => {
|
||||
if (!normalizedOldRel) return true;
|
||||
if (normalizedKey === normalizedOldRel) return true;
|
||||
return (
|
||||
isDirectory &&
|
||||
normalizedOldRel &&
|
||||
normalizedKey.startsWith(`${normalizedOldRel}/`)
|
||||
);
|
||||
};
|
||||
const mapKey = (normalizedKey) => {
|
||||
const suffix = normalizedOldRel
|
||||
? normalizedKey.slice(normalizedOldRel.length).replace(/^\/+/, "")
|
||||
: normalizedKey;
|
||||
if (!normalizedNewRel) return suffix;
|
||||
return `${normalizedNewRel}${suffix ? `/${suffix}` : ""}`;
|
||||
};
|
||||
|
||||
let moved = false;
|
||||
|
||||
if (oldInfo.files && typeof oldInfo.files === "object") {
|
||||
const remaining = {};
|
||||
for (const [key, value] of Object.entries(oldInfo.files)) {
|
||||
const normalizedKey = normalizeTrashPath(key);
|
||||
if (!shouldMove(normalizedKey)) {
|
||||
remaining[key] = value;
|
||||
continue;
|
||||
}
|
||||
const nextKey = mapKey(normalizedKey);
|
||||
if (!newInfo.files || typeof newInfo.files !== "object") {
|
||||
newInfo.files = {};
|
||||
}
|
||||
newInfo.files[nextKey] = value;
|
||||
moved = true;
|
||||
}
|
||||
if (Object.keys(remaining).length > 0) oldInfo.files = remaining;
|
||||
else delete oldInfo.files;
|
||||
}
|
||||
|
||||
if (oldInfo.seriesEpisodes && typeof oldInfo.seriesEpisodes === "object") {
|
||||
const remainingEpisodes = {};
|
||||
for (const [key, value] of Object.entries(oldInfo.seriesEpisodes)) {
|
||||
const normalizedKey = normalizeTrashPath(key);
|
||||
if (!shouldMove(normalizedKey)) {
|
||||
remainingEpisodes[key] = value;
|
||||
continue;
|
||||
}
|
||||
const nextKey = mapKey(normalizedKey);
|
||||
if (
|
||||
!newInfo.seriesEpisodes ||
|
||||
typeof newInfo.seriesEpisodes !== "object"
|
||||
) {
|
||||
newInfo.seriesEpisodes = {};
|
||||
}
|
||||
newInfo.seriesEpisodes[nextKey] = value;
|
||||
moved = true;
|
||||
}
|
||||
if (Object.keys(remainingEpisodes).length > 0) {
|
||||
oldInfo.seriesEpisodes = remainingEpisodes;
|
||||
} else {
|
||||
delete oldInfo.seriesEpisodes;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldInfo.primaryVideoPath) {
|
||||
const normalizedPrimary = normalizeTrashPath(oldInfo.primaryVideoPath);
|
||||
if (shouldMove(normalizedPrimary)) {
|
||||
const nextPrimary = mapKey(normalizedPrimary);
|
||||
newInfo.primaryVideoPath = nextPrimary;
|
||||
if (oldInfo.primaryMediaInfo !== undefined) {
|
||||
newInfo.primaryMediaInfo = oldInfo.primaryMediaInfo;
|
||||
delete oldInfo.primaryMediaInfo;
|
||||
}
|
||||
if (oldInfo.movieMatch !== undefined) {
|
||||
newInfo.movieMatch = oldInfo.movieMatch;
|
||||
delete oldInfo.movieMatch;
|
||||
}
|
||||
delete oldInfo.primaryVideoPath;
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!moved) return false;
|
||||
|
||||
writeInfoForRoot(oldRoot, oldInfo);
|
||||
writeInfoForRoot(newRoot, newInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
function renameInfoPaths(rootFolder, oldRel, newRel) {
|
||||
if (!rootFolder) return;
|
||||
const info = readInfoForRoot(rootFolder);
|
||||
@@ -3405,6 +3524,149 @@ app.delete("/api/file", requireAuth, (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// --- 🚚 Dosya veya klasörü hedef klasöre taşıma ---
|
||||
app.post("/api/file/move", requireAuth, (req, res) => {
|
||||
try {
|
||||
const { sourcePath, targetDirectory } = req.body || {};
|
||||
if (!sourcePath) {
|
||||
return res.status(400).json({ error: "sourcePath gerekli" });
|
||||
}
|
||||
|
||||
const normalizedSource = normalizeTrashPath(sourcePath);
|
||||
if (!normalizedSource) {
|
||||
return res.status(400).json({ error: "Geçersiz sourcePath" });
|
||||
}
|
||||
|
||||
const sourceFullPath = path.join(DOWNLOAD_DIR, normalizedSource);
|
||||
if (!fs.existsSync(sourceFullPath)) {
|
||||
return res.status(404).json({ error: "Kaynak öğe bulunamadı" });
|
||||
}
|
||||
|
||||
const sourceStats = fs.statSync(sourceFullPath);
|
||||
const isDirectory = sourceStats.isDirectory();
|
||||
|
||||
const normalizedTargetDir = targetDirectory
|
||||
? normalizeTrashPath(targetDirectory)
|
||||
: "";
|
||||
|
||||
if (normalizedTargetDir) {
|
||||
const targetDirFullPath = path.join(DOWNLOAD_DIR, normalizedTargetDir);
|
||||
if (!fs.existsSync(targetDirFullPath)) {
|
||||
return res.status(404).json({ error: "Hedef klasör bulunamadı" });
|
||||
}
|
||||
}
|
||||
|
||||
const posixPath = path.posix;
|
||||
const sourceName = posixPath.basename(normalizedSource);
|
||||
const newRelativePath = normalizedTargetDir
|
||||
? posixPath.join(normalizedTargetDir, sourceName)
|
||||
: sourceName;
|
||||
|
||||
if (newRelativePath === normalizedSource) {
|
||||
return res.json({ success: true, unchanged: true });
|
||||
}
|
||||
|
||||
if (
|
||||
isDirectory &&
|
||||
(newRelativePath === normalizedSource ||
|
||||
newRelativePath.startsWith(`${normalizedSource}/`))
|
||||
) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Bir klasörü kendi içine taşıyamazsın." });
|
||||
}
|
||||
|
||||
const newFullPath = path.join(DOWNLOAD_DIR, newRelativePath);
|
||||
if (fs.existsSync(newFullPath)) {
|
||||
return res
|
||||
.status(409)
|
||||
.json({ error: "Hedef konumda aynı isimde bir öğe zaten var" });
|
||||
}
|
||||
|
||||
const destinationParent = path.dirname(newFullPath);
|
||||
if (!fs.existsSync(destinationParent)) {
|
||||
fs.mkdirSync(destinationParent, { recursive: true });
|
||||
}
|
||||
|
||||
const sourceRoot = rootFromRelPath(normalizedSource);
|
||||
const destRoot = rootFromRelPath(newRelativePath);
|
||||
|
||||
const sourceSegments = relPathToSegments(normalizedSource);
|
||||
const destSegments = relPathToSegments(newRelativePath);
|
||||
const sourceRelWithinRoot = sourceSegments.slice(1).join("/");
|
||||
const destRelWithinRoot = destSegments.slice(1).join("/");
|
||||
|
||||
if (sourceRoot && !sourceRelWithinRoot) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "Kök klasör bu yöntemle taşınamaz" });
|
||||
}
|
||||
|
||||
fs.renameSync(sourceFullPath, newFullPath);
|
||||
|
||||
const sameRoot =
|
||||
sourceRoot && destRoot && sourceRoot === destRoot && sourceRoot !== null;
|
||||
const movedAcrossRoots =
|
||||
sourceRoot && destRoot && sourceRoot !== destRoot && sourceRoot !== null;
|
||||
|
||||
if (sameRoot) {
|
||||
renameInfoPaths(sourceRoot, sourceRelWithinRoot, destRelWithinRoot);
|
||||
renameSeriesDataPaths(sourceRoot, sourceRelWithinRoot, destRelWithinRoot);
|
||||
renameTrashEntries(sourceRoot, sourceRelWithinRoot, destRelWithinRoot);
|
||||
if (isDirectory) {
|
||||
removeThumbnailsForDirectory(sourceRoot, sourceRelWithinRoot);
|
||||
} else {
|
||||
removeThumbnailsForPath(normalizedSource);
|
||||
}
|
||||
trashStateCache.delete(sourceRoot);
|
||||
} else {
|
||||
if (movedAcrossRoots) {
|
||||
moveInfoDataBetweenRoots(
|
||||
sourceRoot,
|
||||
destRoot,
|
||||
sourceRelWithinRoot,
|
||||
destRelWithinRoot,
|
||||
isDirectory
|
||||
);
|
||||
if (isDirectory) {
|
||||
removeThumbnailsForDirectory(sourceRoot, sourceRelWithinRoot);
|
||||
} else {
|
||||
removeThumbnailsForPath(normalizedSource);
|
||||
}
|
||||
if (sourceRoot) trashStateCache.delete(sourceRoot);
|
||||
if (destRoot) trashStateCache.delete(destRoot);
|
||||
} else if (sourceRoot) {
|
||||
if (isDirectory) {
|
||||
pruneInfoForDirectory(sourceRoot, sourceRelWithinRoot);
|
||||
removeThumbnailsForDirectory(sourceRoot, sourceRelWithinRoot);
|
||||
} else {
|
||||
pruneInfoEntry(sourceRoot, sourceRelWithinRoot);
|
||||
removeThumbnailsForPath(normalizedSource);
|
||||
}
|
||||
trashStateCache.delete(sourceRoot);
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceRoot) {
|
||||
broadcastFileUpdate(sourceRoot);
|
||||
}
|
||||
if (destRoot && destRoot !== sourceRoot) {
|
||||
broadcastFileUpdate(destRoot);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
newPath: newRelativePath,
|
||||
rootFolder: destRoot || null,
|
||||
isDirectory,
|
||||
movedAcrossRoots: Boolean(movedAcrossRoots)
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("❌ File move error:", err);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// --- 📁 Dosya gezgini (🆕 type ve url alanları eklendi; resim thumb'ı) ---
|
||||
app.get("/api/files", requireAuth, (req, res) => {
|
||||
// --- 🧩 .ignoreFiles içeriğini oku ---
|
||||
@@ -3859,41 +4121,117 @@ app.get("/api/movies", requireAuth, (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
async function rebuildMovieMetadata({ clearCache = false } = {}) {
|
||||
if (!TMDB_API_KEY) {
|
||||
throw new Error("TMDB API key tanımlı değil.");
|
||||
}
|
||||
|
||||
if (clearCache && fs.existsSync(MOVIE_DATA_ROOT)) {
|
||||
try {
|
||||
fs.rmSync(MOVIE_DATA_ROOT, { recursive: true, force: true });
|
||||
console.log("🧹 Movie cache temizlendi.");
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`⚠️ Movie cache temizlenemedi (${MOVIE_DATA_ROOT}): ${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fs.mkdirSync(MOVIE_DATA_ROOT, { recursive: true });
|
||||
|
||||
const dirEntries = fs
|
||||
.readdirSync(DOWNLOAD_DIR, { withFileTypes: true })
|
||||
.filter((d) => d.isDirectory());
|
||||
|
||||
const processed = [];
|
||||
|
||||
for (const dirent of dirEntries) {
|
||||
const folder = sanitizeRelative(dirent.name);
|
||||
if (!folder) continue;
|
||||
const rootDir = path.join(DOWNLOAD_DIR, folder);
|
||||
if (!fs.existsSync(rootDir)) continue;
|
||||
|
||||
try {
|
||||
const info = readInfoForRoot(folder) || {};
|
||||
const displayName = info?.name || dirent.name || folder;
|
||||
|
||||
const normalizePath = (value) =>
|
||||
value ? String(value).replace(/\\/g, "/") : value;
|
||||
|
||||
let primaryVideo = normalizePath(info?.primaryVideoPath || null);
|
||||
if (primaryVideo) {
|
||||
const absPrimary = path.join(rootDir, primaryVideo);
|
||||
if (!fs.existsSync(absPrimary)) {
|
||||
primaryVideo = null;
|
||||
}
|
||||
}
|
||||
if (!primaryVideo) {
|
||||
primaryVideo = normalizePath(guessPrimaryVideo(folder));
|
||||
}
|
||||
|
||||
if (!primaryVideo) {
|
||||
removeMovieData(folder);
|
||||
if (clearCache) {
|
||||
upsertInfoFile(rootDir, {
|
||||
primaryVideoPath: null,
|
||||
primaryMediaInfo: null
|
||||
});
|
||||
}
|
||||
console.log(
|
||||
`ℹ️ Movie taraması atlandı (video bulunamadı): ${folder}`
|
||||
);
|
||||
processed.push(folder);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mediaInfo =
|
||||
info?.files?.[primaryVideo]?.mediaInfo || info?.primaryMediaInfo || null;
|
||||
|
||||
if (!mediaInfo) {
|
||||
const absVideo = path.join(rootDir, primaryVideo);
|
||||
if (fs.existsSync(absVideo)) {
|
||||
try {
|
||||
mediaInfo = await extractMediaInfo(absVideo);
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`⚠️ Media info alınamadı (${absVideo}): ${err?.message || err}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ensured = await ensureMovieData(
|
||||
folder,
|
||||
displayName,
|
||||
primaryVideo,
|
||||
mediaInfo
|
||||
);
|
||||
|
||||
const update = {
|
||||
primaryVideoPath: primaryVideo,
|
||||
primaryMediaInfo: ensured || mediaInfo || null
|
||||
};
|
||||
upsertInfoFile(rootDir, update);
|
||||
|
||||
processed.push(folder);
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`❌ Movie metadata yeniden oluşturulamadı (${folder}):`,
|
||||
err?.message || err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
app.post("/api/movies/refresh", requireAuth, async (req, res) => {
|
||||
if (!TMDB_API_KEY) {
|
||||
return res.status(400).json({ error: "TMDB API key tanımlı değil." });
|
||||
}
|
||||
|
||||
try {
|
||||
const folders = fs
|
||||
.readdirSync(DOWNLOAD_DIR, { withFileTypes: true })
|
||||
.filter((d) => d.isDirectory())
|
||||
.map((d) => d.name);
|
||||
|
||||
const processed = [];
|
||||
for (const folder of folders) {
|
||||
const info = readInfoForRoot(folder);
|
||||
const displayName = info?.name || folder;
|
||||
const primaryVideo = info?.primaryVideoPath || guessPrimaryVideo(folder);
|
||||
const candidateMedia =
|
||||
info?.files?.[primaryVideo]?.mediaInfo || info?.primaryMediaInfo || null;
|
||||
const ensured = await ensureMovieData(
|
||||
folder,
|
||||
displayName,
|
||||
primaryVideo,
|
||||
candidateMedia
|
||||
);
|
||||
if (primaryVideo || ensured) {
|
||||
const update = {};
|
||||
if (primaryVideo) update.primaryVideoPath = primaryVideo;
|
||||
if (ensured) update.primaryMediaInfo = ensured;
|
||||
if (Object.keys(update).length) {
|
||||
upsertInfoFile(path.join(DOWNLOAD_DIR, folder), update);
|
||||
}
|
||||
}
|
||||
processed.push(folder);
|
||||
}
|
||||
|
||||
const processed = await rebuildMovieMetadata();
|
||||
res.json({ ok: true, processed });
|
||||
} catch (err) {
|
||||
console.error("🎬 Movies refresh error:", err);
|
||||
@@ -3901,6 +4239,20 @@ app.post("/api/movies/refresh", requireAuth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/movies/rescan", requireAuth, async (req, res) => {
|
||||
if (!TMDB_API_KEY) {
|
||||
return res.status(400).json({ error: "TMDB API key tanımlı değil." });
|
||||
}
|
||||
|
||||
try {
|
||||
const processed = await rebuildMovieMetadata({ clearCache: true });
|
||||
res.json({ ok: true, processed });
|
||||
} catch (err) {
|
||||
console.error("🎬 Movies rescan error:", err);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// --- 📺 TV dizileri listesi ---
|
||||
app.get("/api/tvshows", requireAuth, (req, res) => {
|
||||
try {
|
||||
@@ -4283,33 +4635,58 @@ app.get("/api/tvshows", requireAuth, (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/tvshows/refresh", requireAuth, async (req, res) => {
|
||||
async function rebuildTvMetadata({ clearCache = false } = {}) {
|
||||
if (!TVDB_API_KEY) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "TVDB API erişimi için gerekli anahtar tanımlı değil." });
|
||||
throw new Error("TVDB API erişimi için gerekli anahtar tanımlı değil.");
|
||||
}
|
||||
|
||||
try {
|
||||
const folders = fs
|
||||
.readdirSync(DOWNLOAD_DIR, { withFileTypes: true })
|
||||
.filter((d) => d.isDirectory())
|
||||
.map((d) => d.name);
|
||||
if (clearCache && fs.existsSync(TV_DATA_ROOT)) {
|
||||
try {
|
||||
fs.rmSync(TV_DATA_ROOT, { recursive: true, force: true });
|
||||
console.log("🧹 TV cache temizlendi.");
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`⚠️ TV cache temizlenemedi (${TV_DATA_ROOT}): ${err.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const processed = [];
|
||||
fs.mkdirSync(TV_DATA_ROOT, { recursive: true });
|
||||
|
||||
for (const folder of folders) {
|
||||
const safeFolder = sanitizeRelative(folder);
|
||||
if (!safeFolder) continue;
|
||||
const rootDir = path.join(DOWNLOAD_DIR, safeFolder);
|
||||
if (!fs.existsSync(rootDir)) continue;
|
||||
if (clearCache) {
|
||||
tvdbSeriesCache.clear();
|
||||
tvdbEpisodeCache.clear();
|
||||
tvdbEpisodeDetailCache.clear();
|
||||
}
|
||||
|
||||
const info = readInfoForRoot(safeFolder) || {};
|
||||
const dirEntries = fs
|
||||
.readdirSync(DOWNLOAD_DIR, { withFileTypes: true })
|
||||
.filter((d) => d.isDirectory());
|
||||
|
||||
const processed = [];
|
||||
|
||||
for (const dirent of dirEntries) {
|
||||
const folder = sanitizeRelative(dirent.name);
|
||||
if (!folder) continue;
|
||||
const rootDir = path.join(DOWNLOAD_DIR, folder);
|
||||
if (!fs.existsSync(rootDir)) continue;
|
||||
|
||||
try {
|
||||
const info = readInfoForRoot(folder) || {};
|
||||
const infoFiles = info.files || {};
|
||||
const detected = {};
|
||||
|
||||
const walkDir = async (currentDir, relativeBase = "") => {
|
||||
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
||||
let entries = [];
|
||||
try {
|
||||
entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`⚠️ Klasör okunamadı (${currentDir}): ${err.message}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
const relPath = relativeBase
|
||||
? `${relativeBase}/${entry.name}`
|
||||
@@ -4321,6 +4698,7 @@ app.post("/api/tvshows/refresh", requireAuth, async (req, res) => {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entry.isFile()) continue;
|
||||
if (entry.name.toLowerCase() === INFO_FILENAME) continue;
|
||||
const ext = path.extname(entry.name).toLowerCase();
|
||||
if (!VIDEO_EXTS.includes(ext)) continue;
|
||||
@@ -4343,7 +4721,7 @@ app.post("/api/tvshows/refresh", requireAuth, async (req, res) => {
|
||||
|
||||
try {
|
||||
const ensured = await ensureSeriesData(
|
||||
safeFolder,
|
||||
folder,
|
||||
normalizedRel,
|
||||
seriesInfo,
|
||||
mediaInfo
|
||||
@@ -4370,7 +4748,7 @@ app.post("/api/tvshows/refresh", requireAuth, async (req, res) => {
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`⚠️ TV metadata yenilenemedi (${safeFolder} - ${entry.name}): ${
|
||||
`⚠️ TV metadata yenilenemedi (${folder} - ${entry.name}): ${
|
||||
err?.message || err
|
||||
}`
|
||||
);
|
||||
@@ -4380,16 +4758,37 @@ app.post("/api/tvshows/refresh", requireAuth, async (req, res) => {
|
||||
|
||||
await walkDir(rootDir);
|
||||
|
||||
if (Object.keys(detected).length) {
|
||||
const episodeCount = Object.keys(detected).length;
|
||||
if (episodeCount > 0) {
|
||||
upsertInfoFile(rootDir, { seriesEpisodes: detected });
|
||||
} else if (clearCache) {
|
||||
upsertInfoFile(rootDir, { seriesEpisodes: {} });
|
||||
}
|
||||
|
||||
processed.push({
|
||||
folder: safeFolder,
|
||||
episodes: Object.keys(detected).length
|
||||
folder,
|
||||
episodes: episodeCount
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`❌ TV metadata yeniden oluşturulamadı (${folder}):`,
|
||||
err?.message || err
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
app.post("/api/tvshows/refresh", requireAuth, async (req, res) => {
|
||||
if (!TVDB_API_KEY) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "TVDB API erişimi için gerekli anahtar tanımlı değil." });
|
||||
}
|
||||
|
||||
try {
|
||||
const processed = await rebuildTvMetadata();
|
||||
res.json({ ok: true, processed });
|
||||
} catch (err) {
|
||||
console.error("📺 TvShows refresh error:", err);
|
||||
@@ -4397,6 +4796,22 @@ app.post("/api/tvshows/refresh", requireAuth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/api/tvshows/rescan", requireAuth, async (req, res) => {
|
||||
if (!TVDB_API_KEY) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "TVDB API erişimi için gerekli anahtar tanımlı değil." });
|
||||
}
|
||||
|
||||
try {
|
||||
const processed = await rebuildTvMetadata({ clearCache: true });
|
||||
res.json({ ok: true, processed });
|
||||
} catch (err) {
|
||||
console.error("📺 TvShows rescan error:", err);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// --- Stream endpoint (torrent içinden) ---
|
||||
app.get("/stream/:hash", requireAuth, (req, res) => {
|
||||
const entry = torrents.get(req.params.hash);
|
||||
|
||||
Reference in New Issue
Block a user