Delete bot.py
#5
by Jitendra55566 - opened
bot.py
DELETED
|
@@ -1,985 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import time
|
| 3 |
-
import math
|
| 4 |
-
import json
|
| 5 |
-
import asyncio
|
| 6 |
-
import subprocess
|
| 7 |
-
import yt_dlp
|
| 8 |
-
from datetime import datetime, timedelta
|
| 9 |
-
from threading import Thread
|
| 10 |
-
from flask import Flask
|
| 11 |
-
from pyrogram import Client, filters
|
| 12 |
-
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
|
| 13 |
-
|
| 14 |
-
# --- Hugging Face को जगाए रखने के लिए Flask Server ---
|
| 15 |
-
app = Flask(__name__)
|
| 16 |
-
@app.route('/')
|
| 17 |
-
def health_check():
|
| 18 |
-
return "Bot is running perfectly!"
|
| 19 |
-
|
| 20 |
-
def run_flask():
|
| 21 |
-
app.run(host="0.0.0.0", port=7860)
|
| 22 |
-
|
| 23 |
-
# --- Telegram Bot की डिटेल्स ---
|
| 24 |
-
BOT_TOKEN = "8728797060:AAH3L0eqApxEKuVvYjKT0JGpgQ3BPlrjbgI"
|
| 25 |
-
API_ID = 35985614
|
| 26 |
-
API_HASH = "6a0df17414daf6935f1f0a71b8af1ee9"
|
| 27 |
-
|
| 28 |
-
bot = Client("LiveBot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN)
|
| 29 |
-
|
| 30 |
-
# --- USER SESSION (Private Channel Download के लिए) ---
|
| 31 |
-
USER_SESSION = "BQIlGM4AazlRr5P1dhaKtIXeQTq0O5LnNaFB9uBcdjcjZQ95iBGIm3L_NTaA0ncK91ZKIr_4loGx8gw84Z_RL9gQDSZ80QOIq5RrwtlKvsfF-NrJElCGJ5NAL8GMtDceRIQpQO78DfI4PM75h7zylKi3jnDQik2bAUzJ7ylG1VI_hgfKxYZlkK2FXkGaACNgJSzUzc-4_THAlSDLNhpQ6Dv_WlFKQpGR47KdZTMxR81ExV5WjW4-AG1A0tr_B0PUvRVyZrpMLb5AzPMqFmByxeQS3QicQzXGKYlJj9Pm5uYRCcFS5WCqhC2lwISMqklhjIivc7LHko5EjyWELHhAhz2RfmPDQAAAAAHK_kURAA" # 👉 यहाँ अपना Pyrogram String Session डालें
|
| 32 |
-
user_app = Client("UserAcc", api_id=API_ID, api_hash=API_HASH, session_string=USER_SESSION)
|
| 33 |
-
|
| 34 |
-
# --- ग्लोबल वेरिएबल्स ---
|
| 35 |
-
ADMIN_ID = 7700628753
|
| 36 |
-
GLOBAL_PASSWORD = "jitu@123"
|
| 37 |
-
PASSWORD_LIMIT = 999999 # डिफ़ॉल्ट लिमिट (Unlimted)
|
| 38 |
-
PASSWORD_USED_BY = {} # जिन लोगों ने पासवर्ड इस्तेमाल किया है उनका डेटा
|
| 39 |
-
GLOBAL_LIMIT = 1
|
| 40 |
-
user_data = {}
|
| 41 |
-
admin_state = None
|
| 42 |
-
|
| 43 |
-
# --- मैसेज डिलीट करने के लिए नया डेटाबेस (टेम्परेरी) ---
|
| 44 |
-
sent_message_map = {}
|
| 45 |
-
recent_admin_messages =[]
|
| 46 |
-
|
| 47 |
-
# --- Data Persistence (बॉट रिस्टार्ट होने पर डेटा सेव रखने के लिए) ---
|
| 48 |
-
DATA_FILE = "bot_config.json"
|
| 49 |
-
|
| 50 |
-
def save_data():
|
| 51 |
-
try:
|
| 52 |
-
# सिर्फ ऑथेंटिकेटेड यूज़र्स का डेटा सेव करेंगे ताकि फाइल भारी न हो
|
| 53 |
-
auth_users = {str(uid): info['name'] for uid, info in user_data.items() if info.get('authenticated')}
|
| 54 |
-
with open(DATA_FILE, "w") as f:
|
| 55 |
-
json.dump({
|
| 56 |
-
"GLOBAL_PASSWORD": GLOBAL_PASSWORD,
|
| 57 |
-
"PASSWORD_LIMIT": PASSWORD_LIMIT,
|
| 58 |
-
"GLOBAL_LIMIT": GLOBAL_LIMIT,
|
| 59 |
-
"PASSWORD_USED_BY": {str(k): v for k, v in PASSWORD_USED_BY.items()},
|
| 60 |
-
"authenticated_users": auth_users
|
| 61 |
-
}, f)
|
| 62 |
-
except Exception as e: pass
|
| 63 |
-
|
| 64 |
-
def load_data():
|
| 65 |
-
global GLOBAL_PASSWORD, PASSWORD_LIMIT, GLOBAL_LIMIT, PASSWORD_USED_BY, user_data
|
| 66 |
-
if os.path.exists(DATA_FILE):
|
| 67 |
-
try:
|
| 68 |
-
with open(DATA_FILE, "r") as f:
|
| 69 |
-
data = json.load(f)
|
| 70 |
-
GLOBAL_PASSWORD = data.get("GLOBAL_PASSWORD", "jitu@123")
|
| 71 |
-
PASSWORD_LIMIT = data.get("PASSWORD_LIMIT", 999999)
|
| 72 |
-
GLOBAL_LIMIT = data.get("GLOBAL_LIMIT", 1)
|
| 73 |
-
PASSWORD_USED_BY = {int(k): v for k, v in data.get("PASSWORD_USED_BY", {}).items()}
|
| 74 |
-
|
| 75 |
-
saved_users = data.get("authenticated_users", {})
|
| 76 |
-
for uid, name in saved_users.items():
|
| 77 |
-
uid = int(uid)
|
| 78 |
-
if uid not in user_data:
|
| 79 |
-
user_data[uid] = {
|
| 80 |
-
'name': name,
|
| 81 |
-
'authenticated': True,
|
| 82 |
-
'step': 'PLATFORM',
|
| 83 |
-
'platform': None,
|
| 84 |
-
'rtmp_url': None,
|
| 85 |
-
'loop_hours': 0,
|
| 86 |
-
'orientation': 'landscape',
|
| 87 |
-
'custom_image_path': None,
|
| 88 |
-
'custom_audio_path': None,
|
| 89 |
-
'video_url': None,
|
| 90 |
-
'schedule_time': None,
|
| 91 |
-
'processes':[],
|
| 92 |
-
'stream_limit': GLOBAL_LIMIT,
|
| 93 |
-
'file_path': None,
|
| 94 |
-
'manual_stop': False,
|
| 95 |
-
'join_date': get_ist_time(),
|
| 96 |
-
'last_online': get_ist_time(),
|
| 97 |
-
'total_streams': 0,
|
| 98 |
-
'last_stream': "Never"
|
| 99 |
-
}
|
| 100 |
-
except Exception as e: pass
|
| 101 |
-
|
| 102 |
-
def get_ist_time():
|
| 103 |
-
return datetime.utcnow() + timedelta(hours=5, minutes=30)
|
| 104 |
-
|
| 105 |
-
def format_date(dt):
|
| 106 |
-
if isinstance(dt, str): return dt
|
| 107 |
-
return dt.strftime("%d-%b-%Y %I:%M %p")
|
| 108 |
-
|
| 109 |
-
def get_video_duration(file_path):
|
| 110 |
-
try:
|
| 111 |
-
result = subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", file_path],
|
| 112 |
-
stdout=subprocess.PIPE,
|
| 113 |
-
stderr=subprocess.STDOUT,
|
| 114 |
-
text=True
|
| 115 |
-
)
|
| 116 |
-
return float(result.stdout.strip())
|
| 117 |
-
except:
|
| 118 |
-
return 0
|
| 119 |
-
|
| 120 |
-
async def notify_admin(client, text):
|
| 121 |
-
try:
|
| 122 |
-
await client.send_message(ADMIN_ID, text)
|
| 123 |
-
except:
|
| 124 |
-
pass
|
| 125 |
-
|
| 126 |
-
def init_user(user_id, message=None):
|
| 127 |
-
if user_id not in user_data:
|
| 128 |
-
name = message.from_user.first_name if message else "Unknown"
|
| 129 |
-
user_data[user_id] = {
|
| 130 |
-
'name': name,
|
| 131 |
-
'authenticated': False if user_id != ADMIN_ID else True,
|
| 132 |
-
'step': 'PASSWORD' if user_id != ADMIN_ID else 'PLATFORM',
|
| 133 |
-
'platform': None,
|
| 134 |
-
'rtmp_url': None,
|
| 135 |
-
'loop_hours': 0,
|
| 136 |
-
'orientation': 'landscape',
|
| 137 |
-
'custom_image_path': None, # नया: कस्टम फोटो सेव करने के लिए
|
| 138 |
-
'custom_audio_path': None, # ऑडियो रिप्लेस
|
| 139 |
-
'watermark_text': None, # <--- नया: मूविंग वाटरमार्क के लिए
|
| 140 |
-
'video_url': None, # डायरेक्ट लिंक
|
| 141 |
-
'schedule_time': None, # शेड्यूलिंग
|
| 142 |
-
'processes':[],
|
| 143 |
-
'stream_limit': GLOBAL_LIMIT,
|
| 144 |
-
'file_path': None,
|
| 145 |
-
'manual_stop': False,
|
| 146 |
-
'join_date': get_ist_time(),
|
| 147 |
-
'last_online': get_ist_time(),
|
| 148 |
-
'total_streams': 0,
|
| 149 |
-
'last_stream': "Never"
|
| 150 |
-
}
|
| 151 |
-
if user_id != ADMIN_ID:
|
| 152 |
-
asyncio.create_task(notify_admin(bot, f"🔔 **New User Joined!**\n👤 **Name:** {name}\n🆔 **ID:** `{user_id}`\n📅 **Date:** {format_date(get_ist_time())}"))
|
| 153 |
-
else:
|
| 154 |
-
user_data[user_id]['last_online'] = get_ist_time()
|
| 155 |
-
if message:
|
| 156 |
-
user_data[user_id]['name'] = message.from_user.first_name
|
| 157 |
-
|
| 158 |
-
def platform_keyboard():
|
| 159 |
-
return InlineKeyboardMarkup([[InlineKeyboardButton("🔴 YouTube Live", callback_data="plat_youtube")],[InlineKeyboardButton("🟢 Rumble Live", callback_data="plat_rumble")],[InlineKeyboardButton("🔵 Custom/Other", callback_data="plat_custom")]])
|
| 160 |
-
|
| 161 |
-
def loop_keyboard():
|
| 162 |
-
return InlineKeyboardMarkup([[InlineKeyboardButton("▶️ 1 Time (No Loop)", callback_data="loop_0")],[InlineKeyboardButton("⏳ 30 Mins", callback_data="loop_0.5"), InlineKeyboardButton("⏳ 1 Hour", callback_data="loop_1")],[InlineKeyboardButton("🔁 3 Hours", callback_data="loop_3"), InlineKeyboardButton("🔁 6 Hours", callback_data="loop_6")],[InlineKeyboardButton("🔁 12 Hours", callback_data="loop_12"), InlineKeyboardButton("🔁 24 Hours", callback_data="loop_24")]
|
| 163 |
-
])
|
| 164 |
-
|
| 165 |
-
def orientation_keyboard():
|
| 166 |
-
return InlineKeyboardMarkup([[InlineKeyboardButton("🖥 Landscape (16:9)", callback_data="ori_landscape")],[InlineKeyboardButton("📱 Portrait / Shorts (9:16)", callback_data="ori_portrait")]])
|
| 167 |
-
|
| 168 |
-
def image_choice_keyboard():
|
| 169 |
-
return InlineKeyboardMarkup([[InlineKeyboardButton("✅ Yes (Add Custom Photo)", callback_data="img_yes")],[InlineKeyboardButton("❌ No (Normal Black Bg)", callback_data="img_no")]])
|
| 170 |
-
|
| 171 |
-
def audio_choice_keyboard():
|
| 172 |
-
return InlineKeyboardMarkup([[InlineKeyboardButton("🎵 Replace Audio (Send File)", callback_data="aud_yes")],[InlineKeyboardButton("🔊 Keep Original Audio", callback_data="aud_no")]])
|
| 173 |
-
|
| 174 |
-
def watermark_choice_keyboard():
|
| 175 |
-
return InlineKeyboardMarkup([[InlineKeyboardButton("✅ Yes (Moving Watermark)", callback_data="wm_yes")],[InlineKeyboardButton("❌ No Watermark", callback_data="wm_no")]])
|
| 176 |
-
|
| 177 |
-
def schedule_choice_keyboard():
|
| 178 |
-
return InlineKeyboardMarkup([[InlineKeyboardButton("▶️ Start Now", callback_data="sch_now")],[InlineKeyboardButton("⏳ Schedule Time", callback_data="sch_later")]])
|
| 179 |
-
|
| 180 |
-
def admin_keyboard():
|
| 181 |
-
return InlineKeyboardMarkup([[InlineKeyboardButton("👥 Users List", callback_data="admin_users"), InlineKeyboardButton("🔴 Live Status", callback_data="admin_live")],[InlineKeyboardButton("🔑 Change Password", callback_data="admin_pass"), InlineKeyboardButton("⚙️ Set Limit", callback_data="admin_limit")],[InlineKeyboardButton("📢 Broadcast / Send Msg", callback_data="admin_msg")],[InlineKeyboardButton("🗑 Delete Last 5 Broadcasts", callback_data="admin_del")]])
|
| 182 |
-
|
| 183 |
-
# --- Live Progress Bar ---
|
| 184 |
-
async def progress_bar(current, total, msg, start_time, last_update):
|
| 185 |
-
now = time.time()
|
| 186 |
-
if now - last_update[0] > 3 or current == total:
|
| 187 |
-
last_update[0] = now
|
| 188 |
-
diff = now - start_time
|
| 189 |
-
percentage = current * 100 / total
|
| 190 |
-
speed = current / diff if diff > 0 else 0
|
| 191 |
-
eta = round((total - current) / speed) if speed > 0 else 0
|
| 192 |
-
|
| 193 |
-
current_mb = round(current / (1024 * 1024), 2)
|
| 194 |
-
total_mb = round(total / (1024 * 1024), 2)
|
| 195 |
-
speed_mb = round(speed / (1024 * 1024), 2)
|
| 196 |
-
|
| 197 |
-
progress_str = "[{0}{1}]".format(''.join(["█" for _ in range(math.floor(percentage / 5))]), ''.join(["░" for _ in range(20 - math.floor(percentage / 5))]))
|
| 198 |
-
eta_str = f"{eta} sec" if eta < 60 else f"{eta//60} min {eta%60} sec"
|
| 199 |
-
|
| 200 |
-
text = f"📥 **Downloading Video...**\n\n{progress_str} **{round(percentage, 2)}%**\n📦 **Size:** `{current_mb} MB / {total_mb} MB`\n🚀 **Speed:** `{speed_mb} MB/s`\n⏳ **ETA:** `{eta_str}`"
|
| 201 |
-
try: await msg.edit_text(text)
|
| 202 |
-
except: pass
|
| 203 |
-
|
| 204 |
-
@bot.on_message(filters.command("admin"))
|
| 205 |
-
async def admin_cmd(client, message):
|
| 206 |
-
if message.chat.id == ADMIN_ID:
|
| 207 |
-
global admin_state
|
| 208 |
-
admin_state = None
|
| 209 |
-
await message.reply("👑 **Welcome to Admin Panel**\nChoose an option below:", reply_markup=admin_keyboard())
|
| 210 |
-
|
| 211 |
-
@bot.on_message(filters.command("start"))
|
| 212 |
-
async def start_cmd(client, message):
|
| 213 |
-
user_id = message.chat.id
|
| 214 |
-
init_user(user_id, message)
|
| 215 |
-
if user_data[user_id]['authenticated']:
|
| 216 |
-
user_data[user_id]['step'] = 'PLATFORM'
|
| 217 |
-
await message.reply("**✅ Logged in!**\n👇 **Select platform:**", reply_markup=platform_keyboard())
|
| 218 |
-
else:
|
| 219 |
-
user_data[user_id]['step'] = 'PASSWORD'
|
| 220 |
-
await message.reply("👋 **Welcome!**\n🔐 **Enter Password to continue:**")
|
| 221 |
-
|
| 222 |
-
@bot.on_message(filters.command("new"))
|
| 223 |
-
async def new_cmd(client, message):
|
| 224 |
-
user_id = message.chat.id
|
| 225 |
-
init_user(user_id, message)
|
| 226 |
-
|
| 227 |
-
if not user_data[user_id]['authenticated']:
|
| 228 |
-
user_data[user_id]['step'] = 'PASSWORD'
|
| 229 |
-
return await message.reply("🔐 **Session Expired! Please enter the new password:**")
|
| 230 |
-
|
| 231 |
-
user_data[user_id]['step'] = 'PLATFORM'
|
| 232 |
-
# नई स्ट्रीम शुरू करते समय पुराना डेटा मिटा दें
|
| 233 |
-
user_data[user_id]['custom_image_path'] = None
|
| 234 |
-
user_data[user_id]['custom_audio_path'] = None
|
| 235 |
-
user_data[user_id]['watermark_text'] = None
|
| 236 |
-
user_data[user_id]['video_url'] = None
|
| 237 |
-
user_data[user_id]['file_path'] = None
|
| 238 |
-
await message.reply("🔄 **New Stream Setup!**\n👇 **Select platform:**", reply_markup=platform_keyboard())
|
| 239 |
-
|
| 240 |
-
@bot.on_message(filters.command("stop"))
|
| 241 |
-
async def stop_cmd(client, message):
|
| 242 |
-
user_id = message.chat.id
|
| 243 |
-
if user_id not in user_data: return
|
| 244 |
-
|
| 245 |
-
active_procs = []
|
| 246 |
-
for p in user_data[user_id].get('processes',[]):
|
| 247 |
-
proc_obj = p['process'] if isinstance(p, dict) else p
|
| 248 |
-
if proc_obj.poll() is None:
|
| 249 |
-
active_procs.append(proc_obj)
|
| 250 |
-
|
| 251 |
-
if not active_procs:
|
| 252 |
-
return await message.reply("⚠️ **No active stream found.**")
|
| 253 |
-
|
| 254 |
-
user_data[user_id]['manual_stop'] = True
|
| 255 |
-
for p in active_procs:
|
| 256 |
-
p.terminate()
|
| 257 |
-
|
| 258 |
-
user_data[user_id]['processes'] =[]
|
| 259 |
-
await message.reply(f"🛑 **Stopping {len(active_procs)} Active Stream(s)... Generating Report!**")
|
| 260 |
-
|
| 261 |
-
@bot.on_callback_query(filters.regex("^admin_"))
|
| 262 |
-
async def admin_callbacks(client, callback_query: CallbackQuery):
|
| 263 |
-
if callback_query.message.chat.id != ADMIN_ID:
|
| 264 |
-
return
|
| 265 |
-
data = callback_query.data
|
| 266 |
-
global admin_state
|
| 267 |
-
global recent_admin_messages
|
| 268 |
-
|
| 269 |
-
if data == "admin_users":
|
| 270 |
-
total_users = len(user_data)
|
| 271 |
-
text = f"👥 **Total Users:** `{total_users}`\n\n"
|
| 272 |
-
for uid, info in user_data.items():
|
| 273 |
-
if uid == ADMIN_ID: continue
|
| 274 |
-
text += f"👤 **Name:** {info['name']}\n🆔 **ID:** `{uid}`\n📅 **Joined:** {format_date(info['join_date'])}\n🕒 **Last Online:** {format_date(info['last_online'])}\n🎬 **Streams Done:** {info['total_streams']}\n⏳ **Last Stream:** {format_date(info['last_stream'])}\n"
|
| 275 |
-
text += "➖➖➖➖➖➖➖➖\n"
|
| 276 |
-
if len(text) > 4000:
|
| 277 |
-
for x in range(0, len(text), 4000):
|
| 278 |
-
await client.send_message(ADMIN_ID, text[x:x+4000])
|
| 279 |
-
else:
|
| 280 |
-
await callback_query.message.reply(text)
|
| 281 |
-
|
| 282 |
-
elif data == "admin_live":
|
| 283 |
-
live_text = ""
|
| 284 |
-
total_live = 0
|
| 285 |
-
for uid, info in user_data.items():
|
| 286 |
-
active_streams =[]
|
| 287 |
-
for p in info.get('processes', []):
|
| 288 |
-
if isinstance(p, dict) and p['process'].poll() is None:
|
| 289 |
-
active_streams.append(p)
|
| 290 |
-
elif not isinstance(p, dict) and p.poll() is None: # Old format safety
|
| 291 |
-
active_streams.append({'process': p, 'start_time': get_ist_time(), 'end_time': "Unknown", 'loop_hours': 0, 'is_image': False})
|
| 292 |
-
|
| 293 |
-
if active_streams:
|
| 294 |
-
total_live += len(active_streams)
|
| 295 |
-
live_text += f"👤 **Name:** {info['name']} (ID: `{uid}`)\n🌐 **Platform:** `{info.get('platform', 'Unknown')}`\n"
|
| 296 |
-
|
| 297 |
-
for i, strm in enumerate(active_streams, 1):
|
| 298 |
-
mode_str = f"{strm['loop_hours']}h Loop" if strm['loop_hours'] > 0 else "1 Time"
|
| 299 |
-
if strm.get('is_image'): mode_str += " (🖼 Image)"
|
| 300 |
-
else: mode_str += " (🎥 Video)"
|
| 301 |
-
|
| 302 |
-
st_str = strm['start_time'].strftime("%I:%M %p")
|
| 303 |
-
et_str = strm['end_time'].strftime("%I:%M %p") if isinstance(strm['end_time'], datetime) else str(strm['end_time'])
|
| 304 |
-
|
| 305 |
-
live_text += f" ➤ **Stream {i}:** Mode: `{mode_str}`\n"
|
| 306 |
-
live_text += f" 🟢 Start: `{st_str}` | 🔴 End: `{et_str}`\n"
|
| 307 |
-
live_text += "➖➖➖➖➖➖\n"
|
| 308 |
-
|
| 309 |
-
if not live_text:
|
| 310 |
-
await callback_query.message.reply("❌ **No one is streaming right now.**")
|
| 311 |
-
else:
|
| 312 |
-
await callback_query.message.reply(f"🔴 **Live Streams ({total_live} total):**\n\n" + live_text)
|
| 313 |
-
|
| 314 |
-
elif data == "admin_pass":
|
| 315 |
-
markup = InlineKeyboardMarkup([
|
| 316 |
-
[InlineKeyboardButton("🔑 Change Password", callback_data="admin_pass_change")],[InlineKeyboardButton("📊 Pass Analysis", callback_data="admin_pass_analysis")]
|
| 317 |
-
])
|
| 318 |
-
await callback_query.message.edit_text("🔐 **Password Management**\nChoose an option below:", reply_markup=markup)
|
| 319 |
-
|
| 320 |
-
elif data == "admin_pass_change":
|
| 321 |
-
admin_state = "AWAIT_NEW_PASSWORD"
|
| 322 |
-
await callback_query.message.reply("🔑 **Please send the NEW PASSWORD and LIMIT.**\n`उदाहरण: 5550 5` (इसका मतलब सिर्फ 5 लोग पासवर्ड यूज़ कर पाएंगे)")
|
| 323 |
-
|
| 324 |
-
elif data == "admin_pass_analysis":
|
| 325 |
-
limit_text = str(PASSWORD_LIMIT) if PASSWORD_LIMIT != 999999 else "Unlimited"
|
| 326 |
-
if not PASSWORD_USED_BY:
|
| 327 |
-
await callback_query.message.reply(f"📊 **Pass Analysis:**\n🔑 **Current Password:** `{GLOBAL_PASSWORD}`\n👥 **Limit:** `0 / {limit_text}`\n\nअभी तक किसी ने भी मौजूदा पासवर्ड का इस्तेमाल नहीं किया है।")
|
| 328 |
-
else:
|
| 329 |
-
text = f"📊 **Pass Analysis:**\n🔑 **Current Password:** `{GLOBAL_PASSWORD}`\n👥 **Limit:** `{len(PASSWORD_USED_BY)} / {limit_text}`\n\n"
|
| 330 |
-
for uid, uname in PASSWORD_USED_BY.items():
|
| 331 |
-
text += f"👤 **Name:** {uname}\n🆔 **ID:** `{uid}`\n➖➖➖➖➖➖\n"
|
| 332 |
-
await callback_query.message.reply(text)
|
| 333 |
-
|
| 334 |
-
elif data == "admin_limit":
|
| 335 |
-
admin_state = "AWAIT_LIMIT"
|
| 336 |
-
await callback_query.message.reply("⚙️ **Set Stream Limit Mode:**\n\n👉 **सबका लिमिट बदलने के लिए:** सिर्फ नंबर भेजें (e.g. `2`)\n👉 **किसी एक यूज़र का लिमिट बदलने के लिए:** ID और नंबर भेजें\n`उदाहरण: 384884848 2`")
|
| 337 |
-
|
| 338 |
-
elif data == "admin_msg":
|
| 339 |
-
admin_state = "AWAIT_BROADCAST"
|
| 340 |
-
await callback_query.message.reply("📢 **Broadcast / Personal Message Mode!**\n\n"
|
| 341 |
-
"👉 **सबको भेजने के लिए:** कोई भी Text, Photo या Video सीधा भेजें।\n"
|
| 342 |
-
"👉 **किसी एक को भेजने के लिए:** ID स्पेस मेसेज लिखें।\n"
|
| 343 |
-
"`उदाहरण: 8383838487 Hello kese ho`")
|
| 344 |
-
|
| 345 |
-
elif data == "admin_del":
|
| 346 |
-
if not recent_admin_messages:
|
| 347 |
-
return await callback_query.message.reply("❌ **No recent messages/broadcasts found to delete.**")
|
| 348 |
-
|
| 349 |
-
deleted_ops = 0
|
| 350 |
-
total_msgs = 0
|
| 351 |
-
|
| 352 |
-
for _ in range(5):
|
| 353 |
-
if not recent_admin_messages:
|
| 354 |
-
break
|
| 355 |
-
targets = recent_admin_messages.pop()
|
| 356 |
-
deleted_ops += 1
|
| 357 |
-
for uid, mid in targets:
|
| 358 |
-
try:
|
| 359 |
-
await client.delete_messages(uid, mid)
|
| 360 |
-
total_msgs += 1
|
| 361 |
-
except: pass
|
| 362 |
-
|
| 363 |
-
await callback_query.message.reply(f"✅ **Deleted last {deleted_ops} broadcast(s)/message(s) from {total_msgs} users' chats!**")
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
@bot.on_callback_query(filters.regex("^plat_"))
|
| 367 |
-
async def select_platform(client, callback_query: CallbackQuery):
|
| 368 |
-
user_id = callback_query.message.chat.id
|
| 369 |
-
init_user(user_id)
|
| 370 |
-
|
| 371 |
-
if not user_data[user_id].get('authenticated'):
|
| 372 |
-
return await callback_query.message.edit_text("🔐 **Session Expired! Please send /start and enter password.**")
|
| 373 |
-
|
| 374 |
-
active_procs = [p for p in user_data[user_id].get('processes', []) if (p['process'].poll() if isinstance(p, dict) else p.poll()) is None]
|
| 375 |
-
user_data[user_id]['processes'] = active_procs
|
| 376 |
-
limit = user_data[user_id].get('stream_limit', GLOBAL_LIMIT)
|
| 377 |
-
|
| 378 |
-
if len(active_procs) >= limit:
|
| 379 |
-
return await callback_query.message.edit_text(f"⚠️ **Limit Reached!**\nआप एक साथ सिर्फ {limit} स्ट्रीम चला सकते हैं। कृपया पहली स्ट्रीम खत्म होने का इंतज़ार करें।")
|
| 380 |
-
|
| 381 |
-
data = callback_query.data
|
| 382 |
-
if data == "plat_youtube":
|
| 383 |
-
user_data[user_id]['platform'] = 'YouTube'
|
| 384 |
-
text = "🔴 **YouTube Selected!**\n🔑 **Send YouTube Stream Key:**"
|
| 385 |
-
elif data == "plat_rumble":
|
| 386 |
-
user_data[user_id]['platform'] = 'Rumble'
|
| 387 |
-
text = "🟢 **Rumble Selected!**\n🔑 **Send Rumble Stream Key (STATIC KEY):**"
|
| 388 |
-
elif data == "plat_custom":
|
| 389 |
-
user_data[user_id]['platform'] = 'Custom'
|
| 390 |
-
text = "🔵 **Custom RTMP Selected!**\n🔗 **Send FULL RTMP URL + Key:**"
|
| 391 |
-
|
| 392 |
-
user_data[user_id]['step'] = 'STREAM_KEY' if data != "plat_custom" else 'CUSTOM_URL'
|
| 393 |
-
await callback_query.message.edit_text(text)
|
| 394 |
-
|
| 395 |
-
@bot.on_callback_query(filters.regex("^loop_"))
|
| 396 |
-
async def select_loop(client, callback_query: CallbackQuery):
|
| 397 |
-
user_id = callback_query.message.chat.id
|
| 398 |
-
init_user(user_id)
|
| 399 |
-
|
| 400 |
-
if not user_data[user_id].get('authenticated'):
|
| 401 |
-
return await callback_query.message.edit_text("🔐 **Session Expired! Please send /start and enter password.**")
|
| 402 |
-
|
| 403 |
-
# Changed int() to float() to support 0.5 hours (30 Mins)
|
| 404 |
-
hours = float(callback_query.data.split("_")[1])
|
| 405 |
-
user_data[user_id]['loop_hours'] = hours
|
| 406 |
-
user_data[user_id]['step'] = 'ORIENTATION'
|
| 407 |
-
|
| 408 |
-
await callback_query.message.edit_text("📐 **Select Stream Orientation:**\nवीडियो कैसे चलाना है?", reply_markup=orientation_keyboard())
|
| 409 |
-
|
| 410 |
-
@bot.on_callback_query(filters.regex("^ori_"))
|
| 411 |
-
async def select_orientation(client, callback_query: CallbackQuery):
|
| 412 |
-
user_id = callback_query.message.chat.id
|
| 413 |
-
init_user(user_id)
|
| 414 |
-
|
| 415 |
-
if not user_data[user_id].get('authenticated'):
|
| 416 |
-
return await callback_query.message.edit_text("🔐 **Session Expired! Please send /start and enter password.**")
|
| 417 |
-
|
| 418 |
-
data = callback_query.data
|
| 419 |
-
if data == "ori_landscape":
|
| 420 |
-
user_data[user_id]['orientation'] = 'landscape'
|
| 421 |
-
user_data[user_id]['step'] = 'VIDEO_OR_URL'
|
| 422 |
-
text = "🖥 **Landscape (16:9) Selected!**\n\n📥 **अब कोई Video File भेजें या डायरेक्ट Link (YouTube/Insta/FB) भेजें:**"
|
| 423 |
-
await callback_query.message.edit_text(text)
|
| 424 |
-
else:
|
| 425 |
-
user_data[user_id]['orientation'] = 'portrait'
|
| 426 |
-
user_data[user_id]['step'] = 'WAIT_IMAGE_CHOICE'
|
| 427 |
-
text = "📱 **Portrait/Shorts (9:16) Selected!**\n\n🖼 **क्या आप वीडियो के ऊपर/नीचे खाली जगह पर कोई Custom Photo (Background) लगाना चाहते हैं?**"
|
| 428 |
-
await callback_query.message.edit_text(text, reply_markup=image_choice_keyboard())
|
| 429 |
-
|
| 430 |
-
@bot.on_callback_query(filters.regex("^img_"))
|
| 431 |
-
async def select_image_choice(client, callback_query: CallbackQuery):
|
| 432 |
-
user_id = callback_query.message.chat.id
|
| 433 |
-
init_user(user_id)
|
| 434 |
-
|
| 435 |
-
if not user_data[user_id].get('authenticated'):
|
| 436 |
-
return await callback_query.message.edit_text("🔐 **Session Expired! Please send /start and enter password.**")
|
| 437 |
-
|
| 438 |
-
data = callback_query.data
|
| 439 |
-
if data == "img_yes":
|
| 440 |
-
user_data[user_id]['step'] = 'AWAIT_IMAGE'
|
| 441 |
-
text = "🖼 **Send your Custom Background Image (Photo)!**\n(यह फोटो आपके वीडियो के पीछे (Background) में दिखेगी)"
|
| 442 |
-
else:
|
| 443 |
-
user_data[user_id]['step'] = 'VIDEO_OR_URL'
|
| 444 |
-
text = "⬛️ **No Image Selected (Black Background).**\n\n📥 **अब कोई Video File भेजें या डायरेक्ट Link (YouTube/Insta/FB) भेजें:**"
|
| 445 |
-
|
| 446 |
-
await callback_query.message.edit_text(text)
|
| 447 |
-
|
| 448 |
-
@bot.on_callback_query(filters.regex("^aud_"))
|
| 449 |
-
async def select_audio_choice(client, callback_query: CallbackQuery):
|
| 450 |
-
user_id = callback_query.message.chat.id
|
| 451 |
-
data = callback_query.data
|
| 452 |
-
if data == "aud_yes":
|
| 453 |
-
user_data[user_id]['step'] = 'AWAIT_AUDIO'
|
| 454 |
-
await callback_query.message.edit_text("🎵 **अपना Audio (MP3) या Video File भेजें जिससे आवाज़ निकालनी है!**")
|
| 455 |
-
else:
|
| 456 |
-
user_data[user_id]['step'] = 'SCHEDULE'
|
| 457 |
-
await callback_query.message.edit_text("⏳ **स्ट्रीम अभी चालू करनी है या Schedule (शेड्यूल) करनी है?**", reply_markup=schedule_choice_keyboard())
|
| 458 |
-
|
| 459 |
-
@bot.on_callback_query(filters.regex("^wm_"))
|
| 460 |
-
async def select_watermark_choice(client, callback_query: CallbackQuery):
|
| 461 |
-
user_id = callback_query.message.chat.id
|
| 462 |
-
if callback_query.data == "wm_yes":
|
| 463 |
-
user_data[user_id]['step'] = 'AWAIT_WATERMARK_TEXT'
|
| 464 |
-
await callback_query.message.edit_text("✍️ **कृपया Watermark का Text भेजें:**\n(जैसे: Class PDF Telegram Pr Milengi)")
|
| 465 |
-
else:
|
| 466 |
-
user_data[user_id]['step'] = 'AUDIO_CHOICE'
|
| 467 |
-
await callback_query.message.edit_text("🎵 **YouTube के लिए ऑडियो जरूरी है।**\nक्या आप अपना खुद का Audio (MP3) लगाना चाहते हैं?", reply_markup=audio_choice_keyboard())
|
| 468 |
-
|
| 469 |
-
@bot.on_callback_query(filters.regex("^sch_"))
|
| 470 |
-
async def select_schedule_choice(client, callback_query: CallbackQuery):
|
| 471 |
-
user_id = callback_query.message.chat.id
|
| 472 |
-
if callback_query.data == "sch_now":
|
| 473 |
-
user_data[user_id]['step'] = 'READY'
|
| 474 |
-
# 🚀 यह मैसेज डिलीट हो जाएगा ताकि स्ट्रीम खत्म होने के बाद स्क्रीनशॉट की तरह फालतू न दिखे
|
| 475 |
-
await callback_query.message.delete()
|
| 476 |
-
await process_stream_start(client, callback_query.message)
|
| 477 |
-
else:
|
| 478 |
-
user_data[user_id]['step'] = 'AWAIT_DATETIME'
|
| 479 |
-
await callback_query.message.edit_text("📅 **Schedule Time भेजो!**\n\n**Format:** `DD/MM/YYYY hh:mm AM/PM`\n**Example:** `26/05/2026 12:40 PM`")
|
| 480 |
-
|
| 481 |
-
@bot.on_message(~filters.command(["start", "stop", "new", "admin"]))
|
| 482 |
-
async def handle_all_messages(client, message):
|
| 483 |
-
user_id = message.chat.id
|
| 484 |
-
init_user(user_id, message)
|
| 485 |
-
step = user_data[user_id].get('step')
|
| 486 |
-
|
| 487 |
-
# === Admin Reply to Delete ===
|
| 488 |
-
if message.text == "/delete" and message.reply_to_message and user_id == ADMIN_ID:
|
| 489 |
-
summary_id = message.reply_to_message.id
|
| 490 |
-
if summary_id in sent_message_map:
|
| 491 |
-
targets = sent_message_map[summary_id]
|
| 492 |
-
deleted_count = 0
|
| 493 |
-
for uid, mid in targets:
|
| 494 |
-
try:
|
| 495 |
-
await client.delete_messages(uid, mid)
|
| 496 |
-
deleted_count += 1
|
| 497 |
-
except: pass
|
| 498 |
-
await message.reply(f"✅ **Message successfully deleted from {deleted_count} user(s) chat!**")
|
| 499 |
-
del sent_message_map[summary_id]
|
| 500 |
-
else:
|
| 501 |
-
await message.reply("❌ **Could not find message data. Already deleted or too old.**")
|
| 502 |
-
return
|
| 503 |
-
|
| 504 |
-
# === Admin State Handling ===
|
| 505 |
-
if user_id == ADMIN_ID:
|
| 506 |
-
global admin_state, GLOBAL_PASSWORD, GLOBAL_LIMIT, recent_admin_messages, PASSWORD_LIMIT, PASSWORD_USED_BY
|
| 507 |
-
|
| 508 |
-
if admin_state == "AWAIT_NEW_PASSWORD" and message.text:
|
| 509 |
-
parts = message.text.strip().split()
|
| 510 |
-
GLOBAL_PASSWORD = parts[0]
|
| 511 |
-
# अगर एडमिन ने स्पेस देकर नंबर लिखा है, तो उसे लिमिट सेट करें, नहीं तो अनलिमिटेड
|
| 512 |
-
PASSWORD_LIMIT = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 999999
|
| 513 |
-
PASSWORD_USED_BY = {} # नया पासवर्ड आते ही पुरानी लिस्ट खाली कर दें
|
| 514 |
-
admin_state = None
|
| 515 |
-
|
| 516 |
-
limit_text = str(PASSWORD_LIMIT) if PASSWORD_LIMIT != 999999 else "Unlimited"
|
| 517 |
-
await message.reply(f"✅ **Password Changed to:** `{GLOBAL_PASSWORD}`\n👥 **Max Users Allowed:** `{limit_text}`\n\nLogging everyone out...")
|
| 518 |
-
for uid in user_data:
|
| 519 |
-
if uid != ADMIN_ID:
|
| 520 |
-
user_data[uid]['authenticated'] = False
|
| 521 |
-
user_data[uid]['step'] = 'PASSWORD'
|
| 522 |
-
try:
|
| 523 |
-
await client.send_message(uid, "⚠️ **लॉगआउट अलर्ट! (Logout Alert)**\n\nएडमिन ने पासवर्ड बदल दिया है। कृपया `/start` दबाकर नया पासवर्ड डालें।")
|
| 524 |
-
except: pass
|
| 525 |
-
save_data() # डेटाबेस अपडेट करें
|
| 526 |
-
return
|
| 527 |
-
|
| 528 |
-
elif admin_state == "AWAIT_LIMIT" and message.text:
|
| 529 |
-
text = message.text.strip()
|
| 530 |
-
parts = text.split()
|
| 531 |
-
if len(parts) == 1 and parts[0].isdigit():
|
| 532 |
-
GLOBAL_LIMIT = int(parts[0])
|
| 533 |
-
for uid in user_data:
|
| 534 |
-
user_data[uid]['stream_limit'] = GLOBAL_LIMIT
|
| 535 |
-
await message.reply(f"✅ **Global Stream Limit set to {GLOBAL_LIMIT} for everyone!**")
|
| 536 |
-
elif len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
|
| 537 |
-
target_id = int(parts[0])
|
| 538 |
-
limit = int(parts[1])
|
| 539 |
-
if target_id in user_data:
|
| 540 |
-
user_data[target_id]['stream_limit'] = limit
|
| 541 |
-
await message.reply(f"✅ **User `{target_id}` can now run {limit} streams at once!**")
|
| 542 |
-
try: await client.send_message(target_id, f"🎉 **Admin Alert:** Your concurrent stream limit has been increased to **{limit}**!")
|
| 543 |
-
except: pass
|
| 544 |
-
else:
|
| 545 |
-
await message.reply("❌ **User not found in bot database!**")
|
| 546 |
-
else:
|
| 547 |
-
await message.reply("❌ **Invalid format!** Send something like `2` or `123456789 2`")
|
| 548 |
-
admin_state = None
|
| 549 |
-
save_data() # डेटाबेस अपडेट करें
|
| 550 |
-
return
|
| 551 |
-
|
| 552 |
-
elif admin_state == "AWAIT_BROADCAST":
|
| 553 |
-
text_to_check = message.text or message.caption
|
| 554 |
-
is_personal = False
|
| 555 |
-
target_id = None
|
| 556 |
-
content = None
|
| 557 |
-
|
| 558 |
-
if text_to_check:
|
| 559 |
-
parts = text_to_check.split(" ", 1)
|
| 560 |
-
if len(parts) > 1 and parts[0].isdigit() and len(parts[0]) > 5:
|
| 561 |
-
is_personal = True
|
| 562 |
-
target_id = int(parts[0])
|
| 563 |
-
content = parts[1]
|
| 564 |
-
|
| 565 |
-
targets =[]
|
| 566 |
-
|
| 567 |
-
if is_personal:
|
| 568 |
-
try:
|
| 569 |
-
if message.text:
|
| 570 |
-
msg = await client.send_message(target_id, content)
|
| 571 |
-
else:
|
| 572 |
-
msg = await message.copy(target_id, caption=content)
|
| 573 |
-
targets.append((target_id, msg.id))
|
| 574 |
-
summary = await message.reply(f"✅ **Message sent privately to `{target_id}`!**\n_Reply to this message with `/delete` to remove it from their chat._")
|
| 575 |
-
except Exception as e:
|
| 576 |
-
await message.reply(f"❌ **Failed to send.** Target might have blocked the bot.")
|
| 577 |
-
admin_state = None
|
| 578 |
-
return
|
| 579 |
-
else:
|
| 580 |
-
success = 0
|
| 581 |
-
for uid in user_data:
|
| 582 |
-
if uid != ADMIN_ID:
|
| 583 |
-
try:
|
| 584 |
-
msg = await message.copy(uid)
|
| 585 |
-
targets.append((uid, msg.id))
|
| 586 |
-
success += 1
|
| 587 |
-
except: pass
|
| 588 |
-
summary = await message.reply(f"✅ **Broadcast Sent successfully to {success} users!**\n_Reply to this message with `/delete` to remove it from all users._")
|
| 589 |
-
|
| 590 |
-
if targets:
|
| 591 |
-
sent_message_map[summary.id] = targets
|
| 592 |
-
recent_admin_messages.append(targets)
|
| 593 |
-
if len(recent_admin_messages) > 50:
|
| 594 |
-
recent_admin_messages.pop(0)
|
| 595 |
-
admin_state = None
|
| 596 |
-
return
|
| 597 |
-
|
| 598 |
-
# === Normal User Handling ===
|
| 599 |
-
if not user_data[user_id]['authenticated'] and step != 'PASSWORD':
|
| 600 |
-
user_data[user_id]['step'] = 'PASSWORD'
|
| 601 |
-
return await message.reply("🔐 **Session Expired! Please enter password:**")
|
| 602 |
-
|
| 603 |
-
# यहाँ हमने नए स्टेप्स (VIDEO_OR_URL और AWAIT_AUDIO) को Add कर दिया है
|
| 604 |
-
if not message.text and step not in['VIDEO_OR_URL', 'AWAIT_IMAGE', 'AWAIT_AUDIO']:
|
| 605 |
-
return
|
| 606 |
-
|
| 607 |
-
if step == 'PASSWORD':
|
| 608 |
-
if message.text == GLOBAL_PASSWORD:
|
| 609 |
-
# चेक करें कि यूज़र पहले से लिस्ट में है या नहीं
|
| 610 |
-
if user_id not in PASSWORD_USED_BY:
|
| 611 |
-
if len(PASSWORD_USED_BY) >= PASSWORD_LIMIT:
|
| 612 |
-
return await message.reply("🚫 **Password Limit Reached!**\nयह पासवर्ड अपनी लिमिट पूरी कर चुका है। कृपया एडमिन से नया पासवर्ड लें।")
|
| 613 |
-
# अगर लिमिट बची है, तो यूज़र को एनालिसिस लिस्ट में जोड़ दें
|
| 614 |
-
PASSWORD_USED_BY[user_id] = user_data[user_id]['name']
|
| 615 |
-
|
| 616 |
-
user_data[user_id]['authenticated'] = True
|
| 617 |
-
user_data[user_id]['step'] = 'PLATFORM'
|
| 618 |
-
save_data() # यूज़र लॉगिन सेव करें ताकि रीस्टार्ट होने पर लॉगिन न हटे
|
| 619 |
-
await message.reply("**✅ Password Correct!**\n👇 **Select platform:**", reply_markup=platform_keyboard())
|
| 620 |
-
else:
|
| 621 |
-
await message.reply("❌ **Wrong Password! Try again.**")
|
| 622 |
-
|
| 623 |
-
elif step == 'STREAM_KEY':
|
| 624 |
-
key = message.text
|
| 625 |
-
platform = user_data[user_id]['platform']
|
| 626 |
-
if platform == 'YouTube':
|
| 627 |
-
user_data[user_id]['rtmp_url'] = f"rtmp://a.rtmp.youtube.com/live2/{key}"
|
| 628 |
-
elif platform == 'Rumble':
|
| 629 |
-
user_data[user_id]['rtmp_url'] = f"rtmp://rtmp.rumble.com/live/{key}"
|
| 630 |
-
user_data[user_id]['step'] = 'LOOP_CHOICE'
|
| 631 |
-
await message.reply(f"✅ **Key Saved!**\n\n⏱ **How long do you want to stream?**", reply_markup=loop_keyboard())
|
| 632 |
-
|
| 633 |
-
elif step == 'CUSTOM_URL':
|
| 634 |
-
user_data[user_id]['rtmp_url'] = message.text
|
| 635 |
-
user_data[user_id]['step'] = 'LOOP_CHOICE'
|
| 636 |
-
await message.reply("✅ **Custom URL Saved!**\n\n⏱ **How long do you want to stream?**", reply_markup=loop_keyboard())
|
| 637 |
-
|
| 638 |
-
elif step == 'AWAIT_IMAGE':
|
| 639 |
-
if message.photo or message.document:
|
| 640 |
-
msg = await message.reply("⏳ **Saving Image...**")
|
| 641 |
-
image_path = await message.download()
|
| 642 |
-
user_data[user_id]['custom_image_path'] = image_path
|
| 643 |
-
user_data[user_id]['step'] = 'VIDEO_OR_URL'
|
| 644 |
-
await msg.edit_text("✅ **Background Image Saved!**\n\n📥 **अब कोई Video File भेजें या डायरेक्ट Link (YouTube/Insta/FB) भेजें:**")
|
| 645 |
-
else:
|
| 646 |
-
await message.reply("⚠️ **Please send a valid Photo!**")
|
| 647 |
-
|
| 648 |
-
elif step == 'VIDEO_OR_URL':
|
| 649 |
-
user_data[user_id]['is_image_only'] = False
|
| 650 |
-
if message.video or message.document:
|
| 651 |
-
msg = await message.reply("⏳ **Downloading Video...**")
|
| 652 |
-
start_time = time.time()
|
| 653 |
-
last_update = [start_time]
|
| 654 |
-
file_path = await message.download(progress=progress_bar, progress_args=(msg, start_time, last_update))
|
| 655 |
-
user_data[user_id]['file_path'] = file_path
|
| 656 |
-
await msg.delete()
|
| 657 |
-
elif message.photo:
|
| 658 |
-
msg = await message.reply("⏳ **Downloading Image...**")
|
| 659 |
-
file_path = await message.download()
|
| 660 |
-
user_data[user_id]['file_path'] = file_path
|
| 661 |
-
user_data[user_id]['is_image_only'] = True
|
| 662 |
-
await msg.delete()
|
| 663 |
-
elif message.text and "t.me/c/" in message.text:
|
| 664 |
-
# Telegram Private Channel Link Logic
|
| 665 |
-
if not USER_SESSION:
|
| 666 |
-
return await message.reply("❌ **String Session सेट नहीं है! बॉट के कोड में USER_SESSION डालें।**")
|
| 667 |
-
|
| 668 |
-
msg = await message.reply("🔍 **प्राइवेट चैनल में वीडियो ढूँढा जा रहा है... (आस-पास के मैसेज चेक कर रहा हूँ)**")
|
| 669 |
-
try:
|
| 670 |
-
link_parts = message.text.strip().split('/')
|
| 671 |
-
chat_id = int("-100" + link_parts[4])
|
| 672 |
-
msg_id = int(link_parts[5])
|
| 673 |
-
|
| 674 |
-
if not user_app.is_connected:
|
| 675 |
-
await user_app.start()
|
| 676 |
-
|
| 677 |
-
target_msg = None
|
| 678 |
-
# आगे-पीछे के 20 मैसेज चेक करेगा कि Video किसमें है
|
| 679 |
-
messages = await user_app.get_messages(chat_id, range(max(1, msg_id - 10), msg_id + 11))
|
| 680 |
-
for m in messages:
|
| 681 |
-
if m and not m.empty and (m.video or m.document):
|
| 682 |
-
target_msg = m
|
| 683 |
-
break
|
| 684 |
-
|
| 685 |
-
if not target_msg:
|
| 686 |
-
return await msg.edit_text("❌ **इस लिंक के आस-पास कोई Video नहीं मिला!**")
|
| 687 |
-
|
| 688 |
-
await msg.edit_text("⏳ **Private Video Downloading...**")
|
| 689 |
-
start_time = time.time()
|
| 690 |
-
last_update = [start_time]
|
| 691 |
-
file_path = await user_app.download_media(target_msg, progress=progress_bar, progress_args=(msg, start_time, last_update))
|
| 692 |
-
|
| 693 |
-
user_data[user_id]['file_path'] = file_path
|
| 694 |
-
await msg.delete()
|
| 695 |
-
except Exception as e:
|
| 696 |
-
return await msg.edit_text(f"❌ **Private Video Error:** {str(e)}")
|
| 697 |
-
|
| 698 |
-
elif message.text and ("http://" in message.text or "https://" in message.text):
|
| 699 |
-
user_data[user_id]['video_url'] = message.text.strip()
|
| 700 |
-
else:
|
| 701 |
-
return await message.reply("⚠️ **कृपया सही Video, Photo या Link भेजें!**")
|
| 702 |
-
|
| 703 |
-
user_data[user_id]['step'] = 'WATERMARK_CHOICE'
|
| 704 |
-
if user_data[user_id]['is_image_only']:
|
| 705 |
-
await message.reply("✨ **क्या आप फोटो पर चलता हुआ (Moving) नाम/वाटरमार्क लगाना चाहते हैं?**\n(यह YouTube को स्पैम से बचाएगा)", reply_markup=watermark_choice_keyboard())
|
| 706 |
-
else:
|
| 707 |
-
await message.reply("✨ **क्या आप वीडियो पर Watermark (जैसे 'Class PDF Telegram...') लगाना चाहते हैं?**\n(यह हर 1 मिनट में दिखाई देगा)", reply_markup=watermark_choice_keyboard())
|
| 708 |
-
|
| 709 |
-
elif step == 'AWAIT_WATERMARK_TEXT':
|
| 710 |
-
user_data[user_id]['watermark_text'] = message.text.strip()
|
| 711 |
-
user_data[user_id]['step'] = 'AUDIO_CHOICE'
|
| 712 |
-
await message.reply(f"✅ **वाटरमार्क '{message.text.strip()}' सेव हो गया!**\n\n🎵 **YouTube के लिए ऑडियो जरूरी है।**\nक्या आप अपना खुद का Audio (MP3) फाइल लगाना चाहते हैं?", reply_markup=audio_choice_keyboard())
|
| 713 |
-
|
| 714 |
-
elif step == 'AWAIT_AUDIO':
|
| 715 |
-
if message.audio or message.video or message.document:
|
| 716 |
-
msg = await message.reply("⏳ **Downloading Audio...**")
|
| 717 |
-
audio_path = await message.download()
|
| 718 |
-
user_data[user_id]['custom_audio_path'] = audio_path
|
| 719 |
-
user_data[user_id]['step'] = 'SCHEDULE'
|
| 720 |
-
await msg.edit_text("✅ **Audio Saved!**\n\n⏳ **स्ट्रीम अभी चालू करनी है या Schedule (शेड्यूल) करनी है?**", reply_markup=schedule_choice_keyboard())
|
| 721 |
-
else:
|
| 722 |
-
await message.reply("⚠️ **कृपया सही Audio या Video File भेजें!**")
|
| 723 |
-
|
| 724 |
-
elif step == 'AWAIT_DATETIME':
|
| 725 |
-
try:
|
| 726 |
-
dt_obj = datetime.strptime(message.text.strip(), "%d/%m/%Y %I:%M %p")
|
| 727 |
-
now_ist = get_ist_time().replace(tzinfo=None)
|
| 728 |
-
if dt_obj <= now_ist:
|
| 729 |
-
return await message.reply("⚠️ **समय (Time) भविष्य का होना चाहिए!** कृपया फिर से भेजें।\nExample: `26/05/2026 12:40 PM`")
|
| 730 |
-
|
| 731 |
-
user_data[user_id]['schedule_time'] = dt_obj
|
| 732 |
-
user_data[user_id]['step'] = 'READY'
|
| 733 |
-
|
| 734 |
-
delay = (dt_obj - now_ist).total_seconds()
|
| 735 |
-
await message.reply(f"✅ **Stream Scheduled!**\nयह वीडियो आटोमेटिकली `{message.text.strip()}` पर चालू हो जाएगा।")
|
| 736 |
-
|
| 737 |
-
asyncio.create_task(scheduled_stream_starter(client, message, delay))
|
| 738 |
-
except Exception as e:
|
| 739 |
-
await message.reply("❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `26/05/2026 12:40 PM`")
|
| 740 |
-
|
| 741 |
-
async def scheduled_stream_starter(client, message, delay):
|
| 742 |
-
await asyncio.sleep(delay)
|
| 743 |
-
await process_stream_start(client, message)
|
| 744 |
-
|
| 745 |
-
async def process_stream_start(client, message):
|
| 746 |
-
await handle_video(client, message)
|
| 747 |
-
|
| 748 |
-
def get_direct_url(url):
|
| 749 |
-
ydl_opts = {'format': 'best', 'quiet': True, 'noplaylist': True}
|
| 750 |
-
try:
|
| 751 |
-
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
| 752 |
-
info = ydl.extract_info(url, download=False)
|
| 753 |
-
return info.get('url', url)
|
| 754 |
-
except:
|
| 755 |
-
return url
|
| 756 |
-
|
| 757 |
-
async def handle_video(client, message):
|
| 758 |
-
user_id = message.chat.id
|
| 759 |
-
|
| 760 |
-
user_data[user_id]['manual_stop'] = False
|
| 761 |
-
msg = await client.send_message(user_id, "⏳ **Preparing Stream Environment...**")
|
| 762 |
-
|
| 763 |
-
file_path = user_data[user_id].get('file_path')
|
| 764 |
-
video_url = user_data[user_id].get('video_url')
|
| 765 |
-
custom_audio = user_data[user_id].get('custom_audio_path')
|
| 766 |
-
|
| 767 |
-
input_source = file_path
|
| 768 |
-
if video_url:
|
| 769 |
-
await msg.edit_text("🔗 **Extracting direct stream link (YouTube/Facebook/Rumble etc.)...**")
|
| 770 |
-
input_source = await asyncio.to_thread(get_direct_url, video_url)
|
| 771 |
-
if not input_source:
|
| 772 |
-
return await msg.edit_text("❌ **Failed to extract link!**")
|
| 773 |
-
|
| 774 |
-
platform = user_data[user_id].get('platform', 'Custom')
|
| 775 |
-
loop_hours = user_data[user_id].get('loop_hours', 0)
|
| 776 |
-
rtmp_url = user_data[user_id]['rtmp_url']
|
| 777 |
-
orientation = user_data[user_id].get('orientation', 'landscape')
|
| 778 |
-
image_path = user_data[user_id].get('custom_image_path')
|
| 779 |
-
|
| 780 |
-
user_data[user_id]['total_streams'] += 1
|
| 781 |
-
user_data[user_id]['last_stream'] = get_ist_time()
|
| 782 |
-
|
| 783 |
-
import random # Random time जनरेट करने के लिए
|
| 784 |
-
|
| 785 |
-
vid_duration = get_video_duration(file_path) if file_path else 3600
|
| 786 |
-
|
| 787 |
-
# -----------------------------------------------------
|
| 788 |
-
# ANTI-BOT RANDOMIZER: हर बार समय को थोड़ा आगे-पीछे करना
|
| 789 |
-
# -----------------------------------------------------
|
| 790 |
-
if loop_hours > 0:
|
| 791 |
-
base_sec = loop_hours * 3600
|
| 792 |
-
# 10% समय रैंडमली कम या ज्यादा करें (जैसे 3 घंटे में +/- 18 मिनट, 30 मिनट में +/- 3 मिनट)
|
| 793 |
-
random_offset = random.randint(int(-0.1 * base_sec), int(0.1 * base_sec))
|
| 794 |
-
actual_stream_seconds = base_sec + random_offset
|
| 795 |
-
total_stream_time = actual_stream_seconds
|
| 796 |
-
else:
|
| 797 |
-
total_stream_time = vid_duration
|
| 798 |
-
actual_stream_seconds = vid_duration
|
| 799 |
-
|
| 800 |
-
is_image_only = user_data[user_id].get('is_image_only', False)
|
| 801 |
-
|
| 802 |
-
# --- FFMPEG Command Setup ---
|
| 803 |
-
command = ["ffmpeg", "-re"]
|
| 804 |
-
|
| 805 |
-
if is_image_only:
|
| 806 |
-
# Image Only Streaming Logic (YouTube Standard 30 FPS)
|
| 807 |
-
command.extend(["-loop", "1", "-framerate", "30", "-i", input_source])
|
| 808 |
-
|
| 809 |
-
if custom_audio:
|
| 810 |
-
command.extend(["-stream_loop", "-1", "-i", custom_audio])
|
| 811 |
-
command.extend(["-map", "0:v", "-map", "1:a:0?"])
|
| 812 |
-
else:
|
| 813 |
-
# YouTube rejects streams without audio, so we add silent dummy audio
|
| 814 |
-
command.extend(["-f", "lavfi", "-i", "anullsrc=channel_layout=stereo:sample_rate=44100"])
|
| 815 |
-
command.extend(["-map", "0:v", "-map", "1:a"])
|
| 816 |
-
|
| 817 |
-
# -----------------------------------------------------
|
| 818 |
-
# NEW: Aspect Ratio Fix for Image (Portrait vs Landscape)
|
| 819 |
-
# -----------------------------------------------------
|
| 820 |
-
if orientation == 'portrait':
|
| 821 |
-
# Makes 16:9 photo into 9:16 (Adds black bars on top/bottom)
|
| 822 |
-
vf_filter = "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black"
|
| 823 |
-
else:
|
| 824 |
-
# Ensures normal photos fit 16:9 screen
|
| 825 |
-
vf_filter = "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black"
|
| 826 |
-
|
| 827 |
-
# --- NEW: SLOW SMOOTH MOVING WATERMARK ---
|
| 828 |
-
wm_text = user_data[user_id].get('watermark_text')
|
| 829 |
-
if wm_text:
|
| 830 |
-
safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
|
| 831 |
-
# sin(t/10) और cos(t/15) का मतलब है कि यह बहुत धीरे-धीरे (Slowly) पूरी स्क्रीन पर गोल घूमेगा
|
| 832 |
-
drawtext = f",drawtext=text='{safe_text}':fontcolor=white@0.4:fontsize=50:x=(w-tw)/2+((w-tw)/2.5)*sin(t/10):y=(h-th)/2+((h-th)/2.5)*cos(t/15)"
|
| 833 |
-
vf_filter += drawtext
|
| 834 |
-
|
| 835 |
-
command.extend(["-vf", vf_filter])
|
| 836 |
-
# -----------------------------------------------------
|
| 837 |
-
|
| 838 |
-
# Encode with strict YouTube standards (30 FPS, 2-sec Keyframe, CBR)
|
| 839 |
-
command.extend(["-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p"])
|
| 840 |
-
# YouTube Keyframe Fix: 30fps के हिसाब से हर 2 सेकंड में कीफ्रेम (-g 60)
|
| 841 |
-
command.extend(["-r", "30", "-g", "60", "-sc_threshold", "0"])
|
| 842 |
-
# YouTube Bitrate Fix: Strict CBR (Constant Bitrate) with Dummy Padding
|
| 843 |
-
command.extend(["-b:v", "4000k", "-maxrate", "4000k", "-minrate", "4000k", "-bufsize", "8000k", "-nal-hrd", "cbr"])
|
| 844 |
-
command.extend(["-c:a", "aac", "-b:a", "128k"])
|
| 845 |
-
|
| 846 |
-
else:
|
| 847 |
-
# Normal Video Logic
|
| 848 |
-
if loop_hours > 0: command.extend(["-stream_loop", "-1"])
|
| 849 |
-
command.extend(["-i", input_source])
|
| 850 |
-
|
| 851 |
-
current_idx = 1; idx_aud = 0
|
| 852 |
-
if custom_audio:
|
| 853 |
-
command.extend(["-stream_loop", "-1", "-i", custom_audio])
|
| 854 |
-
idx_aud = current_idx; current_idx += 1
|
| 855 |
-
|
| 856 |
-
idx_img = -1
|
| 857 |
-
if orientation == 'portrait' and image_path:
|
| 858 |
-
command.extend(["-loop", "1", "-i", image_path])
|
| 859 |
-
idx_img = current_idx; current_idx += 1
|
| 860 |
-
|
| 861 |
-
if orientation == 'portrait':
|
| 862 |
-
if image_path:
|
| 863 |
-
vf_complex = f"[{idx_img}:v]scale=1080:1920[bg];[0:v]scale=1080:1920:force_original_aspect_ratio=decrease[vid];[bg][vid]overlay=(W-w)/2:(H-h)/2:shortest=1[outv]"
|
| 864 |
-
command.extend(["-filter_complex", vf_complex, "-map", "[outv]"])
|
| 865 |
-
else:
|
| 866 |
-
vf_filter = "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black"
|
| 867 |
-
command.extend(["-vf", vf_filter, "-map", "0:v"])
|
| 868 |
-
|
| 869 |
-
# YouTube Strict Bitrate & Keyframe (GOP) Fix for Portrait Video
|
| 870 |
-
command.extend(["-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p"])
|
| 871 |
-
command.extend(["-r", "30", "-g", "60", "-sc_threshold", "0"])
|
| 872 |
-
command.extend(["-b:v", "6000k", "-maxrate", "6000k", "-minrate", "6000k", "-bufsize", "12000k", "-nal-hrd", "cbr"])
|
| 873 |
-
else:
|
| 874 |
-
# YouTube Strict Bitrate & Keyframe (GOP) Fix for Landscape Video
|
| 875 |
-
wm_text = user_data[user_id].get('watermark_text')
|
| 876 |
-
|
| 877 |
-
if wm_text:
|
| 878 |
-
safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
|
| 879 |
-
# Custom Timing Logic: 1st min, 3rd min, then every 5 mins (each for 15 sec)
|
| 880 |
-
timing_logic = "between(t\\,60\\,75)+between(t\\,180\\,195)+(gte(t\\,480)*lt(mod(t-480\\,300)\\,15))"
|
| 881 |
-
|
| 882 |
-
vf_filter = f"drawtext=text=' Telegram\\: {safe_text} ':fontcolor=white:fontsize=38:box=1:boxcolor=#2AABEE@0.9:boxborderw=15:x=40:y=h-th-40:enable='{timing_logic}'"
|
| 883 |
-
command.extend(["-vf", vf_filter, "-map", "0:v"])
|
| 884 |
-
else:
|
| 885 |
-
command.extend(["-map", "0:v"])
|
| 886 |
-
|
| 887 |
-
command.extend(["-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p"])
|
| 888 |
-
command.extend(["-r", "30", "-g", "60", "-sc_threshold", "0"])
|
| 889 |
-
command.extend(["-b:v", "6000k", "-maxrate", "6000k", "-minrate", "6000k", "-bufsize", "12000k", "-nal-hrd", "cbr"])
|
| 890 |
-
|
| 891 |
-
if custom_audio:
|
| 892 |
-
command.extend(["-map", f"{idx_aud}:a:0?", "-c:a", "aac", "-b:a", "128k", "-shortest"])
|
| 893 |
-
else:
|
| 894 |
-
command.extend(["-map", "0:a?", "-c:a", "aac", "-b:a", "128k"])
|
| 895 |
-
|
| 896 |
-
# Apply time limit for BOTH Image and Video
|
| 897 |
-
if loop_hours > 0:
|
| 898 |
-
seconds_str = str(int(actual_stream_seconds)) # असली रैंडम सेकंड्स
|
| 899 |
-
if not is_image_only: command.extend(["-fflags", "+genpts"])
|
| 900 |
-
command.extend(["-t", seconds_str])
|
| 901 |
-
|
| 902 |
-
command.extend(["-f", "flv", rtmp_url])
|
| 903 |
-
|
| 904 |
-
st_type = "🖼 Image" if is_image_only else "🎥 Video"
|
| 905 |
-
live_msg = await msg.edit_text(f"🟢 **Live Started on {platform}!**\n📋 **Type:** `{st_type}`\n📐 **Mode:** `{orientation.capitalize()}`\n⏳ **Time Left:** `Calculating...`\n\n🔹 To stop, send /stop")
|
| 906 |
-
|
| 907 |
-
process_obj = subprocess.Popen(command)
|
| 908 |
-
|
| 909 |
-
# Store Advanced Details for Admin Panel (Now saves actual randomized end time)
|
| 910 |
-
start_t = get_ist_time()
|
| 911 |
-
end_t = start_t + timedelta(seconds=actual_stream_seconds) if loop_hours > 0 else "Auto (Depends on Video)"
|
| 912 |
-
|
| 913 |
-
user_data[user_id]['processes'].append({
|
| 914 |
-
'process': process_obj,
|
| 915 |
-
'start_time': start_t,
|
| 916 |
-
'end_time': end_t,
|
| 917 |
-
'loop_hours': loop_hours,
|
| 918 |
-
'is_image': is_image_only
|
| 919 |
-
})
|
| 920 |
-
|
| 921 |
-
asyncio.create_task(monitor_stream(client, message.chat.id, process_obj, file_path, live_msg.id, loop_hours, platform, total_stream_time, image_path, custom_audio))
|
| 922 |
-
|
| 923 |
-
async def monitor_stream(client, chat_id, process, file_path, msg_id, loop_hours, platform, total_time, image_path, custom_audio):
|
| 924 |
-
start_time_ist = get_ist_time()
|
| 925 |
-
last_ui_update = time.time()
|
| 926 |
-
mode_text = f"🔁 {loop_hours} Hours Loop" if loop_hours > 0 else "▶️ 1-Time Play"
|
| 927 |
-
|
| 928 |
-
while process.poll() is None:
|
| 929 |
-
await asyncio.sleep(5)
|
| 930 |
-
now_time = time.time()
|
| 931 |
-
if total_time > 0 and (now_time - last_ui_update > 60):
|
| 932 |
-
last_ui_update = now_time
|
| 933 |
-
elapsed = (get_ist_time() - start_time_ist).total_seconds()
|
| 934 |
-
rem_sec = total_time - elapsed
|
| 935 |
-
|
| 936 |
-
if rem_sec > 0:
|
| 937 |
-
h = int(rem_sec // 3600)
|
| 938 |
-
m = int((rem_sec % 3600) // 60)
|
| 939 |
-
time_left_str = f"{h}h {m}m" if h > 0 else f"{m}m"
|
| 940 |
-
try:
|
| 941 |
-
await client.edit_message_text(
|
| 942 |
-
chat_id, msg_id,
|
| 943 |
-
f"🟢 **Live is RUNNING on {platform}!**\n"
|
| 944 |
-
f"⏱ **Loop:** `{mode_text}`\n"
|
| 945 |
-
f"⏳ **Time Left:** `{time_left_str}`\n\n"
|
| 946 |
-
f"🔹 Send /stop to end manually."
|
| 947 |
-
)
|
| 948 |
-
except: pass
|
| 949 |
-
|
| 950 |
-
end_time_ist = get_ist_time()
|
| 951 |
-
duration = end_time_ist - start_time_ist
|
| 952 |
-
|
| 953 |
-
try: await client.delete_messages(chat_id, msg_id)
|
| 954 |
-
except: pass
|
| 955 |
-
|
| 956 |
-
# Server Storage साफ़ करना (Delete file to save memory)
|
| 957 |
-
try:
|
| 958 |
-
if file_path and os.path.exists(file_path): os.remove(file_path)
|
| 959 |
-
if image_path and os.path.exists(image_path): os.remove(image_path)
|
| 960 |
-
if custom_audio and os.path.exists(custom_audio): os.remove(custom_audio)
|
| 961 |
-
except: pass
|
| 962 |
-
|
| 963 |
-
start_str = start_time_ist.strftime("%I:%M %p")
|
| 964 |
-
end_str = end_time_ist.strftime("%I:%M %p")
|
| 965 |
-
dur_str = str(duration).split('.')[0]
|
| 966 |
-
|
| 967 |
-
is_manual = user_data.get(chat_id, {}).get('manual_stop', False)
|
| 968 |
-
status_text = "🛑 **Live Stream Stopped Manually!**" if is_manual else "🔴 **Live Stream Ended Automatically!**"
|
| 969 |
-
|
| 970 |
-
report_msg = (
|
| 971 |
-
f"{status_text}\n\n"
|
| 972 |
-
f"📊 **STREAM SUMMARY:**\n"
|
| 973 |
-
f"🔸 **Platform:** `{platform}`\n"
|
| 974 |
-
f"🟢 **Started at:** `{start_str}`\n"
|
| 975 |
-
f"🔴 **Ended at:** `{end_str}`\n"
|
| 976 |
-
f"⏱ **Total Runtime:** `{dur_str}`\n\n"
|
| 977 |
-
f"🗑 __Video, Audio & Image deleted safely.__"
|
| 978 |
-
)
|
| 979 |
-
|
| 980 |
-
await client.send_message(chat_id, report_msg)
|
| 981 |
-
|
| 982 |
-
if __name__ == "__main__":
|
| 983 |
-
load_data() # बॉट स्टार्ट होते ही पुरानी सेटिंग्स और यूज़र्स वापस लोड करें
|
| 984 |
-
Thread(target=run_flask).start()
|
| 985 |
-
bot.run()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|