diff --git a/client/public/rabbit.svg b/client/public/rabbit.svg new file mode 100644 index 0000000..15e0ab7 --- /dev/null +++ b/client/public/rabbit.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/client/src/App.svelte b/client/src/App.svelte index d7621be..7e35771 100644 --- a/client/src/App.svelte +++ b/client/src/App.svelte @@ -6,6 +6,7 @@ import Files from "./routes/Files.svelte"; import Transfers from "./routes/Transfers.svelte"; import Trash from "./routes/Trash.svelte"; + import Rabbit from "./routes/Rabbit.svelte"; import Movies from "./routes/Movies.svelte"; import TvShows from "./routes/TvShows.svelte"; import Music from "./routes/Music.svelte"; @@ -150,6 +151,7 @@ + diff --git a/client/src/assets/rabbit.svg b/client/src/assets/rabbit.svg new file mode 100644 index 0000000..3b26713 --- /dev/null +++ b/client/src/assets/rabbit.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/src/components/Sidebar.svelte b/client/src/components/Sidebar.svelte index 38ee8b6..28d7019 100644 --- a/client/src/components/Sidebar.svelte +++ b/client/src/components/Sidebar.svelte @@ -1,11 +1,12 @@ {/if} @@ -304,4 +336,10 @@ const unsubscribeMusic = musicCount.subscribe((count) => { border-radius: 9px; line-height: 1; } + + .rabbit-icon { + width: 18px; + height: 18px; + object-fit: contain; + } diff --git a/client/src/routes/Files.svelte b/client/src/routes/Files.svelte index 9b7f8b7..2fb45b3 100644 --- a/client/src/routes/Files.svelte +++ b/client/src/routes/Files.svelte @@ -303,6 +303,20 @@ } function updateVisibleState(fileList, path = currentPath) { + const rabbitCompletedRoots = new Set( + fileList + .filter( + (f) => + !f.isDirectory && + (f.displayName === ".ph_complete" || f.name?.endsWith("/.ph_complete")) + ) + .map((f) => { + const segs = f.displaySegments || f.name?.split("/") || []; + return segs.length ? segs[0] : null; + }) + .filter(Boolean) + ); + const dirs = buildDirectoryEntries(fileList); const directoryMap = new Map(); dirs.forEach((dir) => { @@ -334,7 +348,16 @@ (file) => !file.isDirectory && normalizePath(file.displayParentPath) === normalizePath(path) && - file.displayName.toLowerCase() !== "info.js", + file.displayName.toLowerCase() !== "info.js" && + !file.displayName.toLowerCase().includes(".part") && + file.displayName !== ".ph_complete" && + !(() => { + const segs = file.displaySegments || []; + const root = segs.length ? segs[0] : ""; + const isRabbit = root.startsWith("ph_"); + const isMp4 = file.displayName.toLowerCase().endsWith(".mp4"); + return isRabbit && isMp4 && !rabbitCompletedRoots.has(root); + })() ); applyOrdering(path); breadcrumbs = computeBreadcrumbs(path); diff --git a/client/src/routes/Rabbit.svelte b/client/src/routes/Rabbit.svelte new file mode 100644 index 0000000..4a91518 --- /dev/null +++ b/client/src/routes/Rabbit.svelte @@ -0,0 +1,223 @@ + + +
+
+

Rabbit

+ +
+ + {#if loading} +
Yükleniyor...
+ {:else if error} +
{error}
+ {:else if items.length === 0} +
Henüz içerik yok.
+ {:else} +
+ {#each items as item (item.id)} +
playItem(item)}> + {#if thumbUrl(item)} + {item.title} + {:else} +
+ {/if} +
+
{item.title}
+ {#if item.added} +
{new Date(item.added).toLocaleString()}
+ {/if} +
+
+ {/each} +
+ {/if} + + {#if showPlayer && selected} +
(showPlayer = false)}> +
+ +
{selected.title}
+ {#if videoUrl(selected)} + + {:else} +
Video yolu bulunamadı
+ {/if} +
+
+ {/if} +
+ + diff --git a/client/src/routes/Transfers.svelte b/client/src/routes/Transfers.svelte index b6e6c14..f7230ec 100644 --- a/client/src/routes/Transfers.svelte +++ b/client/src/routes/Transfers.svelte @@ -75,6 +75,23 @@ } const YT_VIDEO_ID_RE = /^[A-Za-z0-9_-]{11}$/; + function normalizePornhubUrl(value) { + if (!value || typeof value !== "string") return null; + try { + const url = new URL(value.trim()); + if (url.protocol !== "https:") return null; + const host = url.hostname.toLowerCase(); + if (host !== "pornhub.com" && host !== "www.pornhub.com") return null; + if (url.pathname !== "/view_video.php") return null; + const viewkey = url.searchParams.get("viewkey"); + if (!viewkey) return null; + return `https://www.pornhub.com/view_video.php?viewkey=${encodeURIComponent( + viewkey + )}`; + } catch { + return null; + } + } function isMagnetLink(value) { if (!value || typeof value !== "string") return false; @@ -99,7 +116,7 @@ } async function handleUrlInput() { - const input = prompt("Magnet veya YouTube URL girin:"); + const input = prompt("Magnet, YouTube veya Pornhub URL girin:"); if (!input) return; if (isMagnetLink(input)) { await apiFetch("/api/transfer", { @@ -125,8 +142,23 @@ await list(); return; } + const normalizedPh = normalizePornhubUrl(input); + if (normalizedPh) { + const resp = await apiFetch("/api/pornhub/download", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ url: normalizedPh }) + }); + if (!resp.ok) { + const data = await resp.json().catch(() => null); + alert(data?.error || "Pornhub indirmesi başlatılamadı"); + return; + } + await list(); + return; + } alert( - "Yalnızca magnet linkleri veya https://www.youtube.com/watch?v=... formatındaki YouTube URL'leri destekleniyor." + "Yalnızca magnet linkleri, YouTube (https://www.youtube.com/watch?v=...) veya Pornhub (https://www.pornhub.com/view_video.php?viewkey=...) URL'leri destekleniyor." ); } @@ -556,7 +588,11 @@ class="thumb" on:load={(e) => e.target.classList.add("loaded")} /> - {:else if t.type === "youtube" && (!t.progress || t.progress <= 0)} + {:else if (t.type === "pornhub" && (!t.progress || t.progress <= 0))} +
+
+
+ {:else if (t.type === "youtube" && (!t.progress || t.progress <= 0))}
@@ -568,8 +604,8 @@
-
-
{t.name}
+
+
{t.name}
{#if t.type === "youtube"}
Source: YouTube @@ -577,8 +613,15 @@
Added: {formatDate(t.added)}
+ {:else if t.type === "pornhub"} +
+ Source: Rabbit +
+
+ Added: {formatDate(t.added)} +
{/if} -
+
{#if t.type !== "youtube"}