Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,7 +5,7 @@ import requests
|
|
| 5 |
import asyncio
|
| 6 |
import re
|
| 7 |
import urllib3
|
| 8 |
-
from flask import Flask, jsonify, make_response, request
|
| 9 |
from supabase import create_client
|
| 10 |
from pyrogram import Client, filters, enums, idle
|
| 11 |
from pyrogram.errors import SessionPasswordNeeded, PhoneCodeInvalid, PhoneCodeExpired, UserDeactivated, SessionRevoked, AuthKeyUnregistered, FloodWait
|
|
@@ -19,9 +19,12 @@ API_ID = int(os.environ.get("API_ID", 0))
|
|
| 19 |
API_HASH = os.environ.get("API_HASH")
|
| 20 |
SUPABASE_URL = os.environ.get("SUPABASE_URL")
|
| 21 |
SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
|
| 22 |
-
BYSE_API_KEY = os.environ.get("BYSE_API_KEY")
|
| 23 |
|
| 24 |
PREMIUM_CHANNEL_ID = -1002825744390
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
# WebApp URL (index.html)
|
| 27 |
WEB_APP_URL = "https://rony90790.github.io/Forward-bot/index.html"
|
|
@@ -47,6 +50,84 @@ bot = Client("file_unlocker_bot", api_id=API_ID, api_hash=API_HASH, bot_token=BO
|
|
| 47 |
async def db_query(func):
|
| 48 |
return await asyncio.to_thread(func)
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
# ================= FLASK API ROUTES =================
|
| 51 |
@app.route('/')
|
| 52 |
def index():
|
|
@@ -312,12 +393,10 @@ async def clone_videos_background(client, source_id, dest_id, status_msg):
|
|
| 312 |
else:
|
| 313 |
await status_msg.edit_text(f"⏳ <b>Cloning started!</b>\nFetching video list from <code>{source_id}</code>...\n<i>This might take a few minutes if the group has many videos.</i>", parse_mode=enums.ParseMode.HTML)
|
| 314 |
|
| 315 |
-
# === Auto Reconnect & Retry loop for Video Scanning ===
|
| 316 |
video_ids = []
|
| 317 |
retries = 5
|
| 318 |
while retries > 0:
|
| 319 |
try:
|
| 320 |
-
# যদি বট ক্লায়েন্ট কোনো কারণে ডিসকানেক্ট হয়, রিকানেক্ট করবে
|
| 321 |
if not client.is_connected:
|
| 322 |
try: await client.connect()
|
| 323 |
except: pass
|
|
@@ -328,18 +407,16 @@ async def clone_videos_background(client, source_id, dest_id, status_msg):
|
|
| 328 |
video_ids.append(msg.id)
|
| 329 |
if len(video_ids) % 200 == 0:
|
| 330 |
await asyncio.sleep(0.1)
|
| 331 |
-
break
|
| 332 |
except Exception as e:
|
| 333 |
err_msg = str(e).lower()
|
| 334 |
-
# যদি ডিসকানেক্ট বা কানেকশন জনিত সমস্যা হয়
|
| 335 |
if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "reset" in err_msg:
|
| 336 |
retries -= 1
|
| 337 |
-
video_ids = []
|
| 338 |
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)
|
| 339 |
await asyncio.sleep(10)
|
| 340 |
else:
|
| 341 |
-
raise e
|
| 342 |
-
# =======================================================
|
| 343 |
|
| 344 |
if not video_ids:
|
| 345 |
if last_copied_id:
|
|
@@ -347,7 +424,7 @@ async def clone_videos_background(client, source_id, dest_id, status_msg):
|
|
| 347 |
else:
|
| 348 |
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)
|
| 349 |
|
| 350 |
-
video_ids.reverse()
|
| 351 |
total = len(video_ids)
|
| 352 |
|
| 353 |
if last_copied_id:
|
|
@@ -362,7 +439,6 @@ async def clone_videos_background(client, source_id, dest_id, status_msg):
|
|
| 362 |
copy_success = False
|
| 363 |
copy_retries = 3
|
| 364 |
|
| 365 |
-
# === Video Sending Auto Reconnect & Retry ===
|
| 366 |
while copy_retries > 0:
|
| 367 |
try:
|
| 368 |
if not client.is_connected:
|
|
@@ -373,21 +449,19 @@ async def clone_videos_background(client, source_id, dest_id, status_msg):
|
|
| 373 |
success += 1
|
| 374 |
await save_progress(source_id, dest_id, msg_id)
|
| 375 |
copy_success = True
|
| 376 |
-
break
|
| 377 |
except FloodWait as e:
|
| 378 |
-
# লিমিট এররে রিট্রাই কমানো হবে না, শুধু ওয়েট করবে
|
| 379 |
await asyncio.sleep(e.value + 2)
|
| 380 |
except Exception as e:
|
| 381 |
err_msg = str(e).lower()
|
| 382 |
if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "reset" in err_msg:
|
| 383 |
copy_retries -= 1
|
| 384 |
-
await asyncio.sleep(5)
|
| 385 |
else:
|
| 386 |
-
break
|
| 387 |
|
| 388 |
if not copy_success:
|
| 389 |
failed += 1
|
| 390 |
-
# ============================================
|
| 391 |
|
| 392 |
if index % 20 == 0 or index == total:
|
| 393 |
try:
|
|
@@ -546,7 +620,7 @@ async def handle_media_upload(client, message):
|
|
| 546 |
original_file = await message.download(progress=download_progress)
|
| 547 |
clean_upload_file = original_file
|
| 548 |
|
| 549 |
-
# ১. ওয়াটারমার্ক (Watermark) করা -
|
| 550 |
if media_type == "video" and not is_large_video:
|
| 551 |
await status_msg.edit_text("⏳ Watermarking video... (HD + Superfast Processing)")
|
| 552 |
watermarked_file = f"{original_file}_wm.mp4"
|
|
@@ -561,35 +635,31 @@ async def handle_media_upload(client, message):
|
|
| 561 |
if process.returncode == 0 and os.path.exists(watermarked_file):
|
| 562 |
clean_upload_file = watermarked_file
|
| 563 |
|
| 564 |
-
# ২. byse.sx
|
| 565 |
if media_type == "video":
|
| 566 |
-
await status_msg.edit_text("⏳ Uploading Clean HD video to
|
| 567 |
-
api_endpoint = "https://api.byse.sx/upload/server"
|
| 568 |
-
loop = asyncio.get_event_loop()
|
| 569 |
-
response = await loop.run_in_executor(None, lambda: requests.get(api_endpoint, params={'key': BYSE_API_KEY}, timeout=30))
|
| 570 |
-
result = response.json()
|
| 571 |
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
file_code = upload_res['files'][0].get('filecode')
|
| 580 |
-
if file_code: embed_link = f"https://bysesayeveum.com/e/{file_code}"
|
| 581 |
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
|
|
|
| 585 |
|
| 586 |
if is_large_video:
|
| 587 |
-
|
|
|
|
| 588 |
await client.send_video(message.chat.id, message.video.file_id, caption=admin_cap, parse_mode=enums.ParseMode.HTML)
|
| 589 |
await status_msg.delete()
|
| 590 |
return
|
| 591 |
|
| 592 |
-
# ৩. টেলিগ্রামে
|
| 593 |
telegram_file = clean_upload_file
|
| 594 |
if is_blur and not is_large_video:
|
| 595 |
await status_msg.edit_text(f"⏳ Applying {blur_percent}% blur for Telegram broadcast...")
|
|
@@ -624,7 +694,13 @@ async def handle_media_upload(client, message):
|
|
| 624 |
caption_text = f"{clean_caption}\n\n👇 <i>Click the button below to open Bot!</i>" if clean_caption else f"🔥 <b>New Premium Viral Content!</b> 🔞\n\n🎬 <b>Watch HD Video Here:</b>\n👉 <b><a href='{bot_link}'>▶️ Click Here to Watch</a></b>\n\n👇 <i>Click the button below to open Bot!</i>"
|
| 625 |
|
| 626 |
group_markup = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Watch Full Video Here 🔞", url=bot_link)]])
|
| 627 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 628 |
|
| 629 |
thumb_path = None
|
| 630 |
if media_type == "video":
|
|
|
|
| 5 |
import asyncio
|
| 6 |
import re
|
| 7 |
import urllib3
|
| 8 |
+
from flask import Flask, jsonify, make_response, request, Response
|
| 9 |
from supabase import create_client
|
| 10 |
from pyrogram import Client, filters, enums, idle
|
| 11 |
from pyrogram.errors import SessionPasswordNeeded, PhoneCodeInvalid, PhoneCodeExpired, UserDeactivated, SessionRevoked, AuthKeyUnregistered, FloodWait
|
|
|
|
| 19 |
API_HASH = os.environ.get("API_HASH")
|
| 20 |
SUPABASE_URL = os.environ.get("SUPABASE_URL")
|
| 21 |
SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
|
|
|
|
| 22 |
|
| 23 |
PREMIUM_CHANNEL_ID = -1002825744390
|
| 24 |
+
STORAGE_CHANNEL_ID = -1002825744390 # যে চ্যানেলে ভিডিও স্টোর হবে
|
| 25 |
+
|
| 26 |
+
# আপনার Hugging Face স্পেসের ডিরেক্ট ইউআরএল (টোকেন জেনারেট করার জন্য)
|
| 27 |
+
BACKEND_URL = os.environ.get("BACKEND_URL", "https://pmrony-forwardbot.hf.space")
|
| 28 |
|
| 29 |
# WebApp URL (index.html)
|
| 30 |
WEB_APP_URL = "https://rony90790.github.io/Forward-bot/index.html"
|
|
|
|
| 50 |
async def db_query(func):
|
| 51 |
return await asyncio.to_thread(func)
|
| 52 |
|
| 53 |
+
# ==================== CUSTOM VIDEO STREAMING ENGINE ====================
|
| 54 |
+
def get_file_stream(message_id):
|
| 55 |
+
"""টেলিগ্রামের স্টোরেজ চ্যানেল থেকে রিয়েল-টাইমে ডাটা স্ট্রিম করার জেনারেটর"""
|
| 56 |
+
async def stream_generator():
|
| 57 |
+
try:
|
| 58 |
+
msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
|
| 59 |
+
if not msg or not msg.video:
|
| 60 |
+
return
|
| 61 |
+
|
| 62 |
+
# chunk_size অনুযায়ী ডাটা রিয়েল-টাইমে রিড করে পাঠানো হবে
|
| 63 |
+
async for chunk in bot.stream_media(msg.video):
|
| 64 |
+
yield chunk
|
| 65 |
+
except Exception as e:
|
| 66 |
+
print(f"Error in stream_generator: {e}")
|
| 67 |
+
|
| 68 |
+
# Async জেনারেটরকে Flask-এর সাথে সিঙ্ক করার জন্য ইভেন্ট লুপ ফিক্স
|
| 69 |
+
loop = asyncio.new_event_loop()
|
| 70 |
+
def run():
|
| 71 |
+
asyncio.set_event_loop(loop)
|
| 72 |
+
gen = stream_generator()
|
| 73 |
+
while True:
|
| 74 |
+
try:
|
| 75 |
+
chunk = loop.run_until_complete(gen.__anext__())
|
| 76 |
+
yield chunk
|
| 77 |
+
except StopAsyncIteration:
|
| 78 |
+
break
|
| 79 |
+
except Exception as e:
|
| 80 |
+
print(f"Sync wrapper error: {e}")
|
| 81 |
+
break
|
| 82 |
+
loop.close()
|
| 83 |
+
|
| 84 |
+
return run()
|
| 85 |
+
|
| 86 |
+
@app.route('/stream/<int:message_id>')
|
| 87 |
+
def stream_video(message_id):
|
| 88 |
+
"""অনলাইনে প্লেয়ারে ভিডিও দেখার লিংক"""
|
| 89 |
+
try:
|
| 90 |
+
async def get_media_info():
|
| 91 |
+
msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
|
| 92 |
+
if msg and msg.video:
|
| 93 |
+
return msg.video.file_size, msg.video.file_name, msg.video.mime_type
|
| 94 |
+
return None, None, None
|
| 95 |
+
|
| 96 |
+
file_size, file_name, mime_type = run_async(get_media_info())
|
| 97 |
+
if not file_size:
|
| 98 |
+
return "File not found or invalid message", 404
|
| 99 |
+
|
| 100 |
+
response = make_response(Response(get_file_stream(message_id), mimetype=mime_type or "video/mp4"))
|
| 101 |
+
response.headers['Content-Length'] = file_size
|
| 102 |
+
response.headers['Content-Type'] = mime_type or "video/mp4"
|
| 103 |
+
response.headers['Accept-Ranges'] = 'bytes'
|
| 104 |
+
response.headers['Content-Disposition'] = f'inline; filename="{file_name or "video.mp4"}"'
|
| 105 |
+
return response
|
| 106 |
+
except Exception as e:
|
| 107 |
+
return f"Error: {e}", 500
|
| 108 |
+
|
| 109 |
+
@app.route('/download/<int:message_id>')
|
| 110 |
+
def download_video(message_id):
|
| 111 |
+
"""সরাসরি ওয়ান-ক্লিকে ডাউনলোড করার লিংক"""
|
| 112 |
+
try:
|
| 113 |
+
async def get_media_info():
|
| 114 |
+
msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
|
| 115 |
+
if msg and msg.video:
|
| 116 |
+
return msg.video.file_size, msg.video.file_name, msg.video.mime_type
|
| 117 |
+
return None, None, None
|
| 118 |
+
|
| 119 |
+
file_size, file_name, mime_type = run_async(get_media_info())
|
| 120 |
+
if not file_size:
|
| 121 |
+
return "File not found or invalid message", 404
|
| 122 |
+
|
| 123 |
+
response = make_response(Response(get_file_stream(message_id), mimetype=mime_type or "application/octet-stream"))
|
| 124 |
+
response.headers['Content-Length'] = file_size
|
| 125 |
+
response.headers['Content-Disposition'] = f'attachment; filename="{file_name or "video.mp4"}"'
|
| 126 |
+
return response
|
| 127 |
+
except Exception as e:
|
| 128 |
+
return f"Error: {e}", 500
|
| 129 |
+
# =======================================================================
|
| 130 |
+
|
| 131 |
# ================= FLASK API ROUTES =================
|
| 132 |
@app.route('/')
|
| 133 |
def index():
|
|
|
|
| 393 |
else:
|
| 394 |
await status_msg.edit_text(f"⏳ <b>Cloning started!</b>\nFetching video list from <code>{source_id}</code>...\n<i>This might take a few minutes if the group has many videos.</i>", parse_mode=enums.ParseMode.HTML)
|
| 395 |
|
|
|
|
| 396 |
video_ids = []
|
| 397 |
retries = 5
|
| 398 |
while retries > 0:
|
| 399 |
try:
|
|
|
|
| 400 |
if not client.is_connected:
|
| 401 |
try: await client.connect()
|
| 402 |
except: pass
|
|
|
|
| 407 |
video_ids.append(msg.id)
|
| 408 |
if len(video_ids) % 200 == 0:
|
| 409 |
await asyncio.sleep(0.1)
|
| 410 |
+
break
|
| 411 |
except Exception as e:
|
| 412 |
err_msg = str(e).lower()
|
|
|
|
| 413 |
if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "reset" in err_msg:
|
| 414 |
retries -= 1
|
| 415 |
+
video_ids = []
|
| 416 |
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)
|
| 417 |
await asyncio.sleep(10)
|
| 418 |
else:
|
| 419 |
+
raise e
|
|
|
|
| 420 |
|
| 421 |
if not video_ids:
|
| 422 |
if last_copied_id:
|
|
|
|
| 424 |
else:
|
| 425 |
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)
|
| 426 |
|
| 427 |
+
video_ids.reverse()
|
| 428 |
total = len(video_ids)
|
| 429 |
|
| 430 |
if last_copied_id:
|
|
|
|
| 439 |
copy_success = False
|
| 440 |
copy_retries = 3
|
| 441 |
|
|
|
|
| 442 |
while copy_retries > 0:
|
| 443 |
try:
|
| 444 |
if not client.is_connected:
|
|
|
|
| 449 |
success += 1
|
| 450 |
await save_progress(source_id, dest_id, msg_id)
|
| 451 |
copy_success = True
|
| 452 |
+
break
|
| 453 |
except FloodWait as e:
|
|
|
|
| 454 |
await asyncio.sleep(e.value + 2)
|
| 455 |
except Exception as e:
|
| 456 |
err_msg = str(e).lower()
|
| 457 |
if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "reset" in err_msg:
|
| 458 |
copy_retries -= 1
|
| 459 |
+
await asyncio.sleep(5)
|
| 460 |
else:
|
| 461 |
+
break
|
| 462 |
|
| 463 |
if not copy_success:
|
| 464 |
failed += 1
|
|
|
|
| 465 |
|
| 466 |
if index % 20 == 0 or index == total:
|
| 467 |
try:
|
|
|
|
| 620 |
original_file = await message.download(progress=download_progress)
|
| 621 |
clean_upload_file = original_file
|
| 622 |
|
| 623 |
+
# ১. ওয়াটারমার্ক (Watermark) করা - HD
|
| 624 |
if media_type == "video" and not is_large_video:
|
| 625 |
await status_msg.edit_text("⏳ Watermarking video... (HD + Superfast Processing)")
|
| 626 |
watermarked_file = f"{original_file}_wm.mp4"
|
|
|
|
| 635 |
if process.returncode == 0 and os.path.exists(watermarked_file):
|
| 636 |
clean_upload_file = watermarked_file
|
| 637 |
|
| 638 |
+
# ২. byse.sx এর বদলে আপনার নিজের টেলিগ্রাম স্টোরেজ চ্যানেলে HD ভিডিও আপলোড
|
| 639 |
if media_type == "video":
|
| 640 |
+
await status_msg.edit_text("⏳ Uploading Clean HD video to your storage channel...")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 641 |
|
| 642 |
+
# আপনার নিজের চ্যানেলে ব্যাকআপ হিসেবে HD ওয়াটারমার্কড ভিডিওটি পাঠানো হচ্ছে
|
| 643 |
+
sent_to_channel = await client.send_video(
|
| 644 |
+
chat_id=STORAGE_CHANNEL_ID,
|
| 645 |
+
video=clean_upload_file,
|
| 646 |
+
caption=f"Backup of video uploaded by Admin. File: {os.path.basename(clean_upload_file)}"
|
| 647 |
+
)
|
| 648 |
+
storage_msg_id = sent_to_channel.id
|
|
|
|
|
|
|
| 649 |
|
| 650 |
+
# আপনার নিজস্ব ডোমেইন বা সার্ভারের স্ট্রিমিং ও ডিরেক্ট ডাউনলোড লিংক জেনারেট
|
| 651 |
+
stream_link = f"{BACKEND_URL}/stream/{storage_msg_id}"
|
| 652 |
+
download_link = f"{BACKEND_URL}/download/{storage_msg_id}"
|
| 653 |
+
embed_link = stream_link # এমবেড লিংক হিসেবে এখন নিজস্ব স্ট্রিমিং লিংক ব্যবহার হবে
|
| 654 |
|
| 655 |
if is_large_video:
|
| 656 |
+
# যদি লার্জ ভিডিও হয়, তবে সেটিরও স্ট্রিমিং লিংক জেনারেট হয়ে অ্যাডমিন চ্যাটে শো করবে
|
| 657 |
+
admin_cap = f"✅ <b>Success! (Large Video)</b>\n\n🔗 <b>Embed Link (Clean HD):</b>\n<code>{embed_link or 'N/A'}</code>\n\n📌 <i>Broadcast skipped due to large file size.</i>"
|
| 658 |
await client.send_video(message.chat.id, message.video.file_id, caption=admin_cap, parse_mode=enums.ParseMode.HTML)
|
| 659 |
await status_msg.delete()
|
| 660 |
return
|
| 661 |
|
| 662 |
+
# ৩. টেলিগ্রামের জন্য ভিডিও ব্লার করা
|
| 663 |
telegram_file = clean_upload_file
|
| 664 |
if is_blur and not is_large_video:
|
| 665 |
await status_msg.edit_text(f"⏳ Applying {blur_percent}% blur for Telegram broadcast...")
|
|
|
|
| 694 |
caption_text = f"{clean_caption}\n\n👇 <i>Click the button below to open Bot!</i>" if clean_caption else f"🔥 <b>New Premium Viral Content!</b> 🔞\n\n🎬 <b>Watch HD Video Here:</b>\n👉 <b><a href='{bot_link}'>▶️ Click Here to Watch</a></b>\n\n👇 <i>Click the button below to open Bot!</i>"
|
| 695 |
|
| 696 |
group_markup = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Watch Full Video Here 🔞", url=bot_link)]])
|
| 697 |
+
|
| 698 |
+
# অ্যাডমিন চ্যাটে সাকস���স মেসেজ ও লিংক পাঠানো হচ্ছে
|
| 699 |
+
admin_cap = (
|
| 700 |
+
f"✅ <b>Upload and Processing Complete!</b>\n\n"
|
| 701 |
+
f"🎬 <b>Stream/Watch Online Link:</b>\n<code>{stream_link}</code>\n\n"
|
| 702 |
+
f"📥 <b>Direct Download Link:</b>\n<code>{download_link}</code>"
|
| 703 |
+
)
|
| 704 |
|
| 705 |
thumb_path = None
|
| 706 |
if media_type == "video":
|