diff --git a/app.js b/app.js index 72b229d..5d21914 100644 --- a/app.js +++ b/app.js @@ -4,10 +4,57 @@ var TWITCH_CHANNEL = "duda_xd"; /* ------------------------------------------------------------------ - * Twitch: Live-Player einbetten. Zeigt automatisch den aktuellen - * Stream, sobald du live bist. Die Domain wird dynamisch gesetzt, - * damit der Player ueberall funktioniert (auch lokal). - * Twitch ist ein iframe-Embed -> keine CORS-Probleme. + * YouTube: Liest die Datei videos.json, die auf dem Server von + * fetch_videos.py (einmal am Tag per Cron) mit den aktuellen + * Video-IDs deines Kanals aktualisiert wird. + * + * Dein API-Key ist dabei NIE im Browser - er bleibt komplett auf + * dem Server versteckt (siehe fetch_videos.py). + * ------------------------------------------------------------------ */ + function loadYouTube() { + var grid = document.getElementById("yt-grid"); + if (!grid) return; + + fetch("videos.json", { cache: "no-cache" }) + .then(function (r) { + if (!r.ok) throw new Error("HTTP " + r.status); + return r.json(); + }) + .then(function (ids) { + if (!Array.isArray(ids) || !ids.length) { + grid.innerHTML = '
Noch keine Videos vorhanden.
'; + return; + } + renderYouTubeGrid(ids); + }) + .catch(function () { + grid.innerHTML = + '
Videos konnten nicht geladen werden.
'; + }); + } + + function renderYouTubeGrid(videoIds) { + var grid = document.getElementById("yt-grid"); + var html = ""; + for (var i = 0; i < videoIds.length; i++) { + var id = String(videoIds[i]).trim(); + if (!id) continue; + html += + '
' + + '
' + + '' + + '
' + + '
'; + } + grid.innerHTML = html || '
Keine Videos gefunden.
'; + } + + /* ------------------------------------------------------------------ + * Twitch: Live-Player einbetten. * ------------------------------------------------------------------ */ function loadTwitch() { var wrap = document.getElementById("twitch-embed"); @@ -24,5 +71,8 @@ wrap.appendChild(iframe); } - document.addEventListener("DOMContentLoaded", loadTwitch); -})(); + document.addEventListener("DOMContentLoaded", function () { + loadYouTube(); + loadTwitch(); + }); +})(); \ No newline at end of file diff --git a/fetch_videos.py b/fetch_videos.py new file mode 100644 index 0000000..f4d082c --- /dev/null +++ b/fetch_videos.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +""" +DUDA17 - YouTube Video-Liste aktualisieren +========================================== +Dieses Skript laeuft auf deinem Server (einmal am Tag per Cron) +und holt automatisch die aktuellen Video-IDs deines YouTube-Kanals +ueber die offizielle YouTube Data API v3. + +Es schreibt die Video-IDs in die Datei "videos.json", die deine +Webseite dann einfach liest und anzeigt. So bleibt dein API-Key +auf dem Server versteckt und ist NICHT im Browser/DevTools sichtbar. + +EINRICHTUNG: + 1. Setze nachfolgend API_KEY und CHANNEL_ID ein. + 2. Teste: python3 fetch_videos.py + 3. Danach einmal taeglich automatisch ausfuehren (Cron): + crontab -e + # folgende Zeile einfügen (jeden Tag um 06:00): + 0 6 * * * cd /PFAD/ZU/DEINER/WEBSEITE && /usr/bin/python3 fetch_videos.py + + Der Pfad in der Cron-Zeile ist der Ordner, in dem auch 'videos.json' + abgelegt werden soll (= WebRoot-Ordner). +""" + +import json +import sys +import os +import urllib.request +import urllib.parse + +# --------------------------------------------------------------- +# KONFIGURATION - hier eintragen! +# --------------------------------------------------------------- +API_KEY = "AIzaSyC-3qzGsalOYpT_gqay1arltMGKjJYPH-8" # dein YouTube API-Key +CHANNEL_ID = "UCC0zG_TbEEic12DQHnEGmlQ" # deine Kanal-ID +MAX_RESULTS = 50 # wie viele Videos holen + +# Ordner, in den videos.json geschrieben werden soll. +# Standard: aktuelles Verzeichnis dieses Skripts. +# Auf dem Server: absolute Pfad zu deiner WebRoot angeben, z.B.: +# OUT_DIR = "/var/www/html/social" +OUT_DIR = os.path.dirname(os.path.abspath(__file__)) +# --------------------------------------------------------------- + + +def get_uploads_playlist(channel_id): + # Uploads-Playlist eines Kanals: "UU" + Kanal-ID ohne "UC" + return "UU" + channel_id[2:] if channel_id.startswith("UC") else "UU" + channel_id + + +def build_url(playlist_id): + params = { + "part": "snippet", + "playlistId": playlist_id, + "maxResults": str(MAX_RESULTS), + "key": API_KEY, + } + return "https://www.googleapis.com/youtube/v3/playlistItems?" + urllib.parse.urlencode(params) + + +def fetch_json(url): + req = urllib.request.Request( + url, + headers={"User-Agent": "Mozilla/5.0 (Duda17-fetch)"}, + ) + with urllib.request.urlopen(req, timeout=30) as resp: + return json.loads(resp.read().decode("utf-8")) + + +def main(): + if API_KEY == "DEIN_API_KEY_HIER": + print("FEHLER: Setze zuerst API_KEY in fetch_videos.py", file=sys.stderr) + sys.exit(1) + + playlist_id = get_uploads_playlist(CHANNEL_ID) + url = build_url(playlist_id) + + print("Hole Videos von Kanal:", CHANNEL_ID) + print("Uploads-Playlist:", playlist_id) + + data = fetch_json(url) + + if "error" in data: + print("API-Fehler:", json.dumps(data["error"], indent=2), file=sys.stderr) + sys.exit(1) + + items = [] + for it in data.get("items", []): + snippet = it.get("snippet", {}) + video_id = snippet.get("resourceId", {}).get("videoId") + if video_id: + items.append( + { + "id": video_id, + "title": snippet.get("title", ""), + "thumb": snippet.get("thumbnails", {}).get("high", {}).get("url", ""), + "published": snippet.get("publishedAt", ""), + } + ) + + # Neueste zuerst (die API liefert bereits chronologisch; schneller + # hier nach Erscheinungsdatum sortieren, um sicherzugehen) + items.sort(key=lambda x: x.get("published") or "", reverse=True) + + # Nur die IDs in die Datei schreiben - schlank fuer die Webseite, + # die nur die Embed-URLs braucht. + video_ids = [v["id"] for v in items] + + os.makedirs(OUT_DIR, exist_ok=True) + out_path = os.path.join(OUT_DIR, "videos.json") + with open(out_path, "w", encoding="utf-8") as f: + json.dump(video_ids, f, ensure_ascii=False, indent=2) + + print(f"Erfolgreich. {len(video_ids)} Videos geschrieben nach {out_path}") + + +if __name__ == "__main__": + main() diff --git a/index.html b/index.html index 6e9074e..1970d77 100644 --- a/index.html +++ b/index.html @@ -53,20 +53,8 @@ -
- -
-
- -
-
+
+
Lade YouTube-Videos…
diff --git a/style.css b/style.css index 8efbe6e..0b8d4d7 100644 --- a/style.css +++ b/style.css @@ -170,11 +170,6 @@ body { gap: 24px; } -/* Der YouTube-Uploads-Player (alle Videos) soll die volle Breite nutzen */ -.video-card-wide { - grid-column: 1 / -1; -} - .video-card { background: var(--bg-card); border-radius: 16px; @@ -236,6 +231,13 @@ body { color: var(--tiktok-pink); } +.loading code { + background: var(--bg-card); + padding: 2px 6px; + border-radius: 6px; + color: var(--tiktok-cyan); +} + /* TikTok Embed */ .tiktok-embed-wrap { background: var(--bg-card); diff --git a/videos.json b/videos.json new file mode 100644 index 0000000..ad37231 --- /dev/null +++ b/videos.json @@ -0,0 +1,10 @@ +[ + "HPEbsIInMEg", + "4I8ay7l4Gfk", + "AZycMlj2FJc", + "lpClI8-7S7E", + "C5O9fwbMCvc", + "8TJGbSExWTc", + "KIpWKm5puIw", + "HPKnQa6Dv4k" +]