Youtube download özelliği eklendi

This commit is contained in:
2025-11-30 19:46:29 +03:00
parent 0b802ba55c
commit decf503297
2 changed files with 690 additions and 61 deletions

View File

@@ -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;"
/>
</label>
<label class="btn-primary" on:click={addMagnet}>
<label class="btn-primary" on:click={handleUrlInput}>
<i class="fa-solid fa-magnet btn-icon"></i> ADD URL
</label>
</div>
@@ -476,19 +542,28 @@
<div class="torrent-info">
<div class="torrent-header">
<div class="torrent-name">{t.name}</div>
<div class="torrent-title">
<div class="torrent-name">{t.name}</div>
{#if t.type === "youtube"}
<div class="torrent-subtitle">
Added: {formatDate(t.added)}
</div>
{/if}
</div>
<div style="display:flex; gap:5px;">
<button
class="toggle-btn"
on:click|stopPropagation={() => toggleSingleTorrent(t.infoHash)}
title={t.paused ? "Devam Ettir" : "Durdur"}
>
{#if t.paused}
<i class="fa-solid fa-play"></i>
{:else}
<i class="fa-solid fa-pause"></i>
{/if}
</button>
{#if t.type !== "youtube"}
<button
class="toggle-btn"
on:click|stopPropagation={() => toggleSingleTorrent(t.infoHash)}
title={t.paused ? "Devam Ettir" : "Durdur"}
>
{#if t.paused}
<i class="fa-solid fa-play"></i>
{:else}
<i class="fa-solid fa-pause"></i>
{/if}
</button>
{/if}
<button
class="remove-btn"
on:click|stopPropagation={() => removeTorrent(t.infoHash)}
@@ -498,26 +573,33 @@
</div>
<div class="torrent-hash">
Hash: {t.infoHash} | Tracker: {t.tracker ?? "Unknown"} | Added:
{t.added ? new Date(t.added).toLocaleString() : "Unknown"}
{#if t.type === "youtube"}
Source: YouTube | Added:
{t.added ? formatDate(t.added) : "Unknown"}
{:else}
Hash: {t.infoHash} | Tracker: {t.tracker ?? "Unknown"} | Added:
{t.added ? formatDate(t.added) : "Unknown"}
{/if}
</div>
<div class="torrent-files">
{#each t.files as f}
<div class="file-row">
<button
on:click|stopPropagation={() =>
selectFile(t.infoHash, f.index)}
>
{f.index === t.selectedIndex ? "Selected" : "Select"}
</button>
<div class="filename">{f.name}</div>
<div class="filesize">
{(f.length / 1e6).toFixed(1)} MB
{#if t.files && t.files.length}
<div class="torrent-files">
{#each t.files as f}
<div class="file-row">
<button
on:click|stopPropagation={() =>
selectFile(t.infoHash, f.index)}
>
{f.index === t.selectedIndex ? "Selected" : "Select"}
</button>
<div class="filename">{f.name}</div>
<div class="filesize">
{(f.length / 1e6).toFixed(1)} MB
</div>
</div>
</div>
{/each}
</div>
{/each}
</div>
{/if}
<div class="progress-bar">
<div
@@ -530,12 +612,17 @@
{#if (t.progress || 0) < 1}
{(t.progress * 100).toFixed(1)}% •
{t.downloaded ? (t.downloaded / 1e6).toFixed(1) : 0} MB •
{formatSpeed(t.downloadSpeed)}
{t.numPeers ?? 0} peers
{formatSpeed(t.downloadSpeed)}
{#if t.type !== "youtube"}
{t.numPeers ?? 0} peers
{/if}
{:else}
100.0% • {(t.downloaded / 1e6).toFixed(1)} MB
{/if}
</div>
{#if t.status === "error"}
<div class="torrent-error">Download failed</div>
{/if}
</div>
</div>
{/each}
@@ -656,6 +743,7 @@
</div>
{/if}
<style>
/* --- Torrent Listeleme --- */
.torrent-list {
@@ -711,10 +799,22 @@
font-weight: 700;
}
.torrent-title {
display: flex;
flex-direction: column;
gap: 2px;
}
.torrent-name {
word-break: break-word;
}
.torrent-subtitle {
font-size: 12px;
font-weight: 400;
color: #666;
}
.toggle-btn {
background: transparent;
border: none;
@@ -799,6 +899,13 @@
transition: width 0.3s;
}
.torrent-error {
color: #e74c3c;
font-size: 12px;
margin-top: 4px;
}
.progress-text {
font-size: 12px;
color: #444;