Youtube download özelliği eklendi
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user