Audiofy – Musikwünsche für Partys

Server (Website, Admin-Bereich, Player-API) und Player (raspotify +
Controller) mit Install-Skripten für Debian-Systeme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 19:16:39 +01:00
co-authored by Claude Opus 5.5
commit 3610de7bae
45 changed files with 5666 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
// Audiofy – Cloudflare Turnstile (Bot-Schutz). Lädt das Widget nur, wenn der Server einen Site-Key liefert.
"use strict";
window.AudiofyTurnstile = (() => {
let ladeVersprechen = null;
function skriptLaden() {
if (!ladeVersprechen) {
ladeVersprechen = new Promise((fertig, fehler) => {
const s = document.createElement("script");
s.src = "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit";
s.async = true;
s.onload = () => fertig(window.turnstile);
s.onerror = () => fehler(new Error("Turnstile konnte nicht geladen werden"));
document.head.append(s);
});
}
return ladeVersprechen;
}
const LEER = { token: async () => null, reset() {}, bereit: () => true };
// Liefert {token(): Promise<string|null>, reset(), bereit()} – ohne Site-Key ein "leerer" Schutz.
async function einrichten(container, aktion) {
// Site-Key und Cloudflare-Skript gleichzeitig holen (spart einen Umlauf)
const skript = skriptLaden().catch(() => null);
let siteKey = null;
try {
siteKey = (await (await fetch("/api/turnstile", { credentials: "same-origin" })).json()).site_key;
} catch (_) { /* ohne Site-Key weiter */ }
if (!siteKey) return LEER;
let aktuell = null;
let warten = [];
const melden = (wert) => { aktuell = wert; warten.forEach((f) => f(wert)); warten = []; };
let ts = null;
let id = null;
try {
ts = await skript;
if (!ts) return LEER; // Skript nicht ladbar → Server entscheidet
id = ts.render(container, {
sitekey: siteKey,
action: aktion,
appearance: "interaction-only", // nur sichtbar, wenn Cloudflare eine Bestätigung braucht
language: "de",
callback: (t) => melden(t),
"expired-callback": () => { aktuell = null; },
"error-callback": () => { melden(null); },
});
} catch (_) {
return LEER; // Server entscheidet dann
}
return {
bereit: () => !!aktuell,
// wartet bis zu 15 s auf ein Token
token() {
if (aktuell) return Promise.resolve(aktuell);
return new Promise((fertig) => {
warten.push(fertig);
setTimeout(() => fertig(null), 15000);
});
},
// jedes Token gilt nur einmal → nach jeder Anfrage ein neues holen
reset() {
aktuell = null;
try { ts.reset(id); } catch (_) { /* egal */ }
},
};
}
return { einrichten };
})();