241 lines
8.1 KiB
Plaintext
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>
|