diff --git a/app.backup.v0.1 b/app.backup.v0.1
deleted file mode 100644
index addaea8..0000000
--- a/app.backup.v0.1
+++ /dev/null
@@ -1,320 +0,0 @@
-import os
-import json
-import io
-import zipfile
-from flask import Flask, render_template_string, request, session, redirect, url_for, send_from_directory, send_file
-
-app = Flask(__name__)
-# WICHTIG: Ändere diesen Key für den produktiven Einsatz
-app.secret_key = '6a305032-9668-43c3-9a72-a407d5be988f'
-
-# Konfiguration
-PASSWORD = 'toniessindteuer'
-BASE_DIR = '/mnt/NAS/Toniebox/'
-
-# ---------------------------------------------------------
-# HTML & JS Template (Frontend)
-# ---------------------------------------------------------
-TEMPLATE = """
-
-
-
-
-
- Tonieflix
-
-
-
- Abmelden
- 🎧 Tonieflix
-
-
-
-
-
-
- {% for book in books %}
-
- {% if book.has_cover %}
-
 }})
- {% else %}
-

- {% endif %}
-
{{ book.name }}
-
- {% endfor %}
-
-
-
-
-
-
-
-"""
-
-LOGIN_TEMPLATE = """
-
-
-
- Login - Toniboni
-
-
-
-
-
-
Toniboni Login
-
-
-
-
-"""
-
-# ---------------------------------------------------------
-# Backend Logik & Routen
-# ---------------------------------------------------------
-
-@app.before_request
-def require_login():
- if request.endpoint not in ['login'] and not session.get('logged_in'):
- return redirect(url_for('login'))
-
-@app.route('/login', methods=['GET', 'POST'])
-def login():
- if request.method == 'POST':
- if request.form.get('password') == PASSWORD:
- session['logged_in'] = True
- return redirect(url_for('index'))
- else:
- return "Falsches Passwort!", 403
- return render_template_string(LOGIN_TEMPLATE)
-
-@app.route('/logout')
-def logout():
- session.clear()
- return redirect(url_for('login'))
-
-@app.route('/')
-def index():
- books = []
- if os.path.exists(BASE_DIR):
- for item in sorted(os.listdir(BASE_DIR)):
- item_path = os.path.join(BASE_DIR, item)
-
- if os.path.isdir(item_path):
- files = os.listdir(item_path)
- tracks = sorted([f for f in files if f.lower().endswith(('.mp3', '.ogg'))])
- has_cover = any(f.lower() == 'cover.jpg' for f in files)
-
- if tracks:
- books.append({
- 'name': item,
- 'tracks': tracks,
- 'has_cover': has_cover
- })
-
- return render_template_string(TEMPLATE, books=books)
-
-@app.route('/cover/')
-def serve_cover(book_name):
- book_path = os.path.join(BASE_DIR, book_name)
- return send_from_directory(book_path, 'cover.jpg')
-
-@app.route('/stream//')
-def serve_audio(book_name, track_name):
- book_path = os.path.join(BASE_DIR, book_name)
- return send_from_directory(book_path, track_name)
-
-@app.route('/download/')
-def download_book(book_name):
- book_path = os.path.join(BASE_DIR, book_name)
-
- # Sicherheitscheck: Existiert der Ordner?
- if not os.path.isdir(book_path):
- return "Hörbuch nicht gefunden", 404
-
- # ZIP-Datei im Arbeitsspeicher erstellen
- memory_file = io.BytesIO()
-
- # ZIP_STORED nutzt keine Kompression (geht extrem schnell, gut für MP3s)
- with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_STORED) as zf:
- for root, dirs, files in os.walk(book_path):
- for file in files:
- file_path = os.path.join(root, file)
- # Schreibe Datei ins ZIP (nutzt Dateinamen ohne den absoluten NAS-Pfad)
- zf.write(file_path, arcname=file)
-
- memory_file.seek(0)
-
- # Datei an den Browser schicken
- return send_file(
- memory_file,
- download_name=f"{book_name}.zip",
- as_attachment=True,
- mimetype='application/zip'
- )
-
-if __name__ == '__main__':
- app.run(host='0.0.0.0', port=5005)
diff --git a/app.backup.v0.2 b/app.backup.v0.2
deleted file mode 100644
index c3c493c..0000000
--- a/app.backup.v0.2
+++ /dev/null
@@ -1,441 +0,0 @@
-import os
-import json
-import io
-import zipfile
-from flask import Flask, render_template_string, request, session, redirect, url_for, send_from_directory, send_file, jsonify
-from flask_cors import CORS
-
-# Konfiguration
-PASSWORD = 'toniessindteuer'
-BASE_DIR = '/mnt/NAS/Toniebox/'
-
-app = Flask(__name__)
-app.config['SECRET_KEY'] = 'tonieflix-super-secret-key'
-CORS(app, supports_credentials=True, allow_headers="*")
-
-
-# ---------------------------------------------------------
-# Hilfsfunktionen
-# ---------------------------------------------------------
-def get_books(single_book=None):
- books = []
- if os.path.exists(BASE_DIR):
- items_to_check = [single_book] if single_book else sorted(os.listdir(BASE_DIR))
-
- for item in items_to_check:
- item_path = os.path.join(BASE_DIR, item)
-
- if os.path.isdir(item_path):
- files = os.listdir(item_path)
- tracks = sorted([f for f in files if f.lower().endswith(('.mp3', '.ogg'))])
- has_cover = any(f.lower() == 'cover.jpg' for f in files)
-
- if tracks:
- books.append({
- 'name': item,
- 'tracks': tracks,
- 'has_cover': has_cover
- })
- return books
-
-# ---------------------------------------------------------
-# HTML & JS Template (Frontend)
-# ---------------------------------------------------------
-TEMPLATE = """
-
-
-
-
-
- Tonieflix
-
-
-
- Abmelden
- 🎬 Tonieflix
-
-
-
-
- {% for book in books %}
-
-
- {% if book.has_cover %}
-
 }})
- {% else %}
-

- {% endif %}
-
{{ book.name }}
-
-
-
-
-
- {% else %}
-
Keine Hörbücher gefunden.
- {% endfor %}
-
-
-
-
-
-
-
▶️
-
Tippe irgendwo auf den Bildschirm, um zu starten
-
-
-
-
-
-
-"""
-
-LOGIN_TEMPLATE = """
-
-
-
- Login - Tonieflix
-
-
-
-
-
-
🎬 Tonieflix
-
-
-
-
-"""
-
-# ---------------------------------------------------------
-# Backend Logik & Routen
-# ---------------------------------------------------------
-
-@app.before_request
-def require_login():
- if request.method == 'OPTIONS':
- return None
- if request.path.startswith('/api/'):
- return None
- if request.args.get('pw') == PASSWORD:
- return None
- if request.endpoint not in ['login', 'static'] and not session.get('logged_in'):
- return redirect(url_for('login', next=request.full_path if request.full_path != '/' else None))
-
-@app.route('/login', methods=['GET', 'POST'])
-def login():
- next_url = request.args.get('next') or url_for('index')
- if request.method == 'POST':
- if request.form.get('password') == PASSWORD:
- session['logged_in'] = True
- return redirect(next_url)
- else:
- return "Falsches Passwort!", 403
- return render_template_string(LOGIN_TEMPLATE)
-
-@app.route('/logout')
-def logout():
- session.clear()
- return redirect(url_for('login'))
-
-@app.route('/')
-def index():
- return render_template_string(TEMPLATE, books=get_books(), auto_play="", single_mode=False)
-
-@app.route('/play/')
-def play_book(book_name):
- return render_template_string(TEMPLATE, books=get_books(single_book=book_name), auto_play=book_name, single_mode=True)
-
-@app.route('/cover/')
-def serve_cover(book_name):
- book_path = os.path.join(BASE_DIR, book_name)
- return send_from_directory(book_path, 'cover.jpg')
-
-@app.route('/stream//')
-def serve_audio(book_name, track_name):
- book_path = os.path.join(BASE_DIR, book_name)
- return send_from_directory(book_path, track_name)
-
-@app.route('/download/')
-def download_book(book_name):
- book_path = os.path.join(BASE_DIR, book_name)
- if not os.path.isdir(book_path):
- return "Hörbuch nicht gefunden", 404
-
- memory_file = io.BytesIO()
- with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_STORED) as zf:
- for root, dirs, files in os.walk(book_path):
- for file in files:
- file_path = os.path.join(root, file)
- zf.write(file_path, arcname=file)
-
- memory_file.seek(0)
- return send_file(
- memory_file,
- download_name=f"{book_name}.zip",
- as_attachment=True,
- mimetype='application/zip'
- )
-
-@app.route('/api/books')
-def api_books():
- client_password = request.headers.get('X-Password')
- if client_password != PASSWORD:
- return jsonify({"error": "Falsches Passwort"}), 403
- return jsonify(get_books())
-
-if __name__ == '__main__':
- app.run(host='0.0.0.0', port=5005)
\ No newline at end of file
diff --git a/tonieapi.py b/tonieapi.py
deleted file mode 100644
index c294bd2..0000000
--- a/tonieapi.py
+++ /dev/null
@@ -1,215 +0,0 @@
-import asyncio
-from pathlib import Path
-from playwright.async_api import async_playwright
-import requests
-
-async def get_tonie_api_token(username: str, password: str) -> tuple[str | None, str | None]:
- """Loggt sich automatisiert bei Tonies ein und gibt Access- und Refresh-Token zurück."""
- async with async_playwright() as p:
- browser = await p.chromium.launch(headless=True)
- context = await browser.new_context(viewport={'width': 1280, 'height': 800})
- page = await context.new_page()
-
- login_url = (
- "https://login.tonies.com/auth/realms/tonies/protocol/openid-connect/auth?"
- "client_id=my-tonies&redirect_uri=https%3A%2F%2Fmy.tonies.com%2Flogin%3Fredirect%3D%252F&"
- "state=24115267-a4f0-4033-a2d4-bef68c83b07d&response_mode=fragment&"
- "response_type=code&scope=openid&nonce=ee64d559-2b1c-4f4d-a01f-40d84c4dced1&"
- "ui_locales=de&code_challenge=_lFACYXyUjTQEnb3JiqUWQ7xu4zA50xMNyXhhp3uUe8&"
- "code_challenge_method=S256"
- )
-
- await page.goto(login_url)
-
- try:
- await page.wait_for_selector("[data-testid='email-toggle']", timeout=5000)
- await page.click("[data-testid='email-toggle']")
- except Exception:
- pass
-
- await page.wait_for_timeout(1000)
- await page.wait_for_selector("input[id='username'], input[name='username']", timeout=5000)
-
- await page.type("input[id='username'], input[name='username']", username, delay=50)
- await page.type("input[id='password'], input[name='password']", password, delay=50)
- await page.click("input[type='submit'], button[type='submit']")
-
- await page.wait_for_timeout(5000)
-
- access_token = await page.evaluate("() => window.localStorage.getItem('authorization')")
- refresh_token = await page.evaluate("() => window.localStorage.getItem('refreshToken')")
-
- await browser.close()
- return access_token, refresh_token
-
-
-def get_household_id(access_token: str) -> str:
- """Ermittelt die Haushalts-ID des Accounts."""
- headers = {
- "Authorization": f"Bearer {access_token}",
- "Content-Type": "application/json",
- "User-Agent": "Mozilla/5.0"
- }
- resp = requests.get("https://api.tonie.cloud/v2/households", headers=headers)
- if resp.status_code != 200:
- raise Exception(f"Fehler beim Laden der Haushalte: {resp.status_code} - {resp.text}")
-
- households = resp.json()
- household_list = households if isinstance(households, list) else households.get("households", [])
- if not household_list:
- raise Exception("Kein Haushalt im Account gefunden.")
- return household_list[0]["id"]
-
-
-def get_creative_tonies(access_token: str, household_id: str) -> list[dict]:
- """Ruft alle Kreativ-Tonies ab."""
- headers = {
- "Authorization": f"Bearer {access_token}",
- "Content-Type": "application/json",
- "User-Agent": "Mozilla/5.0"
- }
- resp = requests.get(f"https://api.tonie.cloud/v2/households/{household_id}/creativetonies", headers=headers)
- if resp.status_code != 200:
- raise Exception(f"Fehler beim Laden der Kreativ-Tonies: {resp.status_code} - {resp.text}")
-
- raw_tonies = resp.json()
- if isinstance(raw_tonies, dict):
- raw_tonies = raw_tonies.get("creativeTonies", raw_tonies.get("items", []))
-
- return [{"id": t.get("id"), "name": t.get("name"), "imageUrl": t.get("imageUrl"), "chapters": t.get("chapters", [])} for t in raw_tonies]
-
-def upload_audio_to_creative_tonie(access_token: str, household_id: str, tonie_id: str, file_path: str, title: str, delete_all_chapters: bool):
- """Lädt eine Audiodatei hoch und fügt sie als Kapitel an den angegebenen Kreativ-Tonie an."""
- headers = {
- "Authorization": f"Bearer {access_token}",
- "Content-Type": "application/json",
- "User-Agent": "Mozilla/5.0"
- }
-
- # Step 1: Upload-Berechtigung bei Toniecloud anfordern
- print("Fordere S3-Upload-Berechtigung an...")
- upload_req_resp = requests.post("https://api.tonie.cloud/v2/file", json={"headers": {}}, headers=headers)
- if upload_req_resp.status_code != 200:
- raise Exception(f"Upload-Request fehlgeschlagen ({upload_req_resp.status_code}): {upload_req_resp.text}")
-
- amazon_data = upload_req_resp.json()
- print("DEBUG S3 Response:", amazon_data) # Hilft beim Debuggen falls es immer noch zickt
- s3_request = amazon_data.get("request", {})
- fields = s3_request.get("fields", {})
- file_id = amazon_data.get("fileId")
- s3_url = s3_request.get("url", "https://bxn-toniecloud-prod-upload.s3.amazonaws.com/")
-
- # Step 2: Datei an Amazon S3 hochladen (multipart/form-data)
- print(f"Lade Datei '{file_path}' zu S3 hoch...")
- if not Path(file_path).exists():
- raise FileNotFoundError(f"Die Datei '{file_path}' wurde nicht gefunden.")
-
- multipart_fields = {
- "key": fields.get("key"),
- "x-amz-algorithm": fields.get("x-amz-algorithm"),
- "x-amz-credential": fields.get("x-amz-credential"),
- "x-amz-date": fields.get("x-amz-date"),
- "policy": fields.get("policy"),
- "x-amz-signature": fields.get("x-amz-signature"),
- "x-amz-security-token": fields.get("x-amz-security-token"),
- }
-
- with open(file_path, "rb") as f:
- files = {"file": (fields.get("key"), f, "audio/mpeg")}
- s3_resp = requests.post(s3_url, data=multipart_fields, files=files)
-
- if s3_resp.status_code not in (200, 204):
- raise Exception(f"S3-Upload fehlgeschlagen mit Status {s3_resp.status_code}: {s3_resp.text}")
-
- print("S3-Upload erfolgreich.")
-
- # Step 3: Kapitelkonfiguration aktualisieren / an den Tonie anfügen
- print("Füge Kapitel zum Kreativ-Tonie hinzu...")
-
- tonie_detail_resp = requests.get(f"https://api.tonie.cloud/v2/households/{household_id}/creativetonies/{tonie_id}", headers=headers)
- if tonie_detail_resp.status_code != 200:
- raise Exception(f"Konnte Tonie-Details nicht abrufen: {tonie_detail_resp.text}")
-
- tonie_data = tonie_detail_resp.json()
-
- cleaned_chapters = []
- if not delete_all_chapters:
- for ch in tonie_data.get("chapters", []):
- c_id = ch.get("id")
- c_file = ch.get("file")
- c_title = ch.get("title", "")
-
- # Falls file ein Dictionary ist oder leer/None, direkt verwerfen
- if isinstance(c_file, dict):
- c_file = c_file.get("id") or c_file.get("file")
-
- # Streng prüfen, ob ein echter String vorhanden ist (kein None, kein leerer String, kein "none")
- if c_id and c_file and str(c_file).strip() and str(c_file).lower() != "none":
- cleaned_chapters.append({
- "id": str(c_id),
- "file": str(c_file),
- "title": str(c_title)
- })
-
- # Neues Kapitel anhängen
- new_chapter = {
- "id": str(file_id),
- "file": str(file_id),
- "title": str(title)
- }
- cleaned_chapters.append(new_chapter)
-
- # Payload für den Update-Endpunkt zusammenbauen
- update_payload = {
- "name": tonie_data.get("name"),
- "imageUrl": tonie_data.get("imageUrl"),
- "chapters": cleaned_chapters
- }
-
- update_resp = requests.put(
- f"https://api.tonie.cloud/v2/households/{household_id}/creativetonies/{tonie_id}",
- json=update_payload,
- headers=headers
- )
-
- if update_resp.status_code not in (200, 204):
- raise Exception(f"Fehler beim Speichern der Kapitel auf dem Tonie: {update_resp.status_code} - {update_resp.text}")
-
- print("Kapitel erfolgreich zum Kreativ-Tonie hinzugefügt!")
-
-if __name__ == "__main__":
- import json
-
- USER = "sandromormile@gmail.com"
- PASS = "Sandro92@tonies"
-
- print("Starte automatisierten Login...")
- access_token, refresh_token = asyncio.run(get_tonie_api_token(USER, PASS))
-
- if access_token:
- print("Login erfolgreich.\n")
- household_id = get_household_id(access_token)
-
- # Beispiel 1: Alle Kreativ-Tonies auflisten
- tonies = get_creative_tonies(access_token, household_id)
- print(f"Gefundene Kreativ-Tonies: {len(tonies)}")
- print(json.dumps(tonies, indent=4, ensure_ascii=False))
-
- # Beispiel 2: Datei auf den ersten Kreativ-Tonie in der Liste hochladen (falls vorhanden)
- if tonies:
- target_tonie = tonies[1]
- print(f"\nLade Datei auf Kreativ-Tonie '{target_tonie['name']}' (ID: {target_tonie['id']}) hoch...")
-
- try:
- upload_audio_to_creative_tonie(
- access_token=access_token,
- household_id=household_id,
- tonie_id=target_tonie["id"],
- file_path="beispiel.mp3",
- title="beispiel.mp3",
- delete_all_chapters=True
- )
- except Exception as e:
- print(f"Upload fehlgeschlagen: {e}")
- else:
- print("Login fehlgeschlagen: Kein Token erhalten.")
\ No newline at end of file