Update bot.py
import os
import time
import math
import json
import asyncio
import subprocess
import uuid
import yt_dlp
from datetime import datetime, timedelta
from threading import Thread
from flask import Flask
from pyrogram import Client, filters
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery
--- Hugging Face को जगाए रखने के लिए Flask Server ---
app = Flask(name)
@app .route('/')
def health_check():
return "Bot is running perfectly!"
def run_flask():
app.run(host="0.0.0.0", port=7860)
--- Telegram Bot की डिटेल्स ---
BOT_TOKEN = "8728797060:AAH3L0eqApxEKuVvYjKT0JGpgQ3BPlrjbgI"
API_ID = 35985614
API_HASH = "6a0df17414daf6935f1f0a71b8af1ee9"
bot = Client("LiveBot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN)
--- USER SESSION (Private Channel Download के लिए) ---
USER_SESSION = "BQIlGM4AazlRr5P1dhaKtIXeQTq0O5LnNaFB9uBcdjcjZQ95iBGIm3L_NTaA0ncK91ZKIr_4loGx8gw84Z_RL9gQDSZ80QOIq5RrwtlKvsfF-NrJElCGJ5NAL8GMtDceRIQpQO78DfI4PM75h7zylKi3jnDQik2bAUzJ7ylG1VI_hgfKxYZlkK2FXkGaACNgJSzUzc-4_THAlSDLNhpQ6Dv_WlFKQpGR47KdZTMxR81ExV5WjW4-AG1A0tr_B0PUvRVyZrpMLb5AzPMqFmByxeQS3QicQzXGKYlJj9Pm5uYRCcFS5WCqhC2lwISMqklhjIivc7LHko5EjyWELHhAhz2RfmPDQAAAAAHK_kURAA" # 👉 यहाँ अपना Pyrogram String Session डालें
user_app = Client("UserAcc", api_id=API_ID, api_hash=API_HASH, session_string=USER_SESSION)
--- ग्लोबल वेरिएबल्स ---
ADMIN_ID = 7700628753
GLOBAL_PASSWORD = "jitu@123"
PASSWORD_LIMIT = 999999 # डिफ़ॉल्ट लिमिट (Unlimted)
PASSWORD_USED_BY = {} # जिन लोगों ने पासवर्ड इस्तेमाल किया है उनका डेटा
GLOBAL_LIMIT = 1
user_data = {}
admin_state = None
--- मैसेज डिलीट करने के लिए नया डेटाबेस (टेम्परेरी) ---
sent_message_map = {}
recent_admin_messages =[]
GLOBAL_SCHEDULE_TIMES = [] # एडमिन के सेट किये गए प्री-डिफाइंड शेड्यूल्स
--- Data Persistence (बॉट रिस्टार्ट होने पर डेटा सेव रखने के लिए) ---
DATA_FILE = "bot_config.json"
def save_data():
try:
# सिर्फ ऑथेंटिकेटेड यूज़र्स का डेटा सेव करेंगे ताकि फाइल भारी न हो
auth_users = {str(uid): info['name'] for uid, info in user_data.items() if info.get('authenticated')}
with open(DATA_FILE, "w") as f:
json.dump({
"GLOBAL_PASSWORD": GLOBAL_PASSWORD,
"PASSWORD_LIMIT": PASSWORD_LIMIT,
"GLOBAL_LIMIT": GLOBAL_LIMIT,
"PASSWORD_USED_BY": {str(k): v for k, v in PASSWORD_USED_BY.items()},
"authenticated_users": auth_users,
"GLOBAL_SCHEDULE_TIMES": GLOBAL_SCHEDULE_TIMES
}, f)
except Exception as e: pass
def load_data():
global GLOBAL_PASSWORD, PASSWORD_LIMIT, GLOBAL_LIMIT, PASSWORD_USED_BY, user_data
if os.path.exists(DATA_FILE):
try:
with open(DATA_FILE, "r") as f:
data = json.load(f)
GLOBAL_PASSWORD = data.get("GLOBAL_PASSWORD", "jitu@123")
PASSWORD_LIMIT = data.get("PASSWORD_LIMIT", 999999)
GLOBAL_LIMIT = data.get("GLOBAL_LIMIT", 1)
PASSWORD_USED_BY = {int(k): v for k, v in data.get("PASSWORD_USED_BY", {}).items()}
GLOBAL_SCHEDULE_TIMES = data.get("GLOBAL_SCHEDULE_TIMES", [])
saved_users = data.get("authenticated_users", {})
for uid, name in saved_users.items():
uid = int(uid)
if uid not in user_data:
user_data[uid] = {
'name': name,
'authenticated': True,
'step': 'PLATFORM',
'platform': None,
'rtmp_url': None,
'loop_hours': 0,
'orientation': 'landscape',
'custom_image_path': None,
'custom_audio_path': None,
'video_url': None,
'schedule_time': None,
'processes':[],
'stream_limit': GLOBAL_LIMIT,
'file_path': None,
'manual_stop': False,
'join_date': get_ist_time(),
'last_online': get_ist_time(),
'total_streams': 0,
'last_stream': "Never"
}
except Exception as e: pass
def get_ist_time():
return datetime.utcnow() + timedelta(hours=5, minutes=30)
def format_date(dt):
if isinstance(dt, str): return dt
return dt.strftime("%d-%b-%Y %I:%M %p")
def get_video_duration(file_path):
try:
result = subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", file_path],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True
)
return float(result.stdout.strip())
except:
return 0
async def notify_admin(client, text):
try:
await client.send_message(ADMIN_ID, text)
except:
pass
def init_user(user_id, message=None):
if user_id not in user_data:
name = message.from_user.first_name if message else "Unknown"
user_data[user_id] = {
'name': name,
'authenticated': False if user_id != ADMIN_ID else True,
'step': 'PASSWORD' if user_id != ADMIN_ID else 'PLATFORM',
'platform': None,
'rtmp_url': None,
'loop_hours': 0,
'orientation': 'landscape',
'custom_image_path': None, # नया: कस्टम फोटो सेव करने के लिए
'custom_audio_path': None, # ऑडियो रिप्लेस
'watermark_text': None, # <--- नया: मूविंग वाटरमार्क के लिए
'video_url': None, # डायरेक्ट लिंक
'schedule_time': None, # शेड्यूलिंग
'processes':[],
'scheduled_tasks': {}, # <--- नया: शेड्यूल्ड टास्क को ट्रैक करने के लिए
'stream_limit': GLOBAL_LIMIT,
'file_path': None,
'manual_stop': False,
'join_date': get_ist_time(),
'last_online': get_ist_time(),
'total_streams': 0,
'last_stream': "Never"
}
if user_id != ADMIN_ID:
asyncio.create_task(notify_admin(bot, f"🔔 **New User Joined!**\n👤 Name: {name}\n🆔 ID: {user_id}\n📅 Date: {format_date(get_ist_time())}"))
else:
user_data[user_id]['last_online'] = get_ist_time()
if message:
user_data[user_id]['name'] = message.from_user.first_name
def platform_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("🔴 YouTube Live", callback_data="plat_youtube")],[InlineKeyboardButton("🟢 Rumble Live", callback_data="plat_rumble")],[InlineKeyboardButton("🔵 Custom/Other", callback_data="plat_custom")]])
def loop_keyboard():
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")]
])
def orientation_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("🖥 Landscape (16:9)", callback_data="ori_landscape")],[InlineKeyboardButton("📱 Portrait / Shorts (9:16)", callback_data="ori_portrait")]])
def image_choice_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("✅ Yes (Add Custom Photo)", callback_data="img_yes")],[InlineKeyboardButton("❌ No (Normal Black Bg)", callback_data="img_no")]])
def audio_choice_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("🎵 Replace Audio (Send File)", callback_data="aud_yes")],[InlineKeyboardButton("🔊 Keep Original Audio", callback_data="aud_no")]])
def watermark_choice_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("✅ Yes (Moving Watermark)", callback_data="wm_yes")],[InlineKeyboardButton("❌ No Watermark", callback_data="wm_no")]])
def schedule_choice_keyboard():
return InlineKeyboardMarkup([[InlineKeyboardButton("▶️ Start Now", callback_data="sch_now")],[InlineKeyboardButton("⏳ Schedule Time", callback_data="sch_later")]])
def admin_keyboard():
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("📅 Manage Schedule Times", callback_data="admin_sch_times")],
[InlineKeyboardButton("📢 Broadcast / Send Msg", callback_data="admin_msg")],
[InlineKeyboardButton("🗑 Delete Last 5 Broadcasts", callback_data="admin_del")]
])
--- Live Progress Bar ---
async def progress_bar(current, total, msg, start_time, last_update):
now = time.time()
if now - last_update[0] > 3 or current == total:
last_update[0] = now
diff = now - start_time
percentage = current * 100 / total
speed = current / diff if diff > 0 else 0
eta = round((total - current) / speed) if speed > 0 else 0
current_mb = round(current / (1024 * 1024), 2)
total_mb = round(total / (1024 * 1024), 2)
speed_mb = round(speed / (1024 * 1024), 2)
progress_str = "[{0}{1}]".format(''.join(["█" for _ in range(math.floor(percentage / 5))]), ''.join(["░" for _ in range(20 - math.floor(percentage / 5))]))
eta_str = f"{eta} sec" if eta < 60 else f"{eta//60} min {eta%60} sec"
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}`"
try: await msg.edit_text(text)
except: pass
@bot .on_message(filters.command("admin"))
async def admin_cmd(client, message):
if message.chat.id == ADMIN_ID:
global admin_state
admin_state = None
await message.reply("👑 Welcome to Admin Panel\nChoose an option below:", reply_markup=admin_keyboard())
@bot .on_message(filters.command("start"))
async def start_cmd(client, message):
user_id = message.chat.id
init_user(user_id, message)
if user_data[user_id]['authenticated']:
user_data[user_id]['step'] = 'PLATFORM'
await message.reply("**✅ Logged in!**\n👇 Select platform:", reply_markup=platform_keyboard())
else:
user_data[user_id]['step'] = 'PASSWORD'
await message.reply("👋 **Welcome!**\n🔐 Enter Password to continue:")
@bot .on_message(filters.command("new"))
async def new_cmd(client, message):
user_id = message.chat.id
init_user(user_id, message)
if not user_data[user_id]['authenticated']:
user_data[user_id]['step'] = 'PASSWORD'
return await message.reply("🔐 **Session Expired! Please enter the new password:**")
user_data[user_id]['step'] = 'PLATFORM'
# नई स्ट्रीम शुरू करते समय पुराना डेटा मिटा दें
user_data[user_id]['custom_image_path'] = None
user_data[user_id]['custom_audio_path'] = None
user_data[user_id]['watermark_text'] = None
user_data[user_id]['video_url'] = None
user_data[user_id]['file_path'] = None
await message.reply("🔄 **New Stream Setup!**\n👇 **Select platform:**", reply_markup=platform_keyboard())
@bot .on_message(filters.command("stop"))
async def stop_cmd(client, message):
user_id = message.chat.id
if user_id not in user_data: return
active_procs = []
for p in user_data[user_id].get('processes',[]):
proc_obj = p['process'] if isinstance(p, dict) else p
if proc_obj.poll() is None:
active_procs.append(proc_obj)
if not active_procs:
return await message.reply("⚠️ **No active stream found.**")
user_data[user_id]['manual_stop'] = True
for p in active_procs:
p.terminate()
user_data[user_id]['processes'] =[]
await message.reply(f"🛑 **Stopping {len(active_procs)} Active Stream(s)... Generating Report!**")
@bot .on_callback_query(filters.regex("^admin_"))
async def admin_callbacks(client, callback_query: CallbackQuery):
if callback_query.message.chat.id != ADMIN_ID:
return
data = callback_query.data
global admin_state
global recent_admin_messages
if data == "admin_users":
total_users = len(user_data)
text = f"👥 **Total Users:** `{total_users}`\n\n"
for uid, info in user_data.items():
if uid == ADMIN_ID: continue
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"
text += "➖➖➖➖➖➖➖➖\n"
if len(text) > 4000:
for x in range(0, len(text), 4000):
await client.send_message(ADMIN_ID, text[x:x+4000])
else:
await callback_query.message.reply(text)
elif data == "admin_live":
live_text = ""
total_live = 0
for uid, info in user_data.items():
active_streams =[]
for p in info.get('processes', []):
if isinstance(p, dict) and p['process'].poll() is None:
active_streams.append(p)
elif not isinstance(p, dict) and p.poll() is None: # Old format safety
active_streams.append({'process': p, 'start_time': get_ist_time(), 'end_time': "Unknown", 'loop_hours': 0, 'is_image': False})
if active_streams:
total_live += len(active_streams)
live_text += f"👤 **Name:** {info['name']} (ID: `{uid}`)\n🌐 **Platform:** `{info.get('platform', 'Unknown')}`\n"
for i, strm in enumerate(active_streams, 1):
mode_str = f"{strm['loop_hours']}h Loop" if strm['loop_hours'] > 0 else "1 Time"
if strm.get('is_image'): mode_str += " (🖼 Image)"
else: mode_str += " (🎥 Video)"
st_str = strm['start_time'].strftime("%I:%M %p")
et_str = strm['end_time'].strftime("%I:%M %p") if isinstance(strm['end_time'], datetime) else str(strm['end_time'])
live_text += f" ➤ **Stream {i}:** Mode: `{mode_str}`\n"
live_text += f" 🟢 Start: `{st_str}` | 🔴 End: `{et_str}`\n"
live_text += "➖➖➖➖➖➖\n"
if not live_text:
await callback_query.message.reply("❌ **No one is streaming right now.**")
else:
await callback_query.message.reply(f"🔴 **Live Streams ({total_live} total):**\n\n" + live_text)
elif data == "admin_pass":
markup = InlineKeyboardMarkup([
[InlineKeyboardButton("🔑 Change Password", callback_data="admin_pass_change")],[InlineKeyboardButton("📊 Pass Analysis", callback_data="admin_pass_analysis")]
])
await callback_query.message.edit_text("🔐 **Password Management**\nChoose an option below:", reply_markup=markup)
elif data == "admin_pass_change":
admin_state = "AWAIT_NEW_PASSWORD"
await callback_query.message.reply("🔑 **Please send the NEW PASSWORD and LIMIT.**\n`उदाहरण: 5550 5` (इसका मतलब सिर्फ 5 लोग पासवर्ड यूज़ कर पाएंगे)")
elif data == "admin_pass_analysis":
limit_text = str(PASSWORD_LIMIT) if PASSWORD_LIMIT != 999999 else "Unlimited"
if not PASSWORD_USED_BY:
await callback_query.message.reply(f"📊 **Pass Analysis:**\n🔑 **Current Password:** `{GLOBAL_PASSWORD}`\n👥 **Limit:** `0 / {limit_text}`\n\nअभी तक किसी ने भी मौजूदा पासवर्ड का इस्तेमाल नहीं किया है।")
else:
text = f"📊 **Pass Analysis:**\n🔑 **Current Password:** `{GLOBAL_PASSWORD}`\n👥 **Limit:** `{len(PASSWORD_USED_BY)} / {limit_text}`\n\n"
for uid, uname in PASSWORD_USED_BY.items():
text += f"👤 **Name:** {uname}\n🆔 **ID:** `{uid}`\n➖➖➖➖➖➖\n"
await callback_query.message.reply(text)
elif data == "admin_limit":
admin_state = "AWAIT_LIMIT"
await callback_query.message.reply("⚙️ **Set Stream Limit Mode:**\n\n👉 **सबका लिमिट बदलने के लिए:** सिर्फ नंबर भेजें (e.g. `2`)\n👉 **किसी एक यूज़र का लिमिट बदलने के लिए:** ID और नंबर भेजें\n`उदाहरण: 384884848 2`")
elif data == "admin_msg":
admin_state = "AWAIT_BROADCAST"
await callback_query.message.reply("📢 **Broadcast / Personal Message Mode!**\n\n"
"👉 **सबको भेजने के लिए:** कोई भी Text, Photo या Video सीधा भेजें।\n"
"👉 **किसी एक को भेजने के लिए:** ID स्पेस मेसेज लिखें।\n"
"`उदाहरण: 8383838487 Hello kese ho`")
elif data == "admin_sch_times":
markup = []
for st in GLOBAL_SCHEDULE_TIMES:
markup.append([InlineKeyboardButton(f"❌ Delete {st}", callback_data=f"admindelscht_{st}")])
markup.append([InlineKeyboardButton("➕ Add New Time", callback_data="admin_sch_add")])
markup.append([InlineKeyboardButton("🔙 Back", callback_data="admin_back")])
await callback_query.message.edit_text("📅 **Manage Pre-defined Schedule Times:**\nये समय यूज़र्स को बटन के रूप में दिखेंगे।", reply_markup=InlineKeyboardMarkup(markup))
elif data == "admin_back":
await callback_query.message.edit_text("👑 **Welcome to Admin Panel**\nChoose an option below:", reply_markup=admin_keyboard())
elif data == "admin_sch_add":
admin_state = "AWAIT_SCH_TIME"
await callback_query.message.reply("📅 **कृपया नया शेड्यूल्ड समय भेजें:**\n\n**Format:** `DD/MM/YYYY hh:mm AM/PM`\n**Example:** `24/06/2026 05:50 AM`")
elif data.startswith("admindelscht_"):
time_to_del = data.replace("admindelscht_", "")
if time_to_del in GLOBAL_SCHEDULE_TIMES:
GLOBAL_SCHEDULE_TIMES.remove(time_to_del)
save_data()
await admin_callbacks(client, callback_query._client.callback_query(id=callback_query.id, from_user=callback_query.from_user, message=callback_query.message, data="admin_sch_times"))
elif data == "admin_del":
if not recent_admin_messages:
return await callback_query.message.reply("❌ **No recent messages/broadcasts found to delete.**")
deleted_ops = 0
total_msgs = 0
for _ in range(5):
if not recent_admin_messages:
break
targets = recent_admin_messages.pop()
deleted_ops += 1
for uid, mid in targets:
try:
await client.delete_messages(uid, mid)
total_msgs += 1
except: pass
await callback_query.message.reply(f"✅ **Deleted last {deleted_ops} broadcast(s)/message(s) from {total_msgs} users' chats!**")
@bot .on_callback_query(filters.regex("^plat_"))
async def select_platform(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
init_user(user_id)
if not user_data[user_id].get('authenticated'):
return await callback_query.message.edit_text("🔐 **Session Expired! Please send /start and enter password.**")
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]
user_data[user_id]['processes'] = active_procs
limit = user_data[user_id].get('stream_limit', GLOBAL_LIMIT)
if len(active_procs) >= limit:
return await callback_query.message.edit_text(f"⚠️ **Limit Reached!**\nआप एक साथ सिर्फ {limit} स्ट्रीम चला सकते हैं। कृपया पहली स्ट्रीम खत्म होने का इंतज़ार करें।")
data = callback_query.data
if data == "plat_youtube":
user_data[user_id]['platform'] = 'YouTube'
text = "🔴 **YouTube Selected!**\n🔑 **Send YouTube Stream Key:**"
elif data == "plat_rumble":
user_data[user_id]['platform'] = 'Rumble'
text = "🟢 **Rumble Selected!**\n🔑 **Send Rumble Stream Key (STATIC KEY):**"
elif data == "plat_custom":
user_data[user_id]['platform'] = 'Custom'
text = "🔵 **Custom RTMP Selected!**\n🔗 **Send FULL RTMP URL + Key:**"
user_data[user_id]['step'] = 'STREAM_KEY' if data != "plat_custom" else 'CUSTOM_URL'
await callback_query.message.edit_text(text)
@bot .on_callback_query(filters.regex("^loop_"))
async def select_loop(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
init_user(user_id)
if not user_data[user_id].get('authenticated'):
return await callback_query.message.edit_text("🔐 **Session Expired! Please send /start and enter password.**")
# Changed int() to float() to support 0.5 hours (30 Mins)
hours = float(callback_query.data.split("_")[1])
user_data[user_id]['loop_hours'] = hours
user_data[user_id]['step'] = 'ORIENTATION'
await callback_query.message.edit_text("📐 **Select Stream Orientation:**\nवीडियो कैसे चलाना है?", reply_markup=orientation_keyboard())
@bot .on_callback_query(filters.regex("^ori_"))
async def select_orientation(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
init_user(user_id)
if not user_data[user_id].get('authenticated'):
return await callback_query.message.edit_text("🔐 **Session Expired! Please send /start and enter password.**")
data = callback_query.data
if data == "ori_landscape":
user_data[user_id]['orientation'] = 'landscape'
user_data[user_id]['step'] = 'VIDEO_OR_URL'
text = "🖥 **Landscape (16:9) Selected!**\n\n📥 **अब कोई Video File भेजें या डायरेक्ट Link (YouTube/Insta/FB) भेजें:**"
await callback_query.message.edit_text(text)
else:
user_data[user_id]['orientation'] = 'portrait'
user_data[user_id]['step'] = 'WAIT_IMAGE_CHOICE'
text = "📱 **Portrait/Shorts (9:16) Selected!**\n\n🖼 **क्या आप वीडियो के ऊपर/नीचे खाली जगह पर कोई Custom Photo (Background) लगाना चाहते हैं?**"
await callback_query.message.edit_text(text, reply_markup=image_choice_keyboard())
@bot .on_callback_query(filters.regex("^img_"))
async def select_image_choice(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
init_user(user_id)
if not user_data[user_id].get('authenticated'):
return await callback_query.message.edit_text("🔐 **Session Expired! Please send /start and enter password.**")
data = callback_query.data
if data == "img_yes":
user_data[user_id]['step'] = 'AWAIT_IMAGE'
text = "🖼 **Send your Custom Background Image (Photo)!**\n(यह फोटो आपके वीडियो के पीछे (Background) में दिखेगी)"
else:
user_data[user_id]['step'] = 'VIDEO_OR_URL'
text = "⬛️ **No Image Selected (Black Background).**\n\n📥 **अब कोई Video File भेजें या डायरेक्ट Link (YouTube/Insta/FB) भेजें:**"
await callback_query.message.edit_text(text)
@bot .on_callback_query(filters.regex("^aud_"))
async def select_audio_choice(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
data = callback_query.data
if data == "aud_yes":
user_data[user_id]['step'] = 'AWAIT_AUDIO'
await callback_query.message.edit_text("🎵 अपना Audio (MP3) या Video File भेजें जिससे आवाज़ निकालनी है!")
else:
user_data[user_id]['step'] = 'SCHEDULE'
await callback_query.message.edit_text("⏳ स्ट्रीम अभी चालू करनी है या Schedule (शेड्यूल) करनी है?", reply_markup=schedule_choice_keyboard())
@bot .on_callback_query(filters.regex("^wm_"))
async def select_watermark_choice(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
if callback_query.data == "wm_yes":
user_data[user_id]['step'] = 'AWAIT_WATERMARK_TEXT'
await callback_query.message.edit_text("✍️ **कृपया Watermark का Text भेजें:**\n(जैसे: Class PDF Telegram Pr Milengi)")
else:
user_data[user_id]['step'] = 'AUDIO_CHOICE'
await callback_query.message.edit_text("🎵 YouTube के लिए ऑडियो जरूरी है।\nक्या आप अपना खुद का Audio (MP3) लगाना चाहते हैं?", reply_markup=audio_choice_keyboard())
@bot .on_callback_query(filters.regex("^sch_"))
async def select_schedule_choice(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
if callback_query.data == "sch_now":
user_data[user_id]['step'] = 'READY'
await callback_query.message.delete()
await process_stream_start(client, callback_query.message)
else:
user_data[user_id]['step'] = 'AWAIT_DATETIME'
markup = []
for st in GLOBAL_SCHEDULE_TIMES:
# Inline button format for specific times
markup.append([InlineKeyboardButton(f"🗓 {st}", callback_data=f"set_schtime_{st}")])
# बटन के बिना मैन्युअल एंट्री का निर्देश
text = "📅 **Schedule Time चुनें या लिखें!**\n\n"
if GLOBAL_SCHEDULE_TIMES:
text += "👇 **एडमिन द्वारा दिए गए समय में से चुनें:**\n"
text += "\n✍️ **या अपना खुद का समय लिखकर भेजें:**\n**Format:** `DD/MM/YYYY hh:mm AM/PM`\n**Example:** `24/06/2026 05:50 AM`"
await callback_query.message.edit_text(text, reply_markup=InlineKeyboardMarkup(markup) if markup else None)
@bot .on_callback_query(filters.regex("^set_schtime_"))
async def select_predefined_time(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
time_str = callback_query.data.replace("set_schtime_", "")
# Create fake message to pass to AWAIT_DATETIME logic
class FakeMessage:
def __init__(self, text, chat_id):
self.text = text
self.chat = type('Chat', (), {'id': chat_id})()
self.id = callback_query.message.id
await callback_query.message.edit_text(f"⏳ Processing schedule for {time_str}...")
await handle_all_messages(client, FakeMessage(time_str, user_id))
@bot .on_message(filters.command("edit"))
async def edit_cmd(client, message):
user_id = message.chat.id
init_user(user_id, message)
markup = []
text = "🛠 **Manage Your Streams:**\nयहाँ से आप अपनी ग़लत बनी हुई या चल रही स्ट्रीम्स को Delete/Stop कर सकते हैं।\n\n"
# Running streams
active_procs = [p for p in user_data[user_id].get('processes', []) if p['process'].poll() is None]
if active_procs:
text += "🔴 **Running Streams:**\n"
for idx, p in enumerate(active_procs, 1):
p_id = p.get('id', 'unknown')
plat = p.get('platform', 'Unknown')
text += f"{idx}. 🟢 {plat} - Started: {p['start_time'].strftime('%I:%M %p')}\n"
markup.append([InlineKeyboardButton(f"🛑 Stop Running: {plat} Stream {idx}", callback_data=f"user_stop_{p_id}")])
# Scheduled streams
scheduled = user_data[user_id].get('scheduled_tasks', {})
if scheduled:
text += "\n⏳ **Scheduled Streams:**\n"
idx = 1
for s_id, s_info in list(scheduled.items()):
time_str = s_info['time_str']
plat = s_info['config'].get('platform', 'Unknown')
text += f"{idx}. 🗓 {plat} - Scheduled for: {time_str}\n"
markup.append([InlineKeyboardButton(f"❌ Cancel Scheduled: {plat} {time_str}", callback_data=f"user_cancelsch_{s_id}")])
idx += 1
if not active_procs and not scheduled:
text = "⚠️ **आपकी कोई भी स्ट्रीम अभी चल नहीं रही है और ना ही शेड्यूल है।**"
await message.reply(text, reply_markup=InlineKeyboardMarkup(markup) if markup else None)
@bot .on_message(~filters.command(["start", "stop", "new", "admin", "edit"]))
async def handle_all_messages(client, message):
user_id = message.chat.id
init_user(user_id, message)
step = user_data[user_id].get('step')
# === Admin Reply to Delete ===
if message.text == "/delete" and message.reply_to_message and user_id == ADMIN_ID:
summary_id = message.reply_to_message.id
if summary_id in sent_message_map:
targets = sent_message_map[summary_id]
deleted_count = 0
for uid, mid in targets:
try:
await client.delete_messages(uid, mid)
deleted_count += 1
except: pass
await message.reply(f"✅ **Message successfully deleted from {deleted_count} user(s) chat!**")
del sent_message_map[summary_id]
else:
await message.reply("❌ **Could not find message data. Already deleted or too old.**")
return
# === Admin State Handling ===
if user_id == ADMIN_ID:
global admin_state, GLOBAL_PASSWORD, GLOBAL_LIMIT, recent_admin_messages, PASSWORD_LIMIT, PASSWORD_USED_BY
if admin_state == "AWAIT_NEW_PASSWORD" and message.text:
parts = message.text.strip().split()
GLOBAL_PASSWORD = parts[0]
# अगर एडमिन ने स्पेस देकर नंबर लिखा है, तो उसे लिमिट सेट करें, नहीं तो अनलिमिटेड
PASSWORD_LIMIT = int(parts[1]) if len(parts) > 1 and parts[1].isdigit() else 999999
PASSWORD_USED_BY = {} # नया पासवर्ड आते ही पुरानी लिस्ट खाली कर दें
admin_state = None
limit_text = str(PASSWORD_LIMIT) if PASSWORD_LIMIT != 999999 else "Unlimited"
await message.reply(f"✅ **Password Changed to:** `{GLOBAL_PASSWORD}`\n👥 **Max Users Allowed:** `{limit_text}`\n\nLogging everyone out...")
for uid in user_data:
if uid != ADMIN_ID:
user_data[uid]['authenticated'] = False
user_data[uid]['step'] = 'PASSWORD'
try:
await client.send_message(uid, "⚠️ **लॉगआउट अलर्ट! (Logout Alert)**\n\nएडमिन ने पासवर्ड बदल दिया है। कृपया `/start` दबाकर नया पासवर्ड डालें।")
except: pass
save_data() # डेटाबेस अपडेट करें
return
elif admin_state == "AWAIT_LIMIT" and message.text:
text = message.text.strip()
parts = text.split()
if len(parts) == 1 and parts[0].isdigit():
GLOBAL_LIMIT = int(parts[0])
for uid in user_data:
user_data[uid]['stream_limit'] = GLOBAL_LIMIT
await message.reply(f"✅ **Global Stream Limit set to {GLOBAL_LIMIT} for everyone!**")
elif len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
target_id = int(parts[0])
limit = int(parts[1])
if target_id in user_data:
user_data[target_id]['stream_limit'] = limit
await message.reply(f"✅ **User `{target_id}` can now run {limit} streams at once!**")
try: await client.send_message(target_id, f"🎉 **Admin Alert:** Your concurrent stream limit has been increased to **{limit}**!")
except: pass
else:
await message.reply("❌ **User not found in bot database!**")
else:
await message.reply("❌ **Invalid format!** Send something like `2` or `123456789 2`")
admin_state = None
save_data() # डेटाबेस अपडेट करें
return
elif admin_state == "AWAIT_BROADCAST":
text_to_check = message.text or message.caption
is_personal = False
target_id = None
content = None
if text_to_check:
parts = text_to_check.split(" ", 1)
if len(parts) > 1 and parts[0].isdigit() and len(parts[0]) > 5:
is_personal = True
target_id = int(parts[0])
content = parts[1]
targets =[]
if is_personal:
try:
if message.text:
msg = await client.send_message(target_id, content)
else:
msg = await message.copy(target_id, caption=content)
targets.append((target_id, msg.id))
summary = await message.reply(f"✅ **Message sent privately to `{target_id}`!**\n_Reply to this message with `/delete` to remove it from their chat._")
except Exception as e:
await message.reply(f"❌ **Failed to send.** Target might have blocked the bot.")
admin_state = None
return
else:
success = 0
for uid in user_data:
if uid != ADMIN_ID:
try:
msg = await message.copy(uid)
targets.append((uid, msg.id))
success += 1
except: pass
summary = await message.reply(f"✅ **Broadcast Sent successfully to {success} users!**\n_Reply to this message with `/delete` to remove it from all users._")
if targets:
sent_message_map[summary.id] = targets
recent_admin_messages.append(targets)
if len(recent_admin_messages) > 50:
recent_admin_messages.pop(0)
admin_state = None
return
elif admin_state == "AWAIT_SCH_TIME" and message.text:
try:
# Validation
dt_obj = datetime.strptime(message.text.strip(), "%d/%m/%Y %I:%M %p")
GLOBAL_SCHEDULE_TIMES.append(message.text.strip())
save_data()
await message.reply(f"✅ **Time Added Successfully!**\n`{message.text.strip()}`\n\nअब यह समय सभी यूज़र्स को बटन में दिखाई देगा।")
admin_state = None
except Exception as e:
await message.reply("❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `24/06/2026 05:50 AM`")
return
# === Normal User Handling ===
if not user_data[user_id]['authenticated'] and step != 'PASSWORD':
user_data[user_id]['step'] = 'PASSWORD'
return await message.reply("🔐 **Session Expired! Please enter password:**")
# यहाँ हमने नए स्टेप्स (VIDEO_OR_URL और AWAIT_AUDIO) को Add कर दिया है
if not message.text and step not in['VIDEO_OR_URL', 'AWAIT_IMAGE', 'AWAIT_AUDIO']:
return
if step == 'PASSWORD':
if message.text == GLOBAL_PASSWORD:
# चेक करें कि यूज़र पहले से लिस्ट में है या नहीं
if user_id not in PASSWORD_USED_BY:
if len(PASSWORD_USED_BY) >= PASSWORD_LIMIT:
return await message.reply("🚫 **Password Limit Reached!**\nयह पासवर्ड अपनी लिमिट पूरी कर चुका है। कृपया एडमिन से नया पासवर्ड लें।")
# अगर लिमिट बची है, तो यूज़र को एनालिसिस लिस्ट में जोड़ दें
PASSWORD_USED_BY[user_id] = user_data[user_id]['name']
user_data[user_id]['authenticated'] = True
user_data[user_id]['step'] = 'PLATFORM'
save_data() # यूज़र लॉगिन सेव करें ताकि रीस्टार्ट होने पर लॉगिन न हटे
await message.reply("**✅ Password Correct!**\n👇 **Select platform:**", reply_markup=platform_keyboard())
else:
await message.reply("❌ **Wrong Password! Try again.**")
elif step == 'STREAM_KEY':
key = message.text
platform = user_data[user_id]['platform']
if platform == 'YouTube':
user_data[user_id]['rtmp_url'] = f"rtmp://a.rtmp.youtube.com/live2/{key}"
elif platform == 'Rumble':
user_data[user_id]['rtmp_url'] = f"rtmp://rtmp.rumble.com/live/{key}"
user_data[user_id]['step'] = 'LOOP_CHOICE'
await message.reply(f"✅ **Key Saved!**\n\n⏱ **How long do you want to stream?**", reply_markup=loop_keyboard())
elif step == 'CUSTOM_URL':
user_data[user_id]['rtmp_url'] = message.text
user_data[user_id]['step'] = 'LOOP_CHOICE'
await message.reply("✅ **Custom URL Saved!**\n\n⏱ **How long do you want to stream?**", reply_markup=loop_keyboard())
elif step == 'AWAIT_IMAGE':
if message.photo or message.document:
msg = await message.reply("⏳ **Saving Image...**")
image_path = await message.download()
user_data[user_id]['custom_image_path'] = image_path
user_data[user_id]['step'] = 'VIDEO_OR_URL'
await msg.edit_text("✅ **Background Image Saved!**\n\n📥 **अब कोई Video File भेजें या डायरेक्ट Link (YouTube/Insta/FB) भेजें:**")
else:
await message.reply("⚠️ **Please send a valid Photo!**")
elif step == 'VIDEO_OR_URL':
user_data[user_id]['is_image_only'] = False
if message.video or message.document:
msg = await message.reply("⏳ **Downloading Video...**")
start_time = time.time()
last_update = [start_time]
file_path = await message.download(progress=progress_bar, progress_args=(msg, start_time, last_update))
user_data[user_id]['file_path'] = file_path
await msg.delete()
elif message.photo:
msg = await message.reply("⏳ **Downloading Image...**")
file_path = await message.download()
user_data[user_id]['file_path'] = file_path
user_data[user_id]['is_image_only'] = True
await msg.delete()
elif message.text and "t.me/c/" in message.text:
# Telegram Private Channel Link Logic
if not USER_SESSION:
return await message.reply("❌ **String Session सेट नहीं है! बॉट के कोड में USER_SESSION डालें।**")
msg = await message.reply("🔍 **प्राइवेट चैनल में वीडियो ढूँढा जा रहा है... (आस-पास के मैसेज चेक कर रहा हूँ)**")
try:
link_parts = message.text.strip().split('/')
chat_id = int("-100" + link_parts[4])
msg_id = int(link_parts[5])
if not user_app.is_connected:
await user_app.start()
target_msg = None
# आगे-पीछे के 20 मैसेज चेक करेगा कि Video किसमें है
messages = await user_app.get_messages(chat_id, range(max(1, msg_id - 10), msg_id + 11))
for m in messages:
if m and not m.empty and (m.video or m.document):
target_msg = m
break
if not target_msg:
return await msg.edit_text("❌ **इस लिंक के आस-पास कोई Video नहीं मिला!**")
await msg.edit_text("⏳ **Private Video Downloading...**")
start_time = time.time()
last_update = [start_time]
file_path = await user_app.download_media(target_msg, progress=progress_bar, progress_args=(msg, start_time, last_update))
user_data[user_id]['file_path'] = file_path
await msg.delete()
except Exception as e:
return await msg.edit_text(f"❌ **Private Video Error:** {str(e)}")
elif message.text and ("http://" in message.text or "https://" in message.text):
user_data[user_id]['video_url'] = message.text.strip()
else:
return await message.reply("⚠️ **कृपया सही Video, Photo या Link भेजें!**")
user_data[user_id]['step'] = 'WATERMARK_CHOICE'
if user_data[user_id]['is_image_only']:
await message.reply("✨ **क्या आप फोटो पर चलता हुआ (Moving) नाम/वाटरमार्क लगाना चाहते हैं?**\n(यह YouTube को स्पैम से बचाएगा)", reply_markup=watermark_choice_keyboard())
else:
await message.reply("✨ **क्या आप वीडियो पर Watermark (जैसे 'Class PDF Telegram...') लगाना चाहते हैं?**\n(यह हर 1 मिनट में दिखाई देगा)", reply_markup=watermark_choice_keyboard())
elif step == 'AWAIT_WATERMARK_TEXT':
user_data[user_id]['watermark_text'] = message.text.strip()
user_data[user_id]['step'] = 'AUDIO_CHOICE'
await message.reply(f"✅ **वाटरमार्क '{message.text.strip()}' सेव हो गया!**\n\n🎵 **YouTube के लिए ऑडियो जरूरी है।**\nक्या आप अपना खुद का Audio (MP3) फाइल लगाना चाहते हैं?", reply_markup=audio_choice_keyboard())
elif step == 'AWAIT_AUDIO':
if message.audio or message.video or message.document:
msg = await message.reply("⏳ **Downloading Audio...**")
audio_path = await message.download()
user_data[user_id]['custom_audio_path'] = audio_path
user_data[user_id]['step'] = 'SCHEDULE'
await msg.edit_text("✅ **Audio Saved!**\n\n⏳ **स्ट्रीम अभी चालू करनी है या Schedule (शेड्यूल) करनी है?**", reply_markup=schedule_choice_keyboard())
else:
await message.reply("⚠️ **कृपया सही Audio या Video File भेजें!**")
elif step == 'AWAIT_DATETIME':
try:
time_str = message.text.strip()
dt_obj = datetime.strptime(time_str, "%d/%m/%Y %I:%M %p")
now_ist = get_ist_time().replace(tzinfo=None)
if dt_obj <= now_ist:
# If pre-defined time is passed, allow it logically but warn if past
try:
await client.send_message(user_id, "⚠️ **समय (Time) भविष्य का होना चाहिए!** कृपया आगे का समय भेजें।")
except:
await message.reply("⚠️ **समय (Time) भविष्य का होना चाहिए!** कृपया आगे का समय भेजें।")
return
user_data[user_id]['step'] = 'READY'
delay = (dt_obj - now_ist).total_seconds()
# --- Snapshot Configuration ---
# (अगर यूज़र दूसरी स्ट्रीम बनायेगा तो पहली स्ट्रीम का डेटा डिलीट न हो)
stream_config = {
'file_path': user_data[user_id].get('file_path'),
'video_url': user_data[user_id].get('video_url'),
'custom_audio_path': user_data[user_id].get('custom_audio_path'),
'platform': user_data[user_id].get('platform', 'Custom'),
'loop_hours': user_data[user_id].get('loop_hours', 0),
'rtmp_url': user_data[user_id].get('rtmp_url'),
'orientation': user_data[user_id].get('orientation', 'landscape'),
'custom_image_path': user_data[user_id].get('custom_image_path'),
'is_image_only': user_data[user_id].get('is_image_only', False),
'watermark_text': user_data[user_id].get('watermark_text')
}
s_id = str(uuid.uuid4())[:8]
task = asyncio.create_task(scheduled_stream_starter(client, message, delay, stream_config, s_id))
user_data[user_id]['scheduled_tasks'][s_id] = {
'task': task,
'time_str': time_str,
'config': stream_config
}
try:
await client.send_message(user_id, f"✅ **Stream Scheduled!**\nयह वीडियो आटोमेटिकली `{time_str}` पर चालू हो जाएगा।\nअगर कोई गलती हुई हो तो `/edit` टाइप करें।")
except:
await message.reply(f"✅ **Stream Scheduled!**\nयह वीडियो आटोमेटिकली `{time_str}` पर चालू हो जाएगा।\nअगर कोई गलती हुई हो तो `/edit` टाइप करें।")
except Exception as e:
try:
await client.send_message(user_id, "❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `24/06/2026 05:50 AM`")
except:
await message.reply("❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `24/06/2026 05:50 AM`")
@bot .on_callback_query(filters.regex("^user_cancelsch_"))
async def cancel_sch_callback(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
s_id = callback_query.data.replace("user_cancelsch_", "")
if s_id in user_data[user_id].get('scheduled_tasks', {}):
user_data[user_id]['scheduled_tasks'][s_id]['task'].cancel()
del user_data[user_id]['scheduled_tasks'][s_id]
await callback_query.message.edit_text("✅ **Scheduled Stream Cancelled / Delete हो गई!**\nअब आप /new से नई बना सकते हैं।")
else:
await callback_query.message.edit_text("❌ यह शेड्यूल पहले ही कैंसिल हो चुका है या चल चुका है।")
@bot .on_callback_query(filters.regex("^user_stop_"))
async def stop_running_callback(client, callback_query: CallbackQuery):
user_id = callback_query.message.chat.id
p_id = callback_query.data.replace("user_stop_", "")
stopped = False
for p in user_data[user_id].get('processes', []):
if p.get('id') == p_id and p['process'].poll() is None:
user_data[user_id]['manual_stop'] = True
p['process'].terminate()
stopped = True
break
if stopped:
await callback_query.message.edit_text("✅ **Running Stream को सफलतापूर्वक बंद कर दिया गया है!**")
else:
await callback_query.message.edit_text("❌ **यह स्ट्रीम पहले ही बंद हो चुकी है।**")
async def scheduled_stream_starter(client, message, delay, stream_config, s_id):
try:
await asyncio.sleep(delay)
user_id = message.chat.id
# Remove from pending queue as it starts
if s_id in user_data[user_id].get('scheduled_tasks', {}):
del user_data[user_id]['scheduled_tasks'][s_id]
await handle_video(client, message, stream_config)
except asyncio.CancelledError:
pass # Stream was cancelled by user
async def process_stream_start(client, message):
await handle_video(client, message)
def get_direct_url(url):
ydl_opts = {'format': 'best', 'quiet': True, 'noplaylist': True}
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info(url, download=False)
return info.get('url', url)
except:
return url
async def handle_video(client, message, stream_config=None):
user_id = message.chat.id
# अगर कॉन्फ़िग स्नैपशॉट (शेड्यूल से) आया है, तो उसका इस्तेमाल करें। नहीं तो करंट डेटा का।
config = stream_config if stream_config else user_data[user_id]
user_data[user_id]['manual_stop'] = False
try:
msg = await client.send_message(user_id, "⏳ **Preparing Stream Environment...**")
except:
msg = await message.reply("⏳ **Preparing Stream Environment...**")
file_path = config.get('file_path')
video_url = config.get('video_url')
custom_audio = config.get('custom_audio_path')
input_source = file_path
if video_url:
await msg.edit_text("🔗 **Extracting direct stream link (YouTube/Facebook/Rumble etc.)...**")
input_source = await asyncio.to_thread(get_direct_url, video_url)
if not input_source:
return await msg.edit_text("❌ **Failed to extract link!**")
platform = config.get('platform', 'Custom')
loop_hours = config.get('loop_hours', 0)
rtmp_url = config.get('rtmp_url')
orientation = config.get('orientation', 'landscape')
image_path = config.get('custom_image_path')
user_data[user_id]['total_streams'] += 1
user_data[user_id]['last_stream'] = get_ist_time()
import random # Random time जनरेट करने के लिए
vid_duration = get_video_duration(file_path) if file_path else 3600
# -----------------------------------------------------
# ANTI-BOT RANDOMIZER: हर बार समय को थोड़ा आगे-पीछे करना
# -----------------------------------------------------
if loop_hours > 0:
base_sec = loop_hours * 3600
# 10% समय रैंडमली कम या ज्यादा करें (जैसे 3 घंटे में +/- 18 मिनट, 30 मिनट में +/- 3 मिनट)
random_offset = random.randint(int(-0.1 * base_sec), int(0.1 * base_sec))
actual_stream_seconds = base_sec + random_offset
total_stream_time = actual_stream_seconds
else:
total_stream_time = vid_duration
actual_stream_seconds = vid_duration
is_image_only = config.get('is_image_only', False)
# --- FFMPEG Command Setup ---
command = ["ffmpeg", "-re"]
if is_image_only:
# Image Only Streaming Logic (YouTube Standard 30 FPS)
command.extend(["-loop", "1", "-framerate", "30", "-i", input_source])
if custom_audio:
command.extend(["-stream_loop", "-1", "-i", custom_audio])
command.extend(["-map", "0:v", "-map", "1:a:0?"])
else:
# YouTube rejects streams without audio, so we add silent dummy audio
command.extend(["-f", "lavfi", "-i", "anullsrc=channel_layout=stereo:sample_rate=44100"])
command.extend(["-map", "0:v", "-map", "1:a"])
# -----------------------------------------------------
# NEW: Aspect Ratio Fix for Image (Portrait vs Landscape)
# -----------------------------------------------------
if orientation == 'portrait':
# Makes 16:9 photo into 9:16 (Adds black bars on top/bottom)
vf_filter = "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black"
else:
# Ensures normal photos fit 16:9 screen
vf_filter = "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black"
# --- NEW: SLOW SMOOTH MOVING WATERMARK ---
wm_text = config.get('watermark_text')
if wm_text:
safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
# sin(t/10) और cos(t/15) का मतलब है कि यह बहुत धीरे-धीरे (Slowly) पूरी स्क्रीन पर गोल घूमेगा
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)"
vf_filter += drawtext
command.extend(["-vf", vf_filter])
# -----------------------------------------------------
# Encode with strict YouTube standards (30 FPS, 2-sec Keyframe, CBR)
command.extend(["-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p"])
# YouTube Keyframe Fix: 30fps के हिसाब से हर 2 सेकंड में कीफ्रेम (-g 60)
command.extend(["-r", "30", "-g", "60", "-sc_threshold", "0"])
# YouTube Bitrate Fix: Strict CBR (Constant Bitrate) with Dummy Padding
command.extend(["-b:v", "4000k", "-maxrate", "4000k", "-minrate", "4000k", "-bufsize", "8000k", "-nal-hrd", "cbr"])
command.extend(["-c:a", "aac", "-b:a", "128k"])
else:
# Normal Video Logic
if loop_hours > 0: command.extend(["-stream_loop", "-1"])
command.extend(["-i", input_source])
current_idx = 1; idx_aud = 0
if custom_audio:
command.extend(["-stream_loop", "-1", "-i", custom_audio])
idx_aud = current_idx; current_idx += 1
idx_img = -1
if orientation == 'portrait' and image_path:
command.extend(["-loop", "1", "-i", image_path])
idx_img = current_idx; current_idx += 1
if orientation == 'portrait':
if image_path:
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]"
command.extend(["-filter_complex", vf_complex, "-map", "[outv]"])
else:
vf_filter = "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:color=black"
command.extend(["-vf", vf_filter, "-map", "0:v"])
# YouTube Strict Bitrate & Keyframe (GOP) Fix for Portrait Video
command.extend(["-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p"])
command.extend(["-r", "30", "-g", "60", "-sc_threshold", "0"])
command.extend(["-b:v", "6000k", "-maxrate", "6000k", "-minrate", "6000k", "-bufsize", "12000k", "-nal-hrd", "cbr"])
else:
# YouTube Strict Bitrate & Keyframe (GOP) Fix for Landscape Video
wm_text = config.get('watermark_text')
if wm_text:
safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
# Custom Timing Logic: 1st min, 3rd min, then every 5 mins (each for 15 sec)
timing_logic = "between(t\\,60\\,75)+between(t\\,180\\,195)+(gte(t\\,480)*lt(mod(t-480\\,300)\\,15))"
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}'"
command.extend(["-vf", vf_filter, "-map", "0:v"])
else:
command.extend(["-map", "0:v"])
command.extend(["-c:v", "libx264", "-preset", "veryfast", "-pix_fmt", "yuv420p"])
command.extend(["-r", "30", "-g", "60", "-sc_threshold", "0"])
command.extend(["-b:v", "6000k", "-maxrate", "6000k", "-minrate", "6000k", "-bufsize", "12000k", "-nal-hrd", "cbr"])
if custom_audio:
command.extend(["-map", f"{idx_aud}:a:0?", "-c:a", "aac", "-b:a", "128k", "-shortest"])
else:
command.extend(["-map", "0:a?", "-c:a", "aac", "-b:a", "128k"])
# Apply time limit for BOTH Image and Video
if loop_hours > 0:
seconds_str = str(int(actual_stream_seconds)) # असली रैंडम सेकंड्स
if not is_image_only: command.extend(["-fflags", "+genpts"])
command.extend(["-t", seconds_str])
command.extend(["-f", "flv", rtmp_url])
st_type = "🖼 Image" if is_image_only else "🎥 Video"
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")
process_obj = subprocess.Popen(command)
# Store Advanced Details for Admin Panel (Now saves actual randomized end time)
start_t = get_ist_time()
end_t = start_t + timedelta(seconds=actual_stream_seconds) if loop_hours > 0 else "Auto (Depends on Video)"
unique_p_id = str(uuid.uuid4())[:8]
user_data[user_id]['processes'].append({
'id': unique_p_id,
'process': process_obj,
'start_time': start_t,
'end_time': end_t,
'loop_hours': loop_hours,
'is_image': is_image_only,
'platform': platform
})
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))
async def monitor_stream(client, chat_id, process, file_path, msg_id, loop_hours, platform, total_time, image_path, custom_audio):
start_time_ist = get_ist_time()
last_ui_update = time.time()
mode_text = f"🔁 {loop_hours} Hours Loop" if loop_hours > 0 else "▶️ 1-Time Play"
while process.poll() is None:
await asyncio.sleep(5)
now_time = time.time()
if total_time > 0 and (now_time - last_ui_update > 60):
last_ui_update = now_time
elapsed = (get_ist_time() - start_time_ist).total_seconds()
rem_sec = total_time - elapsed
if rem_sec > 0:
h = int(rem_sec // 3600)
m = int((rem_sec % 3600) // 60)
time_left_str = f"{h}h {m}m" if h > 0 else f"{m}m"
try:
await client.edit_message_text(
chat_id, msg_id,
f"🟢 **Live is RUNNING on {platform}!**\n"
f"⏱ **Loop:** `{mode_text}`\n"
f"⏳ **Time Left:** `{time_left_str}`\n\n"
f"🔹 Send /stop to end manually."
)
except: pass
end_time_ist = get_ist_time()
duration = end_time_ist - start_time_ist
try: await client.delete_messages(chat_id, msg_id)
except: pass
# Server Storage साफ़ करना (Delete file to save memory)
try:
if file_path and os.path.exists(file_path): os.remove(file_path)
if image_path and os.path.exists(image_path): os.remove(image_path)
if custom_audio and os.path.exists(custom_audio): os.remove(custom_audio)
except: pass
start_str = start_time_ist.strftime("%I:%M %p")
end_str = end_time_ist.strftime("%I:%M %p")
dur_str = str(duration).split('.')[0]
is_manual = user_data.get(chat_id, {}).get('manual_stop', False)
status_text = "🛑 **Live Stream Stopped Manually!**" if is_manual else "🔴 **Live Stream Ended Automatically!**"
report_msg = (
f"{status_text}\n\n"
f"📊 **STREAM SUMMARY:**\n"
f"🔸 **Platform:** `{platform}`\n"
f"🟢 **Started at:** `{start_str}`\n"
f"🔴 **Ended at:** `{end_str}`\n"
f"⏱ **Total Runtime:** `{dur_str}`\n\n"
f"🗑 __Video, Audio & Image deleted safely.__"
)
await client.send_message(chat_id, report_msg)
if name == "main":
load_data() # बॉट स्टार्ट होते ही पुरानी सेटिंग्स और यूज़र्स वापस लोड करें
Thread(target=run_flask).start()
bot.run()