fix Youtube
This commit is contained in:
60
app.js
60
app.js
@@ -4,10 +4,57 @@
|
|||||||
var TWITCH_CHANNEL = "duda_xd";
|
var TWITCH_CHANNEL = "duda_xd";
|
||||||
|
|
||||||
/* ------------------------------------------------------------------
|
/* ------------------------------------------------------------------
|
||||||
* Twitch: Live-Player einbetten. Zeigt automatisch den aktuellen
|
* YouTube: Liest die Datei videos.json, die auf dem Server von
|
||||||
* Stream, sobald du live bist. Die Domain wird dynamisch gesetzt,
|
* fetch_videos.py (einmal am Tag per Cron) mit den aktuellen
|
||||||
* damit der Player ueberall funktioniert (auch lokal).
|
* Video-IDs deines Kanals aktualisiert wird.
|
||||||
* Twitch ist ein iframe-Embed -> keine CORS-Probleme.
|
*
|
||||||
|
* 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 = '<div class="loading">Noch keine Videos vorhanden.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
renderYouTubeGrid(ids);
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
grid.innerHTML =
|
||||||
|
'<div class="loading error">Videos konnten nicht geladen werden.</div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +=
|
||||||
|
'<div class="video-card">' +
|
||||||
|
'<div class="video-wrapper">' +
|
||||||
|
'<iframe src="https://www.youtube.com/embed/' + id +
|
||||||
|
'" title="Duda17 Video" ' +
|
||||||
|
'frameborder="0" allow="accelerometer; autoplay; clipboard-write; ' +
|
||||||
|
'encrypted-media; gyroscope; picture-in-picture; web-share" ' +
|
||||||
|
'referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>' +
|
||||||
|
'</div>' +
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
grid.innerHTML = html || '<div class="loading">Keine Videos gefunden.</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------
|
||||||
|
* Twitch: Live-Player einbetten.
|
||||||
* ------------------------------------------------------------------ */
|
* ------------------------------------------------------------------ */
|
||||||
function loadTwitch() {
|
function loadTwitch() {
|
||||||
var wrap = document.getElementById("twitch-embed");
|
var wrap = document.getElementById("twitch-embed");
|
||||||
@@ -24,5 +71,8 @@
|
|||||||
wrap.appendChild(iframe);
|
wrap.appendChild(iframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", loadTwitch);
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
loadYouTube();
|
||||||
|
loadTwitch();
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
118
fetch_videos.py
Normal file
118
fetch_videos.py
Normal file
@@ -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()
|
||||||
16
index.html
16
index.html
@@ -53,20 +53,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="video-grid">
|
<div id="yt-grid" class="video-grid">
|
||||||
<!-- Offizieller YouTube Embed: zeigt automatisch die neuesten
|
<div class="loading">Lade YouTube-Videos…</div>
|
||||||
Uploads des Kanals. YouTube hostet alles selbst - kein
|
|
||||||
CORS, kein API-Key, keine externen Dienste noetig und
|
|
||||||
neue Videos erscheinen automatisch. -->
|
|
||||||
<div class="video-card video-card-wide">
|
|
||||||
<div class="video-wrapper">
|
|
||||||
<iframe src="https://www.youtube.com/embed?listType=user_uploads&list=UU0zG_TbEEic12DQHnEGmlQ"
|
|
||||||
title="Duda17 - YouTube Uploads"
|
|
||||||
frameborder="0"
|
|
||||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
|
||||||
allowfullscreen></iframe>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section-cta">
|
<div class="section-cta">
|
||||||
|
|||||||
12
style.css
12
style.css
@@ -170,11 +170,6 @@ body {
|
|||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Der YouTube-Uploads-Player (alle Videos) soll die volle Breite nutzen */
|
|
||||||
.video-card-wide {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-card {
|
.video-card {
|
||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
@@ -236,6 +231,13 @@ body {
|
|||||||
color: var(--tiktok-pink);
|
color: var(--tiktok-pink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading code {
|
||||||
|
background: var(--bg-card);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--tiktok-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
/* TikTok Embed */
|
/* TikTok Embed */
|
||||||
.tiktok-embed-wrap {
|
.tiktok-embed-wrap {
|
||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
|
|||||||
10
videos.json
Normal file
10
videos.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[
|
||||||
|
"HPEbsIInMEg",
|
||||||
|
"4I8ay7l4Gfk",
|
||||||
|
"AZycMlj2FJc",
|
||||||
|
"lpClI8-7S7E",
|
||||||
|
"C5O9fwbMCvc",
|
||||||
|
"8TJGbSExWTc",
|
||||||
|
"KIpWKm5puIw",
|
||||||
|
"HPKnQa6Dv4k"
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user