Mroo9 commited on
Commit
2ed4139
·
verified ·
1 Parent(s): 7d19963

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +230 -53
app.py CHANGED
@@ -2,26 +2,32 @@ import os
2
  import time
3
  import subprocess
4
  import threading
 
 
5
  import psutil
 
 
 
 
6
  from flask import Flask
7
 
8
  # ------------------------------------------------------------
9
- # الإعدادات
10
  # ------------------------------------------------------------
11
  TIKTOK_USERNAME = "@a3dam_111"
12
- CUSTOM_STREAM_KEY = os.getenv("CUSTOM_STREAM_KEY", "p3dq-5cex-bc1k-vet7-bsyx")
13
- FFMPEG_LOG = "/tmp/ffmpeg.log"
14
- MONITOR_LOG = "/tmp/monitor.log"
15
 
16
- # إعداد Flask لمنع الإغلاق التلقائي
17
- app = Flask(__name__)
18
 
19
- @app.route('/')
20
- def home():
21
- return "البوت يعمل في الخلفية بنجاح!"
 
 
 
22
 
23
- def run_web():
24
- app.run(host='0.0.0.0', port=7860)
25
 
26
  def log(msg):
27
  ts = time.strftime("%Y-%m-%d %H:%M:%S")
@@ -31,71 +37,242 @@ def log(msg):
31
  f.write(line + "\n")
32
 
33
  # ------------------------------------------------------------
34
- # الكوكيز
35
  # ------------------------------------------------------------
36
  def save_cookies():
37
- for env_var, file_path in [("TIKTOK_COOKIES", "/tmp/tiktok_cookies.txt"), ("TIKTOK_COOKIES_ADULT", "/tmp/tiktok_cookies_adult.txt")]:
38
- cookie_data = os.getenv(env_var, "").strip()
39
- if cookie_data:
40
- with open(file_path, "w", encoding="utf-8") as f:
41
- f.write(cookie_data)
42
- log(f"✅ تم حفظ بيانات الكوكيز في {file_path}")
 
 
43
 
44
  # ------------------------------------------------------------
45
- # الدوال الأساسية
46
  # ------------------------------------------------------------
 
 
 
 
 
 
 
 
 
47
  def get_tiktok_stream_url():
 
 
 
48
  try:
49
  url = f"https://www.tiktok.com/{TIKTOK_USERNAME}/live"
50
- cmd = ["streamlink", "--stream-url", url, "best"]
51
- if os.path.exists("/tmp/tiktok_cookies.txt"): cmd.extend(["--cookies", "/tmp/tiktok_cookies.txt"])
 
 
 
 
 
 
 
 
 
 
 
52
  result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
53
- return result.stdout.strip().split("\n")[0] if result.returncode == 0 else None
54
- except: return None
 
 
55
 
56
  def start_stream(stream_key, stream_url):
 
 
57
  rtmp_url = f"rtmp://a.rtmp.youtube.com/live2/{stream_key}"
58
- cmd = ["ffmpeg", "-re", "-i", stream_url, "-c:v", "copy", "-c:a", "aac", "-b:a", "192k", "-f", "flv", rtmp_url]
59
- with open(FFMPEG_LOG, "w") as f:
60
- return subprocess.Popen(cmd, stderr=f, preexec_fn=os.setsid)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  # ------------------------------------------------------------
63
- # المحرك الرئيسي
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  # ------------------------------------------------------------
65
  def monitor_loop():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  while True:
67
  try:
68
  stream_url = get_tiktok_stream_url()
69
  if stream_url:
70
- log("🟢 البث متاح، بدء التشغيل...")
71
- proc = start_stream(CUSTOM_STREAM_KEY, stream_url)
72
- while proc.poll() is None:
73
- time.sleep(60)
 
 
 
 
 
 
 
 
 
 
74
  else:
75
- log("💤 بانتظار بدء البث في تيك توك...")
76
- time.sleep(60)
77
- except Exception as e:
78
- log(f"💥 خطأ: {e}")
79
- time.sleep(60)
 
 
 
 
 
 
 
 
 
 
80
 
81
  if __name__ == "__main__":
82
- log("✅ بدء تشغيل المحرك النهائي...")
83
-
84
- # 1. تشغيل خادم الويب (Flask) للحفاظ على استقرار السيرفر
85
- web_thread = threading.Thread(target=run_web, daemon=True)
86
- web_thread.start()
87
-
88
- # 2. حفظ الكوكيز
89
- save_cookies()
90
-
91
- # 3. تشغيل حلقة البث
92
  t = threading.Thread(target=monitor_loop, daemon=True)
93
  t.start()
94
 
95
- # حلقة مراقبة الـ Thread
96
- while True:
97
- if not t.is_alive():
98
- log("🚨 إعادة تشغيل المحرك...")
99
- t = threading.Thread(target=monitor_loop, daemon=True)
100
- t.start()
101
- time.sleep(3600)
 
2
  import time
3
  import subprocess
4
  import threading
5
+ import json
6
+ import pickle
7
  import psutil
8
+ from datetime import datetime, timezone
9
+ from google.oauth2.credentials import Credentials
10
+ from google.auth.transport.requests import Request
11
+ from googleapiclient.discovery import build
12
  from flask import Flask
13
 
14
  # ------------------------------------------------------------
15
+ # الإعدادات الأساسية (تتغير ديناميكياً لتفادي تداخل المشاريع)
16
  # ------------------------------------------------------------
17
  TIKTOK_USERNAME = "@a3dam_111"
 
 
 
18
 
19
+ # استخراج اسم نقي للملفات لمنع التداخل والاصطدام بين السكربتات الشغالة معاً
20
+ USER_TAG = TIKTOK_USERNAME.replace("@", "").strip()
21
 
22
+ CUSTOM_STREAM_KEY = os.getenv("CUSTOM_STREAM_KEY", "p3dq-5cex-bc1k-vet7-bsyx")
23
+ FFMPEG_LOG = f"/tmp/ffmpeg_{USER_TAG}.log"
24
+ MONITOR_LOG = f"/tmp/monitor_{USER_TAG}.log"
25
+ LOCK_FILE = f"/tmp/app_monitor_core_{USER_TAG}.lock"
26
+ TOKEN_FILE = f"/tmp/yt_token_{USER_TAG}.pkl"
27
+ BROADCAST_FILE = f"/tmp/current_broadcast_{USER_TAG}.json"
28
 
29
+ TIKTOK_COOKIES = os.getenv("TIKTOK_COOKIES", "")
30
+ TIKTOK_COOKIES_ADULT = os.getenv("TIKTOK_COOKIES_ADULT", "")
31
 
32
  def log(msg):
33
  ts = time.strftime("%Y-%m-%d %H:%M:%S")
 
37
  f.write(line + "\n")
38
 
39
  # ------------------------------------------------------------
40
+ # حفظ الكوكيز باسم معزول
41
  # ------------------------------------------------------------
42
  def save_cookies():
43
+ if TIKTOK_COOKIES:
44
+ with open(f"/tmp/tiktok_cookies_{USER_TAG}.txt", "w") as f:
45
+ f.write(TIKTOK_COOKIES)
46
+ if TIKTOK_COOKIES_ADULT:
47
+ with open(f"/tmp/tiktok_cookies_adult_{USER_TAG}.txt", "w") as f:
48
+ f.write(TIKTOK_COOKIES_ADULT)
49
+
50
+ save_cookies()
51
 
52
  # ------------------------------------------------------------
53
+ # أدوات مساعدة
54
  # ------------------------------------------------------------
55
+ def kill_process_tree(pid):
56
+ try:
57
+ parent = psutil.Process(pid)
58
+ for child in parent.children(recursive=True):
59
+ child.kill()
60
+ parent.kill()
61
+ except:
62
+ pass
63
+
64
  def get_tiktok_stream_url():
65
+ cookies_adult = f"/tmp/tiktok_cookies_adult_{USER_TAG}.txt"
66
+ cookies_normal = f"/tmp/tiktok_cookies_{USER_TAG}.txt"
67
+
68
  try:
69
  url = f"https://www.tiktok.com/{TIKTOK_USERNAME}/live"
70
+ cmd = ["streamlink", "--stream-url", url, "best", "--retry-streams", "5", "--retry-open", "3"]
71
+ if os.path.exists(cookies_adult): cmd.extend(["--cookies", cookies_adult])
72
+ elif os.path.exists(cookies_normal): cmd.extend(["--cookies", cookies_normal])
73
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=25)
74
+ if result.returncode == 0 and result.stdout.strip():
75
+ return result.stdout.strip().split("\n")[0]
76
+ except: pass
77
+
78
+ try:
79
+ url = f"https://www.tiktok.com/{TIKTOK_USERNAME}/live"
80
+ cmd = ["yt-dlp", "--no-warnings", "--no-check-certificates", "--no-live-from-start", "--no-cache-dir", "--rm-cache-dir", "-f", "best", "--get-url", url]
81
+ if os.path.exists(cookies_adult): cmd.extend(["--cookies", cookies_adult])
82
+ elif os.path.exists(cookies_normal): cmd.extend(["--cookies", cookies_normal])
83
  result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
84
+ if result.returncode == 0 and result.stdout.strip():
85
+ return result.stdout.strip().split("\n")[0]
86
+ except: pass
87
+ return None
88
 
89
  def start_stream(stream_key, stream_url):
90
+ if not stream_key or not stream_url: return None
91
+ log("🎬 بدء البث (تزامن كامل الصوت والصورة + تحويل H.264)...")
92
  rtmp_url = f"rtmp://a.rtmp.youtube.com/live2/{stream_key}"
93
+
94
+ # تم حذف سطر killall العام لمنع إغلاق السكربتات الأخرى الشغالة معاً
95
+
96
+ cmd = [
97
+ "nice", "-n", "19", "ffmpeg", "-hide_banner", "-loglevel", "warning",
98
+ "-thread_queue_size", "2048",
99
+ "-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",
100
+ "-headers", "Referer: https://www.tiktok.com/\r\n",
101
+
102
+ # --- علاج مشكلة عدم تزامن الصوت والصورة عند تقطيع إنترنت المذيع ---
103
+ "-use_wallclock_as_timestamps", "1",
104
+ "-fflags", "+genpts+igndts+discardcorrupt",
105
+ "-flags", "+global_header", "-i", stream_url,
106
+
107
+ # تثبيت الـ Framerate على 30 لسد الفجوات الزمنية عند سقوط الإطارات
108
+ "-c:v", "libx264", "-preset", "ultrafast", "-b:v", "2500k", "-maxrate", "2500k", "-bufsize", "5000k", "-pix_fmt", "yuv420p", "-g", "60", "-r", "30",
109
+ "-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",
110
+ "-reconnect", "1", "-reconnect_at_eof", "1", "-reconnect_delay_max", "60", "-rtmp_live", "live", "-max_muxing_queue_size", "2048", "-f", "flv", rtmp_url
111
+ ]
112
+ with open(FFMPEG_LOG, "w") as stderr_file:
113
+ proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=stderr_file, preexec_fn=os.setsid)
114
+ log(f"✅ ffmpeg PID: {proc.pid} للمستخدم {TIKTOK_USERNAME}")
115
+ return proc
116
 
117
  # ------------------------------------------------------------
118
+ # YouTube API (الفحص والتعرف الذكي عبر البصمة الوصفية)
119
+ # ------------------------------------------------------------
120
+ def build_youtube_client():
121
+ try:
122
+ if os.path.exists(TOKEN_FILE):
123
+ with open(TOKEN_FILE, "rb") as f: creds = pickle.load(f)
124
+ if creds and creds.valid: return build('youtube', 'v3', credentials=creds)
125
+ if creds and creds.expired and creds.refresh_token:
126
+ creds.refresh(Request())
127
+ with open(TOKEN_FILE, "wb") as f: pickle.dump(creds, f)
128
+ return build('youtube', 'v3', credentials=creds)
129
+ creds_json = os.getenv("YT_CREDENTIALS_JSON")
130
+ if not creds_json: return None
131
+ data = json.loads(creds_json)
132
+ 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"]))
133
+ if creds.expired and creds.refresh_token: creds.refresh(Request())
134
+ with open(TOKEN_FILE, "wb") as f: pickle.dump(creds, f)
135
+ return build('youtube', 'v3', credentials=creds)
136
+ except Exception as e:
137
+ log(f"❌ فشل بناء عميل YouTube: {e}")
138
+ return None
139
+
140
+ def create_new_broadcast():
141
+ try:
142
+ yt = build_youtube_client()
143
+ if not yt: return None, None
144
+
145
+ now = datetime.now(timezone.utc)
146
+ date_str = now.strftime('%d-%m-%Y %H:%M')
147
+ title = f"🔥 بث تيك توك أحمد عبدالله العراقي المباشر | {date_str} 🚀"
148
+
149
+ # حقن بصمة الحساب السرية داخل الوصف لمنع السكربتات الأخرى من حذفه
150
+ description = (
151
+ "أهلاً بكم في البث المباشر المخصص لـ أحمد عبدالله العراقي.\n"
152
+ f"معرف الحساب المسؤول: [{TIKTOK_USERNAME}]\n"
153
+ "استمتعوا معنا بأجواء البث وتفاعلوا في التعليقات!\n\n"
154
+ "🔔 لا تنسوا الاشتراك في القناة وتفعيل زر التنبيهات ليصلكم كل جديد فور انطلاق البث.\n"
155
+ "شكراً لدعمكم المتواصل!\n\n"
156
+ "#أحمد_عبدالله_العراقي #بث_مباشر #تيك_توك #TikTokLive #العراق #بث #مباشر #Ahmd_Abdullah"
157
+ )
158
+
159
+ insert_body = {
160
+ "snippet": {"title": title, "description": description, "scheduledStartTime": now.isoformat()},
161
+ "status": {"privacyStatus": "public"},
162
+ "contentDetails": {"enableAutoStart": True, "enableAutoStop": False, "latencyPreference": "normal"}
163
+ }
164
+
165
+ new_broadcast = yt.liveBroadcasts().insert(part="snippet,status,contentDetails", body=insert_body).execute()
166
+ bid = new_broadcast["id"]
167
+ log(f"✅ بث جديد تم إنشاؤه للـ ID: {bid}")
168
+
169
+ stream_insert = yt.liveStreams().insert(
170
+ part="snippet,cdn,contentDetails",
171
+ body={"snippet": {"title": f"TikTok Stream Key {USER_TAG}"}, "cdn": {"ingestionType": "rtmp", "resolution": "1080p", "frameRate": "30fps"}, "contentDetails": {"isReusable": True}}
172
+ ).execute()
173
+ stream_id = stream_insert["id"]
174
+ stream_key = stream_insert["cdn"]["ingestionInfo"]["streamName"]
175
+
176
+ yt.liveBroadcasts().bind(id=bid, part="id", streamId=stream_id).execute()
177
+
178
+ with open(BROADCAST_FILE, "w") as f:
179
+ json.dump({"broadcast_id": bid, "stream_key": stream_key}, f)
180
+ return stream_key, bid
181
+ except Exception as e:
182
+ log(f"❌ فشل إنشاء بث جديد: {e}")
183
+ return None, None
184
+
185
+ def finalize_all_broadcasts():
186
+ try:
187
+ yt = build_youtube_client()
188
+ if not yt: return
189
+ # قراءة الـ snippet لفحص الوصف والتحقق من بصمة المشروع
190
+ broadcasts = yt.liveBroadcasts().list(mine=True, part="id,status,snippet", maxResults=50).execute()
191
+ for item in broadcasts.get("items", []):
192
+ bid = item["id"]
193
+ status = item.get("status", {}).get("lifeCycleStatus", "")
194
+ description = item.get("snippet", {}).get("description", "")
195
+
196
+ # نغلق البث فقط وفقط إذا كان يحمل بصمة هذا المستخدم لعدم تدمير البث الآخر
197
+ if status in ["ready", "created", "live", "testing"] and f"[{TIKTOK_USERNAME}]" in description:
198
+ try:
199
+ yt.liveBroadcasts().transition(broadcastStatus="complete", id=bid, part="status").execute()
200
+ log(f"🧹 تم تنظيف وإغلاق بث قديم معلق يخص الحساب {TIKTOK_USERNAME}")
201
+ except: pass
202
+ except: pass
203
+
204
+ def finalize_current_broadcast(broadcast_id):
205
+ if not broadcast_id: return
206
+ try:
207
+ yt = build_youtube_client()
208
+ if not yt: return
209
+ yt.liveBroadcasts().transition(broadcastStatus="complete", id=broadcast_id, part="status").execute()
210
+ except: pass
211
+ if os.path.exists(BROADCAST_FILE): os.remove(BROADCAST_FILE)
212
+
213
+ # ------------------------------------------------------------
214
+ # المحرك الرئيسي الآمن والمعزول كلياً
215
  # ------------------------------------------------------------
216
  def monitor_loop():
217
+ # حذفنا أمر killall الشامل الذي كان يسبب انهيار السكربت الأول عند نهوض الثاني
218
+ if os.path.exists(LOCK_FILE):
219
+ try:
220
+ with open(LOCK_FILE) as f:
221
+ os.kill(int(f.read().strip()), 0)
222
+ log(f"🔒 محرك الحساب {TIKTOK_USERNAME} يعمل بالفعل بشكل مستقر.")
223
+ return
224
+ except:
225
+ os.remove(LOCK_FILE)
226
+
227
+ with open(LOCK_FILE, "w") as f: f.write(str(os.getpid()))
228
+
229
+ finalize_all_broadcasts()
230
+ ffmpeg_proc = None
231
+ was_streaming = False
232
+ consecutive_fails = 0
233
+ current_stream_key = None
234
+ current_broadcast_id = None
235
+
236
+ log(f"🚀 تم إطلاق محرك المراقبة الخاص بـ {TIKTOK_USERNAME} بأمان وبصمة منفصلة.")
237
+
238
  while True:
239
  try:
240
  stream_url = get_tiktok_stream_url()
241
  if stream_url:
242
+ consecutive_fails = 0
243
+ if was_streaming and ffmpeg_proc and ffmpeg_proc.poll() is None: pass
244
+ elif was_streaming and ffmpeg_proc and ffmpeg_proc.poll() is not None:
245
+ kill_process_tree(ffmpeg_proc.pid)
246
+ ffmpeg_proc = start_stream(current_stream_key, stream_url)
247
+ else:
248
+ if not current_broadcast_id:
249
+ finalize_all_broadcasts()
250
+ stream_key, bid = create_new_broadcast()
251
+ if not stream_key: stream_key = CUSTOM_STREAM_KEY
252
+ current_broadcast_id = bid
253
+ current_stream_key = stream_key
254
+ ffmpeg_proc = start_stream(current_stream_key, stream_url)
255
+ was_streaming = True
256
  else:
257
+ if was_streaming:
258
+ consecutive_fails += 1
259
+ if consecutive_fails >= 6:
260
+ if ffmpeg_proc: kill_process_tree(ffmpeg_proc.pid)
261
+ ffmpeg_proc = None
262
+ was_streaming = False
263
+ if current_broadcast_id:
264
+ finalize_current_broadcast(current_broadcast_id)
265
+ current_broadcast_id = None
266
+ time.sleep(30)
267
+ except: time.sleep(30)
268
+
269
+ app = Flask(__name__)
270
+ @app.route("/")
271
+ def index(): return "OK"
272
 
273
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
274
  t = threading.Thread(target=monitor_loop, daemon=True)
275
  t.start()
276
 
277
+ # تنبيه: إذا قمت بتشغيل السكربت الثاني على نفس السيرفر، يرجى تغيير الـ port هنا إلى 781 مثلاً
278
+ app.run(host="0.0.0.0", port=7861, debug=False)