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 %} - Cover - {% else %} - Kein Cover - {% 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

- -
- {% if single_mode %} - 📚 Gesamte Bibliothek laden - {% else %} - - {% endif %} -
- -
- {% for book in books %} -
-
- {% if book.has_cover %} - Cover - {% else %} - Kein Cover - {% endif %} -
{{ book.name }}
-
-
- -
-
- {% else %} -

Keine Hörbücher gefunden.

- {% endfor %} -
- - - - -
-
▶️
-

Tippe irgendwo auf den Bildschirm, um zu starten

-

-
- - - - -""" - -LOGIN_TEMPLATE = """ - - - - Login - 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