Update streamlit_app.py

#1
Files changed (1) hide show
  1. streamlit_app.py +286 -49
streamlit_app.py CHANGED
@@ -4,78 +4,287 @@ import os
4
  import time
5
  import subprocess
6
  import threading
7
- from datetime import datetime
 
 
 
 
 
 
8
  from TikTokLive import TikTokLiveClient
9
 
10
  # ══════════════════════════════════════════════════════════════════════════════
11
- # ⚙️ الإعدادات الثابتة (تعدل مرة واحدة فقط)
12
  # ══════════════════════════════════════════════════════════════════════════════
13
- TIKTOK_USERNAME = "@ah_md107" # الحساب المراد رصده
14
- YOUTUBE_STREAM_KEY = "hetf-6m1q-uyaj-sjww-30qc" # مفتاح بث يوتيوب
15
- POLL_INTERVAL = 60 # فحص كل دقيقة
16
- YT_RTMP_URL = f"rtmp://a.rtmp.youtube.com/live2/{YOUTUBE_STREAM_KEY}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  # ══════════════════════════════════════════════════════════════════════════════
19
- # 🚀 محرك العمل في الخلفية (Background Engine)
20
  # ══════════════════════════════════════════════════════════════════════════════
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  def run_monitor():
23
- """هذه الدالة هي التي تظل تعمل حتى لو أغلقت الصفحة"""
24
- print("✅ بدأ محرك المراقبة التلقائي 24/7...")
 
 
 
 
25
  while True:
26
  try:
27
- # فحص حالة البث
28
  client = TikTokLiveClient(unique_id=TIKTOK_USERNAME)
29
  is_live = asyncio.run(client.is_live())
30
-
 
 
 
 
 
 
 
 
31
  if is_live:
32
- print(f"[{datetime.now().strftime('%H:%M')}] 🔴 البث بدأ! جاري النقل إلى يوتيوب...")
33
-
34
- # أمر النقل المباشر - محسّن للصوت
35
- cmd = (
36
- f"streamlink https://www.tiktok.com/{TIKTOK_USERNAME}/live best --hls-live-edge 3 --stdout | "
37
- f"ffmpeg -thread_queue_size 8192 -i pipe:0 "
38
- f"-c:v copy "
39
- f"-c:a aac -ar 48000 -b:a 192k -ac 2 "
40
- f"-af 'highpass=f=200,lowpass=f=3000,aresample=async=1' "
41
- f"-buffer_size 1024k "
42
- f"-fflags +genpts+igndts "
43
- f"-max_interleave_delta 0 "
44
- f"-f flv {YT_RTMP_URL}"
45
- )
46
-
47
- process = subprocess.Popen(cmd, shell=True)
48
- process.wait() # سينتظر هنا حتى ين��هي البث فعلياً
49
- print("⏹️ انتهى البث، العودة لوضع المراقبة...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  else:
51
- #print(f"🔍 فحص: {TIKTOK_USERNAME} غير متصل حالياً.")
52
- pass
53
-
 
 
 
 
 
 
 
 
 
 
 
54
  except Exception as e:
55
  print(f"⚠️ تنبيه: {e}")
 
 
 
 
 
56
 
57
- time.sleep(POLL_INTERVAL)
58
-
59
- # تشغيل الخيط الخلفي فور تشغيل التطبيق (مرة واحدة فقط)
60
  if "background_started" not in st.session_state:
61
- # البحث عن أي خيط يعمل مسبقاً لمنع التكرار
62
  for thread in threading.enumerate():
63
- if thread.name == "TikTokMonitorThread":
64
  st.session_state.background_started = True
65
  break
66
  else:
67
- # إذا لم يكن هناك خيط، نبدأ واحد جديد
68
  monitor_thread = threading.Thread(target=run_monitor, name="TikTokMonitorThread", daemon=True)
69
  monitor_thread.start()
 
 
 
 
70
  st.session_state.background_started = True
71
 
72
  # ══════════════════════════════════════════════════════════════════════════════
73
- # 🎨 واجهة المستخدم (للمتابعة فقط)
74
  # ══════════════════════════════════════════════════════════════════════════════
75
-
76
  st.set_page_config(page_title="AutoStreamer 24/7", page_icon="🔴")
77
 
78
- # تنسيق الشكل
79
  st.markdown("""
80
  <style>
81
  .status-box {
@@ -89,20 +298,48 @@ st.markdown("""
89
 
90
  st.title("🔴 نظام البث التلقائي المستمر")
91
 
 
 
 
 
 
 
 
 
 
92
  st.markdown(f"""
93
  <div class="status-box">
94
- <h3>حالة النظام: <span style="color: #00ff00;">متصل ويعمل ✅</span></h3>
95
- <p>يتم مراقبة الحساب: <b>{TIKTOK_USERNAME}</b></p>
96
- <p class="live-tag">النظام يعمل في خلفية السيرفر الآن</p>
 
 
97
  </div>
98
  """, unsafe_allow_html=True)
99
 
100
- st.success("ℹ️ يمكنك إغلاق هذه الصفحة الآن. السيرفر سيقوم برصد البث ورفعه تلقائياً في أي وقت (اليوم، غداً، أو أي وقت).")
 
 
 
 
101
 
102
- st.info("💡 بمجرد أن يبدأ صاحب الحساب البث، سيقوم النظام بفتحه فوراً ونقله إلى قناتك على يوتيوب.")
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
- # عرض السجلات البسيطة
105
- if st.button("تحديث الحالة"):
106
  st.rerun()
107
 
108
- st.write(f"آخر فحص تم في: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
 
 
 
4
  import time
5
  import subprocess
6
  import threading
7
+ import signal
8
+ import json
9
+ import base64
10
+ import pickle
11
+ from datetime import datetime, timezone, timedelta
12
+ from google.auth.transport.requests import Request
13
+ from googleapiclient.discovery import build
14
  from TikTokLive import TikTokLiveClient
15
 
16
  # ══════════════════════════════════════════════════════════════════════════════
17
+ # ⚙️ الإعدادات
18
  # ══════════════════════════════════════════════════════════════════════════════
19
+ TIKTOK_USERNAME = "@ah_md107"
20
+ TOKEN = "/tmp/yt.pkl"
21
+ STATUS = "/tmp/status.json"
22
+ HEARTBEAT_FILE = "/tmp/heartbeat.json"
23
+
24
+ if os.getenv("TOKEN_B64"):
25
+ open(TOKEN, "wb").write(base64.b64decode(os.getenv("TOKEN_B64")))
26
+
27
+ # ══════════════════════════════════════════════════════════════════════════════
28
+ # 💓 نبض داخلي
29
+ # ══════════════════════════════════════════════════════════════════════════════
30
+ def internal_heartbeat():
31
+ while True:
32
+ try:
33
+ with open(HEARTBEAT_FILE, "w") as f:
34
+ json.dump({
35
+ "last_ping": datetime.now().isoformat(),
36
+ "status": "alive"
37
+ }, f)
38
+ time.sleep(30)
39
+ except:
40
+ time.sleep(30)
41
+
42
+ # ══════════════════════════════════════════════════════════════════════════════
43
+ # 🎨 عنوان + وصف فخم
44
+ # ══════════════════════════════════════════════════════════════════════════════
45
+ def generate_fancy_title():
46
+ now = datetime.now()
47
+ templates = [
48
+ "🔴 بث أحمد عبدالله العراق | {date} | {time}",
49
+ "⚡ LIVE: أحمد عبدالله | العراق | {date} - {time}",
50
+ "🎥 بث مباشر: أحمد عبدالله العراق | {date} | {time}",
51
+ "📡 أحمد عبدالله - بث حي | العراق | {date} | {time}",
52
+ "🌟 بث حصري: أحمد عبدالله | {date} | {time}",
53
+ ]
54
+ return random.choice(templates).format(
55
+ date=now.strftime('%Y-%m-%d'),
56
+ time=now.strftime('%I:%M %p')
57
+ )
58
+
59
+ def generate_fancy_description():
60
+ now = datetime.now()
61
+ arabic_tags = [
62
+ "#أحمد_عبدالله", "#العراق", "#بث_مباشر", "#تيك_توك", "#يوتيوب",
63
+ "#بث_مباشر_الآن", "#مباشر", "#لايف", "#مشاهير_العراق",
64
+ "#محتوى_مباشر", "#بث_مباشر_يوتيوب", "#الموصل", "#بغداد",
65
+ "#بصرة", "#أنبار", "#كردستان", "#العراق_الآن", "#بث_حي",
66
+ "#محتوى_عراقي", "#شخصيات_عراقية", "#ترند_العراق"
67
+ ]
68
+ english_tags = [
69
+ "#AhmedAbdullah", "#TikTokLive", "#YouTubeLive", "#Iraq",
70
+ "#LiveStream", "#Streaming", "#Viral", "#Trending",
71
+ "#LiveNow", "#TikTok", "#YouTube", "#ContentCreator",
72
+ "#IraqiCreator", "#LiveBroadcast", "#RealTime",
73
+ "#MustWatch", "#LiveStreaming", "#SocialMedia",
74
+ "#Entertainment", "#BreakingLive"
75
+ ]
76
+ import random
77
+ selected_arabic = random.sample(arabic_tags, min(8, len(arabic_tags)))
78
+ selected_english = random.sample(english_tags, min(8, len(english_tags)))
79
+ return f"""🎥 بث مباشر حصري من تيك توك | تابعوا أحدث اللحظات!
80
+
81
+ 📡 البث مباشر الآن من حساب: @{TIKTOK_USERNAME}
82
+ 🕐 تاريخ البث: {now.strftime('%A, %d %B %Y')}
83
+ ⏰ توقيت البث: {now.strftime('%I:%M %p')}
84
+
85
+ ━━━━━━━━━━━━━━━━━━━━━━
86
+ 🏷️ الهاشتاقات العربية:
87
+ {' '.join(selected_arabic)}
88
+
89
+ 🏷️ Arabic Hashtags:
90
+ {' '.join(selected_english)}
91
+
92
+ ━━━━━━━━━━━━━━━━━━━━━━
93
+ 🔗 تابع البث المباشر الآن!
94
+ 🔄 يتم تحديث الوصف تلقائياً في كل بث جديد
95
+ """
96
 
97
  # ══════════════════════════════════════════════════════════════════════════════
98
+ # 🚀 YouTube API
99
  # ══════════════════════════════════════════════════════════════════════════════
100
+ def start_yt_live():
101
+ creds = pickle.load(open(TOKEN, "rb"))
102
+ if creds.expired:
103
+ creds.refresh(Request())
104
+ pickle.dump(creds, open(TOKEN, "wb"))
105
+
106
+ yt = build('youtube', 'v3', credentials=creds)
107
+ title = generate_fancy_title()
108
+ description = generate_fancy_description()
109
+ start_time = (datetime.now(timezone.utc) - timedelta(seconds=10)).isoformat().replace("+00:00", "Z")
110
+
111
+ bc = yt.liveBroadcasts().insert(
112
+ part="snippet,status,contentDetails",
113
+ body={
114
+ "snippet": {
115
+ "title": title,
116
+ "scheduledStartTime": start_time,
117
+ "description": description
118
+ },
119
+ "status": {"privacyStatus": "public", "selfDeclaredMadeForKids": False},
120
+ "contentDetails": {
121
+ "enableAutoStart": True,
122
+ "enableAutoStop": True,
123
+ "enableDvr": True,
124
+ "recordFromStart": True,
125
+ "monitorStream": {
126
+ "enableMonitorStream": True,
127
+ "broadcastStreamDelayMs": 0
128
+ }
129
+ }
130
+ }
131
+ ).execute()
132
+
133
+ stream = yt.liveStreams().insert(
134
+ part="snippet,cdn",
135
+ body={
136
+ "snippet": {"title": "TikTok Relay"},
137
+ "cdn": {
138
+ "ingestionType": "rtmp",
139
+ "resolution": "720p",
140
+ "frameRate": "30fps"
141
+ }
142
+ }
143
+ ).execute()
144
+
145
+ yt.liveBroadcasts().bind(part="id", id=bc["id"], streamId=stream["id"]).execute()
146
+
147
+ rtmp = f"{stream['cdn']['ingestionInfo']['ingestionAddress']}/{stream['cdn']['ingestionInfo']['streamName']}"
148
+ return bc["id"], rtmp, f"https://youtu.be/{bc['id']}", title
149
+
150
+ # ══════════════════════════════════════════════════════════════════════════════
151
+ # 🔍 كشف البث + نقل (نفس كودك)
152
+ # ══════════════════════════════════════════════════════════════════════════════
153
+ def kill_process_and_children(process):
154
+ try:
155
+ os.killpg(os.getpgid(process.pid), signal.SIGTERM)
156
+ process.wait(timeout=5)
157
+ except:
158
+ try:
159
+ os.killpg(os.getpgid(process.pid), signal.SIGKILL)
160
+ except:
161
+ pass
162
 
163
  def run_monitor():
164
+ print("✅ بدأ محرك المراقبة 24/7...")
165
+
166
+ current_process = None
167
+ last_room_id = None
168
+ current_rtmp = None
169
+
170
  while True:
171
  try:
 
172
  client = TikTokLiveClient(unique_id=TIKTOK_USERNAME)
173
  is_live = asyncio.run(client.is_live())
174
+
175
+ current_room_id = None
176
+ if is_live:
177
+ try:
178
+ room_info = asyncio.run(client.get_room_info())
179
+ current_room_id = room_info.get("room_id") if room_info else "unknown"
180
+ except:
181
+ current_room_id = "unknown"
182
+
183
  if is_live:
184
+ print(f"[{datetime.now().strftime('%H:%M')}] 🔴 البث مفتوح! room_id: {current_room_id}")
185
+
186
+ if current_room_id != last_room_id:
187
+ print(f"🆕 بث جديد! {last_room_id} → {current_room_id}")
188
+
189
+ if current_process and current_process.poll() is None:
190
+ print("⏹️ إغلاق البث القديم...")
191
+ kill_process_and_children(current_process)
192
+ current_process = None
193
+ time.sleep(5)
194
+
195
+ # إنشاء بث يوتيوب جديد بالعنوان
196
+ print("▶️ إنشاء بث يوتيوب جديد...")
197
+ bid, rtmp, link, title = start_yt_live()
198
+ current_rtmp = rtmp
199
+ last_room_id = current_room_id
200
+
201
+ # نقل البث (نفس كودك)
202
+ cmd = (
203
+ f"streamlink https://www.tiktok.com/{TIKTOK_USERNAME}/live best --hls-live-edge 3 --stdout | "
204
+ f"ffmpeg -thread_queue_size 8192 -i pipe:0 "
205
+ f"-c:v copy "
206
+ f"-c:a aac -ar 48000 -b:a 192k -ac 2 "
207
+ f"-af 'highpass=f=200,lowpass=f=3000,aresample=async=1' "
208
+ f"-buffer_size 1024k "
209
+ f"-fflags +genpts+igndts "
210
+ f"-max_interleave_delta 0 "
211
+ f"-f flv {rtmp}"
212
+ )
213
+
214
+ current_process = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid)
215
+
216
+ with open(STATUS, "w") as f:
217
+ json.dump({
218
+ "live": True,
219
+ "link": link,
220
+ "title": title,
221
+ "started_at": datetime.now().isoformat()
222
+ }, f)
223
+
224
+ print(f"✅ البث بدأ: {link}")
225
+ print(f"📝 العنوان: {title}")
226
+
227
+ else:
228
+ if current_process and current_process.poll() is not None:
229
+ print("⚠️ ffmpeg توقف! إعادة التشغيل...")
230
+ cmd = (
231
+ f"streamlink https://www.tiktok.com/{TIKTOK_USERNAME}/live best --hls-live-edge 3 --stdout | "
232
+ f"ffmpeg -thread_queue_size 8192 -i pipe:0 "
233
+ f"-c:v copy "
234
+ f"-c:a aac -ar 48000 -b:a 192k -ac 2 "
235
+ f"-af 'highpass=f=200,lowpass=f=3000,aresample=async=1' "
236
+ f"-buffer_size 1024k "
237
+ f"-fflags +genpts+igndts "
238
+ f"-max_interleave_delta 0 "
239
+ f"-f flv {current_rtmp}"
240
+ )
241
+ current_process = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid)
242
+
243
  else:
244
+ print(f"[{datetime.now().strftime('%H:%M')}] 🔍 {TIKTOK_USERNAME} غير متصل")
245
+
246
+ if current_process and current_process.poll() is None:
247
+ print("⏹️ أحمد قافل البث، إغلاق...")
248
+ kill_process_and_children(current_process)
249
+ current_process = None
250
+ last_room_id = None
251
+ current_rtmp = None
252
+
253
+ with open(STATUS, "w") as f:
254
+ json.dump({"live": False}, f)
255
+ else:
256
+ last_room_id = None
257
+
258
  except Exception as e:
259
  print(f"⚠️ تنبيه: {e}")
260
+ if current_process:
261
+ kill_process_and_children(current_process)
262
+ current_process = None
263
+
264
+ time.sleep(60)
265
 
266
+ # ══════════════════════════════════════════════════════════════════════════════
267
+ # 🚀 تشغيل المحرك + النبض
268
+ # ══════════════════════════════════════════════════════════════════════════════
269
  if "background_started" not in st.session_state:
 
270
  for thread in threading.enumerate():
271
+ if thread.name in ["TikTokMonitorThread", "HeartbeatThread"]:
272
  st.session_state.background_started = True
273
  break
274
  else:
 
275
  monitor_thread = threading.Thread(target=run_monitor, name="TikTokMonitorThread", daemon=True)
276
  monitor_thread.start()
277
+
278
+ heartbeat_thread = threading.Thread(target=internal_heartbeat, name="HeartbeatThread", daemon=True)
279
+ heartbeat_thread.start()
280
+
281
  st.session_state.background_started = True
282
 
283
  # ══════════════════════════════════════════════════════════════════════════════
284
+ # 🎨 واجهة المستخدم
285
  # ══════════════════════════════════════════════════════════════════════════════
 
286
  st.set_page_config(page_title="AutoStreamer 24/7", page_icon="🔴")
287
 
 
288
  st.markdown("""
289
  <style>
290
  .status-box {
 
298
 
299
  st.title("🔴 نظام البث التلقائي المستمر")
300
 
301
+ try:
302
+ with open(STATUS, "r") as f:
303
+ s = json.load(f)
304
+ except:
305
+ s = {"live": False}
306
+
307
+ status_color = "#00ff00" if s.get("live") else "#ffaa00"
308
+ status_text = "🔴 يبث الآن" if s.get("live") else "🟡 في وضع المراقبة"
309
+
310
  st.markdown(f"""
311
  <div class="status-box">
312
+ <h3>حالة النظام: <span style="color: {status_color};">{status_text}</span></h3>
313
+ <p>الحساب المراقب: <b>{TIKTOK_USERNAME}</b></p>
314
+ <p class="live-tag">يعمل في الخلفية 24/7</p>
315
+ {f'<p>📝 العنوان: {s.get("title", "")}</p>' if s.get("title") else ''}
316
+ {f'<p>🔗 <a href="{s.get("link")}" target="_blank">رابط البث</a></p>' if s.get("link") else ''}
317
  </div>
318
  """, unsafe_allow_html=True)
319
 
320
+ if s.get("live") and s.get("link"):
321
+ st.success("🔴 البث مباشر الآن على يوتيوب!")
322
+ st.video(s["link"])
323
+
324
+ st.info("💡 يمكنك إغلاق هذه الصفحة. النظام يرصد البث ويرفعه تلقائياً.")
325
 
326
+ # عرض النبض
327
+ try:
328
+ if os.path.exists(HEARTBEAT_FILE):
329
+ with open(HEARTBEAT_FILE, "r") as f:
330
+ hb = json.load(f)
331
+ last_ping = datetime.fromisoformat(hb["last_ping"])
332
+ delta = (datetime.now() - last_ping).total_seconds()
333
+ if delta < 60:
334
+ st.success(f"💓 النظام يعمل (آخر نبض: {int(delta)} ثانية)")
335
+ else:
336
+ st.warning(f"⚠️ بطيء (آخر نبض: {int(delta)} ثانية)")
337
+ except:
338
+ pass
339
 
340
+ if st.button("🔄 تحديث الحالة"):
 
341
  st.rerun()
342
 
343
+ st.write(f"⏱️ آخر تحديث: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
344
+ st.markdown("---")
345
+ st.caption("🚀 TikTok → YouTube Auto Relay | أحمد عبدالله العراق")