#!/usr/bin/env python # -*- coding: utf-8 -*- """ Dieses Skript lädt externe Ressourcen wie Font Awesome, Tailwind CSS und Alpine.js herunter und installiert sie lokal, um die Abhängigkeit von externen CDNs zu vermeiden und die Content Security Policy zu erfüllen. """ import os import sys import requests import zipfile import io import shutil import subprocess from pathlib import Path BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # URLs für die Ressourcen RESOURCES = { 'alpine.js': 'https://cdn.jsdelivr.net/npm/alpinejs@3.12.3/dist/cdn.min.js', 'font_awesome': 'https://use.fontawesome.com/releases/v6.4.0/fontawesome-free-6.4.0-web.zip', 'inter_font_300': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2', 'inter_font_400': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2', 'inter_font_500': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2', 'inter_font_600': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2', 'inter_font_700': 'https://fonts.gstatic.com/s/inter/v18/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2', 'jetbrains_font_400': 'https://fonts.gstatic.com/s/jetbrainsmono/v20/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwg.woff2', 'jetbrains_font_500': 'https://fonts.gstatic.com/s/jetbrainsmono/v20/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwhsk.woff2', 'jetbrains_font_700': 'https://fonts.gstatic.com/s/jetbrainsmono/v20/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwhsk.woff2', } # Zielverzeichnisse DIRECTORIES = { 'js': os.path.join(BASE_DIR, 'static', 'js'), 'css': os.path.join(BASE_DIR, 'static', 'css'), 'fonts': os.path.join(BASE_DIR, 'static', 'fonts'), 'webfonts': os.path.join(BASE_DIR, 'static', 'webfonts'), } def download_file(url, filepath): """Lädt eine Datei von einer URL herunter und speichert sie lokal.""" response = requests.get(url, stream=True) if response.status_code == 200: with open(filepath, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"✅ Heruntergeladen: {os.path.basename(filepath)}") return True else: print(f"❌ Fehler beim Herunterladen von {url}: {response.status_code}") return False def extract_zip(zip_data, extract_to): """Extrahiert eine ZIP-Datei in das angegebene Verzeichnis.""" with zipfile.ZipFile(io.BytesIO(zip_data)) as zip_ref: zip_ref.extractall(extract_to) def setup_directories(): """Erstellt die benötigten Verzeichnisse, falls sie nicht existieren.""" for directory in DIRECTORIES.values(): os.makedirs(directory, exist_ok=True) print(f"📁 Verzeichnis erstellt/überprüft: {directory}") def download_alpine(): """Lädt Alpine.js herunter.""" url = RESOURCES['alpine.js'] filepath = os.path.join(DIRECTORIES['js'], 'alpine.min.js') download_file(url, filepath) def download_font_awesome(): """Lädt Font Awesome herunter und extrahiert die Dateien.""" url = RESOURCES['font_awesome'] response = requests.get(url) if response.status_code == 200: # Temporäres Verzeichnis für die Extraktion temp_dir = os.path.join(BASE_DIR, 'temp_fontawesome') os.makedirs(temp_dir, exist_ok=True) # ZIP-Datei extrahieren extract_zip(response.content, temp_dir) # CSS-Datei kopieren fa_dir = os.path.join(temp_dir, 'fontawesome-free-6.4.0-web') css_source = os.path.join(fa_dir, 'css', 'all.min.css') css_dest = os.path.join(DIRECTORIES['css'], 'all.min.css') shutil.copyfile(css_source, css_dest) print(f"✅ Font Awesome CSS kopiert nach {css_dest}") # Webfonts-Verzeichnis kopieren webfonts_source = os.path.join(fa_dir, 'webfonts') shutil.rmtree(DIRECTORIES['webfonts'], ignore_errors=True) shutil.copytree(webfonts_source, DIRECTORIES['webfonts']) print(f"✅ Font Awesome Webfonts kopiert nach {DIRECTORIES['webfonts']}") # Temporäres Verzeichnis löschen shutil.rmtree(temp_dir) return True else: print(f"❌ Fehler beim Herunterladen von Font Awesome: {response.status_code}") return False def download_google_fonts(): """Lädt die Google Fonts (Inter und JetBrains Mono) herunter.""" font_files = { 'inter-light.woff2': RESOURCES['inter_font_300'], 'inter-regular.woff2': RESOURCES['inter_font_400'], 'inter-medium.woff2': RESOURCES['inter_font_500'], 'inter-semibold.woff2': RESOURCES['inter_font_600'], 'inter-bold.woff2': RESOURCES['inter_font_700'], 'jetbrainsmono-regular.woff2': RESOURCES['jetbrains_font_400'], 'jetbrainsmono-medium.woff2': RESOURCES['jetbrains_font_500'], 'jetbrainsmono-bold.woff2': RESOURCES['jetbrains_font_700'], } for filename, url in font_files.items(): filepath = os.path.join(DIRECTORIES['fonts'], filename) download_file(url, filepath) def setup_tailwind(): """Richtet Tailwind CSS ein.""" # Tailwind-Konfiguration erstellen, falls sie nicht existiert tailwind_config = os.path.join(BASE_DIR, 'tailwind.config.js') if not os.path.exists(tailwind_config): with open(tailwind_config, 'w') as f: f.write("""/** @type {import('tailwindcss').Config} */ module.exports = { darkMode: 'class', content: [ "./templates/**/*.html", "./static/**/*.js", ], theme: { extend: { fontFamily: { sans: ['Inter', 'ui-sans-serif', 'system-ui', '-apple-system', 'sans-serif'], mono: ['JetBrains Mono', 'ui-monospace', 'monospace'] }, colors: { primary: { 50: '#f5f3ff', 100: '#ede9fe', 200: '#ddd6fe', 300: '#c4b5fd', 400: '#a78bfa', 500: '#8b5cf6', 600: '#7c3aed', 700: '#6d28d9', 800: '#5b21b6', 900: '#4c1d95' }, secondary: { 50: '#ecfdf5', 100: '#d1fae5', 200: '#a7f3d0', 300: '#6ee7b7', 400: '#34d399', 500: '#10b981', 600: '#059669', 700: '#047857', 800: '#065f46', 900: '#064e3b' }, dark: { 500: '#374151', 600: '#1f2937', 700: '#111827', 800: '#0e1220', 900: '#0a0e19' } } } }, plugins: [], } """) print(f"✅ Tailwind-Konfiguration erstellt: {tailwind_config}") # Input-CSS-Datei erstellen input_css_dir = os.path.join(DIRECTORIES['css'], 'src') os.makedirs(input_css_dir, exist_ok=True) input_css = os.path.join(input_css_dir, 'input.css') if not os.path.exists(input_css): with open(input_css, 'w') as f: f.write("""@tailwind base; @tailwind components; @tailwind utilities; """) print(f"✅ Tailwind Input-CSS erstellt: {input_css}") # Hinweis zur Kompilierung anzeigen print("\n📋 Um Tailwind CSS zu kompilieren, führe folgenden Befehl aus:") print("npm install -D tailwindcss") print(f"npx tailwindcss -i {input_css} -o {os.path.join(DIRECTORIES['css'], 'tailwind.min.css')} --minify") def main(): """Hauptfunktion: Lädt alle benötigten Ressourcen herunter.""" print("🚀 Starte den Download externer Ressourcen...") setup_directories() # Alpine.js herunterladen print("\n📦 Lade Alpine.js herunter...") download_alpine() # Font Awesome herunterladen print("\n📦 Lade Font Awesome herunter...") download_font_awesome() # Google Fonts herunterladen print("\n📦 Lade Google Fonts herunter...") download_google_fonts() # Tailwind CSS einrichten print("\n📦 Richte Tailwind CSS ein...") setup_tailwind() print("\n✅ Alle Ressourcen wurden erfolgreich heruntergeladen und eingerichtet!") print("🔒 Die Webseite sollte nun ohne externe CDNs funktionieren und die Content Security Policy erfüllen.") if __name__ == "__main__": main()