diff --git a/client/src/routes/Transfers.svelte b/client/src/routes/Transfers.svelte index 9b466c0..c0ca3db 100644 --- a/client/src/routes/Transfers.svelte +++ b/client/src/routes/Transfers.svelte @@ -56,19 +56,70 @@ await list(); } - async function addMagnet() { - const m = prompt("URL Girin:"); - if (!m) return; - await apiFetch("/api/transfer", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ magnet: m }) - }); // ✅ - await list(); + const YT_VIDEO_ID_RE = /^[A-Za-z0-9_-]{11}$/; + + function isMagnetLink(value) { + if (!value || typeof value !== "string") return false; + const normalized = value.trim().toLowerCase(); + return normalized.startsWith("magnet:?xt="); } - function selectFile(hash, index) { - ws?.send(JSON.stringify({ type: "select", infoHash: hash, index })); + function normalizeYoutubeUrl(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 !== "youtube.com" && host !== "www.youtube.com") return null; + if (url.pathname !== "/watch") return null; + const videoId = url.searchParams.get("v"); + if (!videoId || !YT_VIDEO_ID_RE.test(videoId)) return null; + return `https://www.youtube.com/watch?v=${videoId}`; + } catch { + return null; + } + } + + async function handleUrlInput() { + const input = prompt("Magnet veya YouTube URL girin:"); + if (!input) return; + if (isMagnetLink(input)) { + await apiFetch("/api/transfer", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ magnet: input }) + }); + await list(); + return; + } + const normalizedYoutube = normalizeYoutubeUrl(input); + if (normalizedYoutube) { + const resp = await apiFetch("/api/youtube/download", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ url: normalizedYoutube }) + }); + if (!resp.ok) { + const data = await resp.json().catch(() => null); + alert(data?.error || "YouTube 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." + ); + } + + async function selectFile(hash, index) { + try { + await apiFetch(`/api/torrents/${hash}/select/${index}`, { + method: "POST" + }); + } catch (err) { + console.error("Select file error:", err); + } } async function removeTorrent(hash) { @@ -116,13 +167,14 @@ } function updateAllPausedState() { - if (torrents.length === 0) { + const torrentOnly = torrents.filter((t) => !t.type || t.type === "torrent"); + if (torrentOnly.length === 0) { isAllPaused = false; return; } // Eğer tüm torrentler paused ise, global durumu paused yap - const allPaused = torrents.every(t => t.paused === true); + const allPaused = torrentOnly.every((t) => t.paused === true); isAllPaused = allPaused; } @@ -166,18 +218,32 @@ return (bytesPerSec / 1e6).toFixed(2) + " MB/s"; } + function formatDate(value) { + if (!value) return "Unknown"; + try { + return new Date(value).toLocaleString(); + } catch { + return "Unknown"; + } + } + function openModal(t) { + if (!t.files || !t.files.length) { + alert("Bu indirme için oynatılabilir video bulunamadı."); + return; + } const selectedFile = - t.files?.find((f) => f.index === t.selectedIndex) || t.files?.[0]; + t.files.find((f) => f.index === t.selectedIndex) || t.files[0]; if (!selectedFile) { - alert("Bu torrentte oynatılabilir video dosyası bulunamadı!"); + alert("Bu indirmede oynatılabilir video dosyası bulunamadı!"); return; } selectedVideo = { ...t, fileIndex: selectedFile.index, - fileName: selectedFile.name + fileName: selectedFile.name, + type: t.type || "torrent" }; showModal = true; } @@ -403,7 +469,7 @@ style="display:none;" /> -