############################################################### PORT = 4323 ADDON_SIG_TTL = 600 # 10 Minuten ############################################################### import json import gzip import requests import os from io import BytesIO import uuid from flask import Flask, request, Response, abort import time import socket import threading # ---------------- LÄNDER ---------------- REGIONS = [ {"language": "nl", "region": "BE"}, ] GEOIP_URL = "https://www.vavoo.tv/geoip" PING_URL = "https://www.vavoo.tv/api/app/ping" CATALOG_URL = "https://vavoo.to/mediahubmx-catalog.json" RESOLVE_URL = "https://vavoo.to/mediahubmx-resolve.json" LANGUAGE = "de" REGION = "DE" HEADERS = { "accept": "*/*", "user-agent": "electron-fetch/1.0 electron (+https://github.com/arantes555/electron-fetch)", "Accept-Language": LANGUAGE, "Accept-Encoding": "gzip, deflate", "Connection": "close", } def decode_response(resp): if resp.content[:2] == b'\x1f\x8b': return json.loads(gzip.decompress(resp.content)) return resp.json() session = requests.Session() session.headers.update(HEADERS) # ---------------- GEO + INITIAL PING ---------------- r_geo = session.get(GEOIP_URL) r_geo.raise_for_status() geo_data = decode_response(r_geo) unique_id = str(uuid.uuid4()) current_timestamp = int(time.time() * 1000) initial_payload = { "reason": "app-focus", "locale": "de", "theme": "dark", "metadata": { "device": {"type": "desktop", "uniqueId": unique_id}, "os": {"name": "win32", "version": "Windows 10 Pro", "abis": ["x64"], "host": "Lenovo"}, "app": {"platform": "electron"}, "version": {"package": "tv.vavoo.app", "binary": "3.1.8", "js": "3.1.8"}, }, "appFocusTime": 0, "playerActive": False, "playDuration": 0, "devMode": False, "hasAddon": True, "castConnected": False, "package": "tv.vavoo.app", "version": "3.1.8", "process": "app", "firstAppStart": current_timestamp, "lastAppStart": current_timestamp, "ipLocation": None, "adblockEnabled": True, "proxy": {"supported": ["ss"], "engine": "Mu", "enabled": False, "autoServer": True}, "iap": {"supported": False}, } r1 = session.post(PING_URL, json=initial_payload) r1.raise_for_status() data1 = decode_response(r1) # ---------------- ADDON SIG MANAGEMENT ---------------- addon_sig_lock = threading.Lock() addon_sig_data = { "sig": data1.get("addonSig"), "ts": time.time() } def refresh_addon_sig_if_needed(force=False): with addon_sig_lock: now = time.time() if not force and now - addon_sig_data["ts"] < ADDON_SIG_TTL: return addon_sig_data["sig"] payload = initial_payload.copy() payload["lastAppStart"] = int(time.time() * 1000) r = session.post(PING_URL, json=payload) r.raise_for_status() data = decode_response(r) sig = data.get("addonSig") if not sig: raise RuntimeError("No addonSig received") addon_sig_data["sig"] = sig addon_sig_data["ts"] = now print("[✓] addonSig refreshed") return sig # ---------------- CATALOG LOAD (MULTI-REGION) ---------------- items_by_region = {} for entry in REGIONS: LANGUAGE = entry["language"] REGION = entry["region"] region_key = f"{LANGUAGE}-{REGION}" print(f"[+] Lade Katalog für {region_key}") catalog_headers = { "content-type": "application/json; charset=utf-8", "mediahubmx-signature": addon_sig_data["sig"], "user-agent": "MediaHubMX/2", "accept": "*/*", "Accept-Language": LANGUAGE, "Accept-Encoding": "gzip, deflate", "Connection": "close", } cursor = None while True: catalog_payload = { "language": LANGUAGE, "region": REGION, "catalogId": "iptv", "id": "iptv", "adult": False, "search": "", "sort": "", "filter": {}, "cursor": cursor, "clientVersion": "3.0.2" } r_catalog = session.post(CATALOG_URL, json=catalog_payload, headers=catalog_headers) r_catalog.raise_for_status() catalog_data = decode_response(r_catalog) for item in catalog_data.get("items", []): if item.get("type") == "iptv": items_by_region.setdefault(region_key, []).append({ "id": item["ids"]["id"], "url": item["url"], "name": item["name"], "group": item["group"], "logo": item["logo"], "language": LANGUAGE, "region": REGION }) cursor = catalog_data.get("nextCursor") if not cursor: break print(f"[✓] Gesamtanzahl IPTV-Sender: {sum(len(v) for v in items_by_region.values())}") # ---------------- M3U SAVE (MULTI-FILE) ---------------- LOCAL_IP = "127.0.0.1" def get_local_ip(): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) # Connecting to Google's DNS server local_ip = s.getsockname()[0] # Getting the local IP address s.close() print("Local IP address:", local_ip) return local_ip # return LOCAL_IP # Unterordner erstellen PLAYLIST_DIR = "playlists" os.makedirs(PLAYLIST_DIR, exist_ok=True) def save_m3u_files(): local_ip = get_local_ip() for region_key, items in items_by_region.items(): filename = os.path.join(PLAYLIST_DIR, f"vavoo_{region_key}.m3u") m3u = "#EXTM3U\n" for item in items: m3u += ( f'#EXTINF:-1 tvg-id="{item["id"]}" ' f'tvg-name="{item["name"]}" ' f'tvg-logo="{item["logo"]}" ' # f'group-title="{item["group"]} ({item["region"]})",{item["name"]}\n' f'group-title="{item["group"]}",{item["name"]}\n' ) m3u += f"http://{local_ip}:{PORT}/vavoo?channel={item['id']}\n" with open(filename, "w", encoding="utf-8") as f: f.write(m3u) print(f"[✓] {filename} geschrieben") def save_master_m3u(): master = "#EXTM3U\n" for region_key in items_by_region.keys(): filename = f"playlists/vavoo_{region_key}.m3u" master += f'#EXTINF:-1 group-title="Master",{region_key}\n' master += f"{filename}\n" with open("vavoo_master.m3u", "w", encoding="utf-8") as f: f.write(master) print("[✓] vavoo_master.m3u geschrieben") save_m3u_files() save_master_m3u() # ---------------- FLASK APP ---------------- app = Flask(__name__) @app.route("/vavoo") def stream_proxy(): channel_id = request.args.get("channel") if not channel_id: abort(400) channel = None for region_items in items_by_region.values(): for i in region_items: if i["id"] == channel_id: channel = i break if not channel: abort(404) try: sig = refresh_addon_sig_if_needed() except Exception as e: abort(502, str(e)) resolve_headers = { "content-type": "application/json; charset=utf-8", "mediahubmx-signature": sig, "user-agent": "MediaHubMX/2", "accept": "*/*", "Accept-Language": channel["language"], "Accept-Encoding": "gzip, deflate", "Connection": "close", } resolve_payload = { "language": channel["language"], "region": channel["region"], "url": channel["url"], "clientVersion": "3.0.2" } r_resolve = session.post(RESOLVE_URL, json=resolve_payload, headers=resolve_headers) if r_resolve.status_code == 403: sig = refresh_addon_sig_if_needed(force=True) resolve_headers["mediahubmx-signature"] = sig r_resolve = session.post(RESOLVE_URL, json=resolve_payload, headers=resolve_headers) r_resolve.raise_for_status() result = decode_response(r_resolve) if result: return Response(status=302, headers={"Location": result[0]["url"]}) abort(404) # ---------------- ALL-IN-ONE PLAYLIST ---------------- @app.route("/playlist.m3u") def playlist(): local_ip = get_local_ip() m3u = "#EXTM3U\n" for region_items in items_by_region.values(): for item in region_items: m3u += ( f'#EXTINF:-1 tvg-id="{item["id"]}" ' f'tvg-name="{item["name"]}" ' f'tvg-logo="{item["logo"]}" ' # f'group-title="{item["group"]} ({item["region"]})",{item["name"]}\n' f'group-title="{item["group"]}",{item["name"]}\n' ) m3u += f"http://{local_ip}:{PORT}/vavoo?channel={item['id']}\n" return Response(m3u, mimetype="application/x-mpegURL") # ---------------- MASTER PLAYLIST ---------------- @app.route("/master.m3u") def master_playlist(): try: with open("vavoo_master.m3u", "r", encoding="utf-8") as f: content = f.read() return Response(content, mimetype="application/x-mpegURL") except FileNotFoundError: return Response("#EXTM3U\n# Master playlist not found\n", mimetype="application/x-mpegURL") # ---------------- START SERVER ---------------- if __name__ == "__main__": app.run(host="0.0.0.0", port=PORT)