import os import requests from urllib.parse import urljoin, quote, unquote from flask import Flask, Response, send_from_directory, request app = Flask(__name__) # ============================================================================ # TỰ ĐỘNG CẬP NHẬT DUCKDNS KHI APP KHỞI CHẠY # ============================================================================ DUCKDNS_DOMAIN = "chrtv" DUCKDNS_TOKEN = "0a7471b5-56fc-4b60-a5d9-5381f66930d9" def update_duckdns(): try: url = f"https://www.duckdns.org/update?domains={DUCKDNS_DOMAIN}&token={DUCKDNS_TOKEN}&ip=" res = requests.get(url, timeout=10) if "OK" in res.text: print(f"[DuckDNS] Cập nhật Domain {DUCKDNS_DOMAIN}.duckdns.org THÀNH CÔNG!") else: print(f"[DuckDNS] Cập nhật thất bại: {res.text}") except Exception as e: print(f"[DuckDNS] Lỗi kết nối DuckDNS: {e}") # Gọi hàm cập nhật DuckDNS ngay khi load file app.py update_duckdns() # ============================================================================ # CẤU HÌNH BIẾN & LINK LUỒNG # ============================================================================ TARGET_SERVER = "http://23.237.104.106:8080" AMAGI_STUDIO_C = "https://amg12058-c15studio-amg12058c1-lg-us-5787.playouts.now.amagi.tv/playlist.m3u8" WARNER_TV = "http://190.61.90.17:40000/play/a0ho/index.m3u8" PARAMOUNT = "http://206.212.244.63/137/index.m3u8" KENSCOM_180 = "http://103.152.216.26:8443/live/DUC3mlZ3O2Z0i4May6u1cA/1785547208/180.m3u8" KENSCOM_191 = "http://103.152.216.26:8443/live/DUC3mlZ3O2Z0i4May6u1cA/1785547208/191.m3u8" HEADERS = { "User-Agent": "VLC/3.0.20 LibVLC/3.0.20", "Accept": "*/*", "Accept-Language": "vi-VN,vi;q=0.9,en-US;q=0.8,en;q=0.7", "Connection": "keep-alive", "Host": "103.152.216.26:8443" } PUBLIC_BASE_URL = os.environ.get("PUBLIC_BASE_URL", "https://ken1402-antv-live.hf.space") def add_cors_headers(response): response.headers['Access-Control-Allow-Origin'] = '*' response.headers['Access-Control-Allow-Methods'] = 'GET, OPTIONS, HEAD' response.headers['Access-Control-Allow-Headers'] = '*' return response @app.route('/') def home(): return f"IPTV Server & ErsatzTV Engine is Running! DuckDNS Active: {DUCKDNS_DOMAIN}.duckdns.org", 200 # ============================================================================ # 1. PLAYLIST M3U TỔNG HỢP # ============================================================================ @app.route('/playlist.m3u', methods=['GET', 'OPTIONS']) def get_playlist(): if request.method == 'OPTIONS': return add_cors_headers(Response(status=204)) m3u_content = f"""#EXTM3U #EXTINF:-1 tvg-id="HBO" group-title="US Live" tvg-logo="https://i.imgur.com/vHqQZ4u.png",HBO Movies HD {PUBLIC_BASE_URL}/stream/hbo/master.m3u8 #EXTINF:-1 tvg-id="WarnerTV" group-title="US Live" tvg-logo="https://i.imgur.com/2Xy349p.png",Warner TV HD {PUBLIC_BASE_URL}/stream/warnertv #EXTINF:-1 tvg-id="Paramount" group-title="US Live" tvg-logo="https://i.imgur.com/Q23kLMl.png",Paramount Network HD {PUBLIC_BASE_URL}/stream/paramount #EXTINF:-1 tvg-id="StudioC" group-title="US Live",Amagi Studio C HD {PUBLIC_BASE_URL}/stream/studioc #EXTINF:-1 tvg-id="Live180" group-title="VN Live",KensCom Channel 180 HD {PUBLIC_BASE_URL}/stream/180.m3u8 #EXTINF:-1 tvg-id="Live191" group-title="VN Live",KensCom Channel 191 HD {PUBLIC_BASE_URL}/stream/191.m3u8""" res = Response(m3u_content.strip(), mimetype='application/x-mpegurl') return add_cors_headers(res) # ============================================================================ # 2. RESTREAM HBO # ============================================================================ @app.route('/stream/hbo/', methods=['GET', 'OPTIONS']) def serve_hbo(filename): if request.method == 'OPTIONS': return add_cors_headers(Response(status=204)) folder_path = '/app/hls/hbo' if filename in ["index.m3u8", "master.m3u8"]: filename = "master.m3u8" file_path = os.path.join(folder_path, filename) if not os.path.exists(file_path): return "Stream đang khởi tạo...", 503 res = send_from_directory(folder_path, filename) add_cors_headers(res) if filename.endswith('.m3u8'): res.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate, max-age=0' else: res.headers['Cache-Control'] = 'max-age=3600' return res # ============================================================================ # 3. PROXY HLS KHÁC # ============================================================================ def handle_hls_proxy(default_target_url, endpoint_path): if request.method == 'OPTIONS': return add_cors_headers(Response(status=204)) target_url = request.args.get('url') or default_target_url target_url = unquote(target_url) req_headers = HEADERS.copy() if "amagi.tv" in target_url: req_headers.pop("Host", None) try: resp = requests.get(target_url, headers=req_headers, timeout=10, stream=True) if resp.status_code != 200: return add_cors_headers(Response(f"Upstream Error: HTTP {resp.status_code}", status=resp.status_code)) raw_bytes = resp.content raw_text = raw_bytes.decode('utf-8-sig', errors='ignore') if raw_text.strip().startswith('#EXTM3U'): lines = raw_text.splitlines() rewritten_lines = [] for line in lines: line_str = line.strip() if not line_str or line_str.startswith('#'): rewritten_lines.append(line_str) else: full_url = urljoin(target_url, line_str) rewritten_lines.append(f"{endpoint_path}?url={quote(full_url)}") res = Response("\n".join(rewritten_lines).strip(), status=200, mimetype='application/x-mpegurl') res.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' return add_cors_headers(res) else: res = Response(raw_bytes, status=200, mimetype='video/mp2t') return add_cors_headers(res) except Exception as e: return add_cors_headers(Response(f"Proxy Exception: {str(e)}", status=502)) @app.route('/stream/studioc', methods=['GET', 'OPTIONS']) def proxy_studioc(): return handle_hls_proxy(AMAGI_STUDIO_C, "/stream/studioc") @app.route('/stream/warnertv', methods=['GET', 'OPTIONS']) def proxy_warnertv(): return handle_hls_proxy(WARNER_TV, "/stream/warnertv") @app.route('/stream/paramount', methods=['GET', 'OPTIONS']) def proxy_paramount(): return handle_hls_proxy(PARAMOUNT, "/stream/paramount") @app.route('/stream/180.m3u8', methods=['GET', 'OPTIONS']) def proxy_180(): return handle_hls_proxy(KENSCOM_180, "/stream/180.m3u8") @app.route('/stream/191.m3u8', methods=['GET', 'OPTIONS']) def proxy_191(): return handle_hls_proxy(KENSCOM_191, "/stream/191.m3u8") if __name__ == '__main__': app.run(host='0.0.0.0', port=7860, threaded=True)