Files
yt/src/components/DownloadForm.astro

241 lines
8.1 KiB
Plaintext

---
import { t } from "../lib/i18n";
const streamOnly = process.env.STREAM_ONLY === "true";
---
<div class="container mx-auto px-4 py-8 max-w-2xl">
<h1 class="text-2xl font-bold mb-6">{t(Astro, "downloadForm.title")}</h1>
<form id="downloadForm" class="space-y-4">
<div class="form-control">
<label class="label" for="url">
<span class="label-text">{t(Astro, "downloadForm.urlLabel")}</span>
</label>
<div class="join w-full">
<input
type="url"
id="url"
name="url"
required
class="input input-bordered join-item flex-1 border border-base-300"
placeholder={t(Astro, "downloadForm.urlPlaceholder")}
/>
<button
type="submit"
id="downloadBtn"
class="btn btn-primary join-item"
>
{t(Astro, "common.download")}
</button>
</div>
</div>
<div class="form-control">
<label class="label" for="format">
<span class="label-text">{t(Astro, "downloadForm.formatLabel")}</span>
</label>
<select id="format" class="select select-bordered w-full">
<option value="mp4" selected
>{t(Astro, "downloadForm.formatMp4")}</option
>
<option value="audio">{t(Astro, "downloadForm.formatAudio")}</option>
<option value="best">{t(Astro, "downloadForm.formatBest")}</option>
</select>
</div>
<div class="collapse collapse-arrow border border-base-300 bg-base-200">
<input type="checkbox" id="cookiesToggle" />
<div class="collapse-title text-sm font-medium">
{t(Astro, "downloadForm.cookiesLabel")}
</div>
<div class="collapse-content">
<div class="form-control">
<label class="label">
<span class="label-text text-xs"
>{t(Astro, "downloadForm.cookiesHelp")}</span
>
</label>
<textarea
id="cookies"
name="cookies"
class="textarea textarea-bordered h-32 text-xs font-mono"
placeholder={t(Astro, "downloadForm.cookiesPlaceholder")}
></textarea>
</div>
</div>
</div>
</form>
<div id="status" class="mt-4 hidden"></div>
<div id="loading" class="mt-4 hidden">
<div class="flex items-center gap-2">
<span class="loading loading-spinner loading-md"></span>
<span>{t(Astro, "downloadForm.downloadInProgress")}</span>
</div>
</div>
<div id="lastFile" class="mt-4 hidden">
<div class="alert alert-success">
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<div>
<div class="font-bold">{t(Astro, "downloadForm.lastFile")}</div>
<div id="lastFileName" class="text-sm"></div>
</div>
<div>
<a id="lastFileLink" href="#" class="btn btn-sm btn-primary" download>
{t(Astro, "common.download")}
</a>
{
!streamOnly && (
<a href="/files" class="btn btn-sm btn-ghost ml-2">
{t(Astro, "common.allFiles")}
</a>
)
}
</div>
</div>
</div>
</div>
<script>
const form = document.getElementById("downloadForm");
const status = document.getElementById("status");
const loading = document.getElementById("loading");
const downloadBtn = document.getElementById("downloadBtn");
const lastFile = document.getElementById("lastFile");
const lastFileName = document.getElementById("lastFileName");
const lastFileLink = document.getElementById(
"lastFileLink"
) as HTMLAnchorElement;
form?.addEventListener("submit", async (e) => {
e.preventDefault();
const urlInput = document.getElementById("url") as HTMLInputElement;
const formatSelect = document.getElementById("format") as HTMLSelectElement;
const cookiesInput = document.getElementById(
"cookies"
) as HTMLTextAreaElement;
const url = urlInput?.value;
const format = formatSelect?.value || "mp4";
const cookies = cookiesInput?.value?.trim() || null;
if (!url || !downloadBtn || !status || !loading) return;
(downloadBtn as HTMLButtonElement).disabled = true;
status.classList.add("hidden");
loading?.classList.remove("hidden");
try {
const response = await fetch("/api/download", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-format": format,
},
body: JSON.stringify({ url, cookies }),
});
// Prüfe ob Response ein Stream ist (Content-Type ist nicht JSON)
const contentType = response.headers.get("content-type");
const isStream = contentType && !contentType.includes("application/json");
if (isStream && response.ok) {
// Stream-Modus: Datei direkt herunterladen
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = downloadUrl;
// Dateiname aus Content-Disposition Header extrahieren
const contentDisposition = response.headers.get("content-disposition");
let filename = "video." + format;
if (contentDisposition) {
const filenameMatch = contentDisposition.match(
/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
);
if (filenameMatch && filenameMatch[1]) {
filename = filenameMatch[1].replace(/['"]/g, "");
// URL-dekodieren falls nötig
try {
filename = decodeURIComponent(filename);
} catch (e) {
// Falls Dekodierung fehlschlägt, Original verwenden
}
}
}
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(downloadUrl);
loading?.classList.add("hidden");
if (status) {
status.classList.remove("hidden");
status.className = "alert alert-success";
status.innerHTML = `<span>${document.documentElement.lang === "en" ? "Download successful!" : "Download erfolgreich!"}</span>`;
}
urlInput.value = "";
} else {
// Normaler Modus: JSON Response
const data = await response.json();
loading?.classList.add("hidden");
if (response.ok && status) {
status.classList.remove("hidden");
status.className = "alert alert-success";
status.innerHTML = `<span>${document.documentElement.lang === "en" ? "Download successful!" : "Download erfolgreich!"}</span>`;
urlInput.value = "";
// Show last file
if (data.filename && lastFile && lastFileName && lastFileLink) {
lastFileName.textContent = data.filename;
lastFileLink.href = `/api/download-file?file=${encodeURIComponent(data.filename)}`;
lastFile.classList.remove("hidden");
}
} else if (status) {
status.classList.remove("hidden");
status.className = "alert alert-error";
const errorMsg =
data.error ||
(document.documentElement.lang === "en"
? "Unknown error"
: "Unbekannter Fehler");
status.innerHTML = `<span>${document.documentElement.lang === "en" ? "Error: " : "Fehler: "}${errorMsg}</span>`;
}
}
} catch (error) {
loading?.classList.add("hidden");
if (status) {
status.classList.remove("hidden");
status.className = "alert alert-error";
const errorMsg =
error instanceof Error
? error.message
: document.documentElement.lang === "en"
? "Network error"
: "Netzwerkfehler";
status.innerHTML = `<span>${document.documentElement.lang === "en" ? "Error: " : "Fehler: "}${errorMsg}</span>`;
}
} finally {
if (downloadBtn) {
(downloadBtn as HTMLButtonElement).disabled = false;
}
}
});
</script>