-
-
{selected.title}
- {#if videoUrl(selected)}
+{#if showPlayer && selectedVideo}
+
+
+
+
+
+ {#key encName}
- {:else}
-
Video yolu bulunamadı
- {/if}
+ on:ended={() => (isPlaying = false)}
+ >
+ {#if subtitleURL}
+
+ {/if}
+
+ {/key}
+
+
+
+
+
+
+
+ {formatTime(currentTime)} / {formatTime(duration)}
+
+
+
+
- {/if}
-
+
+{/if}
diff --git a/client/src/stores/rabbitStore.js b/client/src/stores/rabbitStore.js
index 78fa235..ac698fb 100644
--- a/client/src/stores/rabbitStore.js
+++ b/client/src/stores/rabbitStore.js
@@ -1,4 +1,5 @@
import { writable } from "svelte/store";
+import { apiFetch } from "../utils/api.js";
const countStore = writable(0);
export const rabbitCount = countStore;
@@ -6,3 +7,19 @@ export const rabbitCount = countStore;
export function setRabbitCount(count) {
countStore.set(Number(count) || 0);
}
+
+// Rabbit listesini yenile
+export async function refreshRabbitCount() {
+ try {
+ const resp = await apiFetch("/api/rabbit");
+ if (!resp.ok) {
+ console.warn("Rabbit listesi yenilenemedi:", resp.status);
+ return;
+ }
+ const data = await resp.json();
+ const count = data?.items?.length || 0;
+ setRabbitCount(count);
+ } catch (err) {
+ console.warn("Rabbit sayısı yenilenemedi:", err?.message || err);
+ }
+}
diff --git a/server/server.js b/server/server.js
index 18d333c..99b36f6 100644
--- a/server/server.js
+++ b/server/server.js
@@ -1458,6 +1458,98 @@ function writeRabbitMetadata(job, absMedia, mediaInfo, infoJson) {
}
}
+function removeRabbitMetadata(folderId) {
+ const safeFolder = sanitizeRelative(folderId);
+ if (!safeFolder) return false;
+ const targetDir = path.join(RABBIT_DATA_ROOT, safeFolder);
+ if (!fs.existsSync(targetDir)) return false;
+ try {
+ fs.rmSync(targetDir, { recursive: true, force: true });
+ return true;
+ } catch (err) {
+ console.warn("⚠️ Rabbit metadata silinemedi:", err.message);
+ return false;
+ }
+}
+
+function extractPornhubViewKey(folderId) {
+ if (!folderId || typeof folderId !== "string") return null;
+ if (!folderId.startsWith("ph_")) return null;
+ const parts = folderId.split("_");
+ if (parts.length < 2) return null;
+ return parts[1] || null;
+}
+
+async function rebuildRabbitMetadataForFolder(folderId) {
+ const safeFolder = sanitizeRelative(folderId);
+ if (!safeFolder) return false;
+ const rootDir = path.join(DOWNLOAD_DIR, safeFolder);
+ if (!fs.existsSync(rootDir)) return false;
+
+ const hasCompleteFlag = fs.existsSync(path.join(rootDir, ".ph_complete"));
+ const mediaFile = findYoutubeMediaFile(rootDir, false);
+ if (!mediaFile) return false;
+ const absMedia = path.join(rootDir, mediaFile);
+ if (!fs.existsSync(absMedia)) return false;
+
+ const infoJsonName = findYoutubeInfoJson(rootDir);
+ let infoJson = null;
+ if (infoJsonName) {
+ try {
+ infoJson = JSON.parse(fs.readFileSync(path.join(rootDir, infoJsonName), "utf-8"));
+ } catch (err) {
+ console.warn("⚠️ Rabbit info.json okunamadı:", err.message);
+ }
+ }
+
+ const extractor = String(infoJson?.extractor || infoJson?.extractor_key || "").toLowerCase();
+ const webpageUrl = String(infoJson?.webpage_url || infoJson?.original_url || "");
+ const isPornhub =
+ hasCompleteFlag ||
+ safeFolder.startsWith("ph_") ||
+ extractor.includes("pornhub") ||
+ webpageUrl.includes("pornhub.com");
+ if (!isPornhub) return false;
+
+ const viewKey = extractPornhubViewKey(safeFolder) || infoJson?.id || null;
+ const title = infoJson?.title || deriveYoutubeTitle(mediaFile, viewKey);
+ const url =
+ infoJson?.webpage_url ||
+ infoJson?.original_url ||
+ (viewKey ? `https://www.pornhub.com/view_video.php?viewkey=${viewKey}` : null);
+ let addedAt = Date.now();
+ if (Number.isFinite(infoJson?.timestamp)) {
+ addedAt = Number(infoJson.timestamp) * 1000;
+ } else {
+ try {
+ addedAt = fs.statSync(absMedia).mtimeMs || Date.now();
+ } catch (err) {
+ /* no-op */
+ }
+ }
+ const stats = fs.statSync(absMedia);
+ const mediaInfo = await extractMediaInfo(absMedia).catch(() => null);
+
+ const job = {
+ id: safeFolder,
+ folderId: safeFolder,
+ savePath: rootDir,
+ url,
+ added: addedAt,
+ title,
+ files: [
+ {
+ index: 0,
+ name: mediaFile.replace(/\\/g, "/"),
+ length: stats.size
+ }
+ ]
+ };
+
+ writeRabbitMetadata(job, absMedia, mediaInfo, infoJson);
+ return true;
+}
+
function countRabbitItems() {
try {
const entries = fs
@@ -4528,6 +4620,14 @@ function detectMediaFlagsForPath(info, relWithinRoot, isDirectory) {
const flags = { movies: false, tv: false };
if (!info || typeof info !== "object") return flags;
+ const infoFiles = info.files || {};
+ const isExternalMedia =
+ info.tracker === "youtube" ||
+ info.source === "youtube" ||
+ info.source === "pornhub" ||
+ Object.values(infoFiles).some((meta) => Boolean(meta?.youtube));
+ if (isExternalMedia) return flags;
+
const normalized = normalizeTrashPath(relWithinRoot);
const matchesPath = (candidate) => {
const normalizedCandidate = normalizeTrashPath(candidate);
@@ -4541,8 +4641,7 @@ function detectMediaFlagsForPath(info, relWithinRoot, isDirectory) {
return normalizedCandidate === normalized;
};
- const files = info.files || {};
- for (const [key, meta] of Object.entries(files)) {
+ for (const [key, meta] of Object.entries(infoFiles)) {
if (!meta) continue;
if (!matchesPath(key)) continue;
if (meta.movieMatch) flags.movies = true;
@@ -5518,6 +5617,10 @@ app.delete("/api/file", requireAuth, (req, res) => {
trashStateCache.delete(folderId);
}
+ if (folderId && removeRabbitMetadata(folderId)) {
+ broadcastRabbitCount();
+ }
+
if (folderId) {
let matchedInfoHash = null;
for (const [infoHash, entry] of torrents.entries()) {
@@ -5995,7 +6098,7 @@ app.get("/api/trash", requireAuth, (req, res) => {
});
// --- 🗑️ Çöpten geri yükleme API (.trash flag sistemi) ---
-app.post("/api/trash/restore", requireAuth, (req, res) => {
+app.post("/api/trash/restore", requireAuth, async (req, res) => {
try {
const { trashName } = req.body;
@@ -6021,6 +6124,9 @@ app.post("/api/trash/restore", requireAuth, (req, res) => {
console.log(`♻️ Öğe geri yüklendi: ${safeName}`);
broadcastFileUpdate(rootFolder);
+ if (await rebuildRabbitMetadataForFolder(rootFolder)) {
+ broadcastRabbitCount();
+ }
if (mediaFlags.movies || mediaFlags.tv) {
queueMediaRescan({
movies: mediaFlags.movies,
@@ -6463,9 +6569,22 @@ app.get("/api/rabbit", requireAuth, (req, res) => {
const items = [];
for (const folder of entries) {
const metaPath = path.join(RABBIT_DATA_ROOT, folder, "metadata.json");
+ const downloadRoot = path.join(DOWNLOAD_DIR, folder);
+ if (!fs.existsSync(metaPath) || !fs.existsSync(downloadRoot)) {
+ removeRabbitMetadata(folder);
+ continue;
+ }
if (!fs.existsSync(metaPath)) continue;
try {
const data = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
+ const fileRel = data?.file ? String(data.file) : null;
+ if (fileRel) {
+ const filePath = path.join(downloadRoot, fileRel);
+ if (!fs.existsSync(filePath)) {
+ removeRabbitMetadata(folder);
+ continue;
+ }
+ }
items.push({
id: data.id || folder,
title: data.title || folder,