feat(transfers): mail.ru indirme desteği ekle

Mail.ru video URL'lerini desteklemek için sunucu ve istemci tarafında
gerekli değişiklikler yapıldı.

- Sunucu tarafında Mail.ru URL çözümleme (yt-dlp) ve indirme (aria2c)
  işlevselliği eklendi.
- /api/mailru/download uç noktası oluşturuldu.
- Dockerfile'a aria2c bağımlılığı eklendi.
- Kullanıcı arayüzü Mail.ru URL'lerini kabul edecek ve indirme
  ilerlemesini gösterecek şekilde güncellendi.
- İndirilen dosyalar için otomatik küçük resim oluşturma eklendi.
This commit is contained in:
2026-01-26 20:04:41 +03:00
parent 45e6ef3356
commit 0b99fce5a9
3 changed files with 542 additions and 11 deletions

View File

@@ -98,8 +98,21 @@
}
}
function normalizeMailRuUrl(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.endsWith("mail.ru")) return null;
return url.toString();
} catch {
return null;
}
}
async function handleUrlInput() {
const input = prompt("Magnet veya YouTube URL girin:");
const input = prompt("Magnet, YouTube veya Mail.ru URL girin:");
if (!input) return;
if (isMagnetLink(input)) {
await apiFetch("/api/transfer", {
@@ -125,8 +138,23 @@
await list();
return;
}
const normalizedMailRu = normalizeMailRuUrl(input);
if (normalizedMailRu) {
const resp = await apiFetch("/api/mailru/download", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url: normalizedMailRu })
});
if (!resp.ok) {
const data = await resp.json().catch(() => null);
alert(data?.error || "Mail.ru 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, https://www.youtube.com/watch?v=... formatındaki YouTube URL'leri veya mail.ru linkleri destekleniyor."
);
}
@@ -556,7 +584,7 @@
class="thumb"
on:load={(e) => e.target.classList.add("loaded")}
/>
{:else if t.type === "youtube" && (!t.progress || t.progress <= 0)}
{:else if (t.type === "youtube" || t.type === "mailru") && (!t.progress || t.progress <= 0)}
<div class="thumb placeholder loading">
<div class="spinner"></div>
</div>
@@ -570,9 +598,9 @@
<div class="torrent-header">
<div class="torrent-title">
<div class="torrent-name">{t.name}</div>
{#if t.type === "youtube"}
{#if t.type === "youtube" || t.type === "mailru"}
<div class="torrent-subtitle">
Source: YouTube
Source: {t.type === "mailru" ? "Mail.ru" : "YouTube"}
</div>
<div class="torrent-subtitle">
Added: {formatDate(t.added)}
@@ -580,7 +608,7 @@
{/if}
</div>
<div style="display:flex; gap:5px;">
{#if t.type !== "youtube"}
{#if t.type === "torrent" || !t.type}
<button
class="toggle-btn"
on:click|stopPropagation={() => toggleSingleTorrent(t.infoHash)}
@@ -601,7 +629,7 @@
</div>
</div>
{#if t.type !== "youtube"}
{#if t.type === "torrent" || !t.type}
<div class="torrent-hash">
Hash: {t.infoHash} | Tracker: {t.tracker ?? "Unknown"} | Added:
{t.added ? formatDate(t.added) : "Unknown"}
@@ -639,7 +667,7 @@
{(t.progress * 100).toFixed(1)}% •
{t.downloaded ? (t.downloaded / 1e6).toFixed(1) : 0} MB •
{formatSpeed(t.downloadSpeed)}
{#if t.type !== "youtube"}
{#if t.type === "torrent" || !t.type}
{t.numPeers ?? 0} peers
{/if}
{:else}