import os import time import subprocess import threading import json import pickle import psutil from datetime import datetime, timezone from google.oauth2.credentials import Credentials from google.auth.transport.requests import Request from googleapiclient.discovery import build from flask import Flask # ------------------------------------------------------------ # الإعدادات الأساسية # ------------------------------------------------------------ TIKTOK_USERNAME = "@a3dam_111" CUSTOM_STREAM_KEY = os.getenv("CUSTOM_STREAM_KEY", "p3dq-5cex-bc1k-vet7-bsyx") FFMPEG_LOG = "/tmp/ffmpeg.log" MONITOR_LOG = "/tmp/monitor.log" LOCK_FILE = "/tmp/app_monitor_core.lock" TOKEN_FILE = "/tmp/yt_token.pkl" BROADCAST_FILE = "/tmp/current_broadcast.json" TIKTOK_COOKIES = os.getenv("TIKTOK_COOKIES", "") TIKTOK_COOKIES_ADULT = os.getenv("TIKTOK_COOKIES_ADULT", "") def log(msg): ts = time.strftime("%Y-%m-%d %H:%M:%S") line = f"[{ts}] {msg}" print(line, flush=True) with open(MONITOR_LOG, "a", encoding="utf-8") as f: f.write(line + "\n") # ------------------------------------------------------------ # حفظ الكوكيز # ------------------------------------------------------------ def save_cookies(): if TIKTOK_COOKIES: with open("/tmp/tiktok_cookies.txt", "w") as f: f.write(TIKTOK_COOKIES) if TIKTOK_COOKIES_ADULT: with open("/tmp/tiktok_cookies_adult.txt", "w") as f: f.write(TIKTOK_COOKIES_ADULT) save_cookies() # ------------------------------------------------------------ # أدوات مساعدة # ------------------------------------------------------------ def kill_process_tree(pid): try: parent = psutil.Process(pid) for child in parent.children(recursive=True): child.kill() parent.kill() except: pass def get_tiktok_stream_url(): try: url = f"https://www.tiktok.com/{TIKTOK_USERNAME}/live" cmd = ["streamlink", "--stream-url", url, "best", "--retry-streams", "5", "--retry-open", "3"] if os.path.exists("/tmp/tiktok_cookies_adult.txt"): cmd.extend(["--cookies", "/tmp/tiktok_cookies_adult.txt"]) elif os.path.exists("/tmp/tiktok_cookies.txt"): cmd.extend(["--cookies", "/tmp/tiktok_cookies.txt"]) result = subprocess.run(cmd, capture_output=True, text=True, timeout=25) if result.returncode == 0 and result.stdout.strip(): return result.stdout.strip().split("\n")[0] except: pass try: url = f"https://www.tiktok.com/{TIKTOK_USERNAME}/live" cmd = ["yt-dlp", "--no-warnings", "--no-check-certificates", "--no-live-from-start", "--no-cache-dir", "--rm-cache-dir", "-f", "best", "--get-url", url] if os.path.exists("/tmp/tiktok_cookies_adult.txt"): cmd.extend(["--cookies", "/tmp/tiktok_cookies_adult.txt"]) elif os.path.exists("/tmp/tiktok_cookies.txt"): cmd.extend(["--cookies", "/tmp/tiktok_cookies.txt"]) result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) if result.returncode == 0 and result.stdout.strip(): return result.stdout.strip().split("\n")[0] except: pass return None def start_stream(stream_key, stream_url): if not stream_key or not stream_url: return None log("🎬 بدء البث (تحويل الفيديو H.264 + فلاتر صوتية + تخطي الحظر)...") rtmp_url = f"rtmp://a.rtmp.youtube.com/live2/{stream_key}" try: subprocess.run(["killall", "-9", "ffmpeg", "yt-dlp", "streamlink"], stderr=subprocess.DEVNULL) except: pass cmd = [ "nice", "-n", "19", "ffmpeg", "-hide_banner", "-loglevel", "warning", "-thread_queue_size", "2048", "-user_agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "-headers", "Referer: https://www.tiktok.com/\r\n", "-fflags", "+genpts+discardcorrupt+igndts", # إصلاح التزامن "-flags", "+global_header", "-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_delay_max", "60", "-i", stream_url, "-c:v", "libx264", "-preset", "ultrafast", "-b:v", "2500k", "-maxrate", "2500k", "-bufsize", "5000k", "-pix_fmt", "yuv420p", "-g", "60", "-c:a", "aac", "-b:a", "192k", "-ar", "44100", "-profile:a", "aac_low", "-af", "aresample=async=1000:min_hard_comp=0.100000:first_pts=0,highpass=f=50,lowpass=f=15000,dynaudnorm=f=150:g=15:m=25:p=0.95", "-max_muxing_queue_size", "2048", "-f", "flv", rtmp_url ] with open(FFMPEG_LOG, "w") as stderr_file: proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=stderr_file, preexec_fn=os.setsid) log(f"✅ ffmpeg PID: {proc.pid}") return proc # ------------------------------------------------------------ # YouTube API # ------------------------------------------------------------ def build_youtube_client(): try: if os.path.exists(TOKEN_FILE): with open(TOKEN_FILE, "rb") as f: creds = pickle.load(f) if creds and creds.valid: return build('youtube', 'v3', credentials=creds) if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) with open(TOKEN_FILE, "wb") as f: pickle.dump(creds, f) return build('youtube', 'v3', credentials=creds) creds_json = os.getenv("YT_CREDENTIALS_JSON") if not creds_json: return None data = json.loads(creds_json) creds = Credentials(token=data.get("token"), refresh_token=data.get("refresh_token"), token_uri=data.get("token_uri"), client_id=data.get("client_id"), client_secret=data.get("client_secret"), scopes=data.get("scopes", ["https://www.googleapis.com/auth/youtube.force-ssl"])) if creds.expired and creds.refresh_token: creds.refresh(Request()) with open(TOKEN_FILE, "wb") as f: pickle.dump(creds, f) return build('youtube', 'v3', credentials=creds) except Exception as e: log(f"❌ فشل بناء عميل YouTube: {e}") return None def create_new_broadcast(): try: yt = build_youtube_client() if not yt: return None, None now = datetime.now(timezone.utc) date_str = now.strftime('%d-%m-%Y %H:%M') title = f"🔥 بث تيك توك أعدام المباشر | {date_str} 🚀" description = ( "أهلاً بكم في البث المباشر المخصص لـ أعدام.\n" "استمتعوا معنا بأجواء البث وتفاعلوا في التعليقات!\n\n" "🔔 لا تنسوا الاشتراك في القناة وتفعيل زر التنبيهات ليصلكم كل جديد فور انطلاق البث.\n" "شكراً لدعمكم المتواصل!\n\n" "#أعدام #بث_مباشر #تيك_توك #TikTokLive #الاردن #بث #مباشر #a3dam_111" ) insert_body = { "snippet": { "title": title, "description": description, "scheduledStartTime": now.isoformat() }, "status": {"privacyStatus": "public"}, "contentDetails": {"enableAutoStart": True, "enableAutoStop": False, "latencyPreference": "normal"} } new_broadcast = yt.liveBroadcasts().insert(part="snippet,status,contentDetails", body=insert_body).execute() bid = new_broadcast["id"] log(f"✅ بث جديد: {bid} | {title}") stream_insert = yt.liveStreams().insert( part="snippet,cdn,contentDetails", body={"snippet": {"title": "TikTok Stream Key"}, "cdn": {"ingestionType": "rtmp", "resolution": "1080p", "frameRate": "30fps"}, "contentDetails": {"isReusable": True}} ).execute() stream_id = stream_insert["id"] stream_key = stream_insert["cdn"]["ingestionInfo"]["streamName"] yt.liveBroadcasts().bind(id=bid, part="id", streamId=stream_id).execute() with open(BROADCAST_FILE, "w") as f: json.dump({"broadcast_id": bid, "stream_key": stream_key}, f) return stream_key, bid except Exception as e: log(f"❌ فشل إنشاء بث جديد: {e}") return None, None # ✅ دالة جديدة: تنظيف البث السابق لنفس المساحة فقط def cleanup_own_previous_broadcast(): """تنهي بثاً سابقاً لهذه المساحة فقط (إن وُجد وما زال مفتوحاً)""" if not os.path.exists(BROADCAST_FILE): return try: with open(BROADCAST_FILE, "r") as f: data = json.load(f) bid = data.get("broadcast_id") if not bid: return yt = build_youtube_client() if not yt: return # جلب حالة البث resp = yt.liveBroadcasts().list(id=bid, part="status").execute() items = resp.get("items", []) if not items: os.remove(BROADCAST_FILE) return status = items[0]["status"]["lifeCycleStatus"] if status in ["live", "ready", "created", "testing"]: log(f"🧹 إنهاء بث سابق لهذه المساحة: {bid}") yt.liveBroadcasts().transition(broadcastStatus="complete", id=bid, part="status").execute() # نحذف الملف على أية حال لأننا سننشئ بثاً جديداً os.remove(BROADCAST_FILE) except Exception as e: log(f"⚠️ تعذر تنظيف البث السابق: {e}") if os.path.exists(BROADCAST_FILE): os.remove(BROADCAST_FILE) def finalize_current_broadcast(broadcast_id): if not broadcast_id: return try: yt = build_youtube_client() if not yt: return yt.liveBroadcasts().transition(broadcastStatus="complete", id=broadcast_id, part="status").execute() log(f"🛑 تم إنهاء البث: {broadcast_id}") except Exception as e: log(f"⚠️ فشل إنهاء البث: {e}") if os.path.exists(BROADCAST_FILE): os.remove(BROADCAST_FILE) # ------------------------------------------------------------ # المحرك الرئيسي # ------------------------------------------------------------ def monitor_loop(): try: subprocess.run(["killall", "-9", "ffmpeg", "yt-dlp", "streamlink"], stderr=subprocess.DEVNULL) except: pass time.sleep(2) if os.path.exists(LOCK_FILE): return with open(LOCK_FILE, "w") as f: f.write(str(os.getpid())) # ✅ تنظيف بث هذه المساحة فقط (وليس كل البثوث) cleanup_own_previous_broadcast() ffmpeg_proc = None was_streaming = False consecutive_fails = 0 current_stream_key = None current_broadcast_id = None while True: try: stream_url = get_tiktok_stream_url() if stream_url: consecutive_fails = 0 if was_streaming and ffmpeg_proc and ffmpeg_proc.poll() is None: pass elif was_streaming and ffmpeg_proc and ffmpeg_proc.poll() is not None: kill_process_tree(ffmpeg_proc.pid) ffmpeg_proc = start_stream(current_stream_key, stream_url) else: if not current_broadcast_id: # لا ننهي كل البثوث، فقط ننشئ بثًا جديدًا (المساحة الأخرى مستقلة) stream_key, bid = create_new_broadcast() if not stream_key: stream_key = CUSTOM_STREAM_KEY current_broadcast_id = bid current_stream_key = stream_key ffmpeg_proc = start_stream(current_stream_key, stream_url) was_streaming = True else: if was_streaming: consecutive_fails += 1 if consecutive_fails >= 6: if ffmpeg_proc: kill_process_tree(ffmpeg_proc.pid) ffmpeg_proc = None was_streaming = False if current_broadcast_id: finalize_current_broadcast(current_broadcast_id) current_broadcast_id = None current_stream_key = None time.sleep(30) except Exception as e: log(f"❌ خطأ في حلقة المراقبة: {e}") time.sleep(30) app = Flask(__name__) @app.route("/") def index(): return "OK" if __name__ == "__main__": t = threading.Thread(target=monitor_loop, daemon=True) t.start() app.run(host="0.0.0.0", port=7860, debug=False)