Update bot.py
#1
by Jitendra55566 - opened
bot.py
CHANGED
|
@@ -4,6 +4,7 @@ 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
|
|
@@ -43,6 +44,7 @@ admin_state = None
|
|
| 43 |
# --- मैसेज डिलीट करने के लिए नया डेटाबेस (टेम्परेरी) ---
|
| 44 |
sent_message_map = {}
|
| 45 |
recent_admin_messages =[]
|
|
|
|
| 46 |
|
| 47 |
# --- Data Persistence (बॉट रिस्टार्ट होने पर डेटा सेव रखने के लिए) ---
|
| 48 |
DATA_FILE = "bot_config.json"
|
|
@@ -57,7 +59,8 @@ def save_data():
|
|
| 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 |
|
|
@@ -71,6 +74,7 @@ def load_data():
|
|
| 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():
|
|
@@ -140,6 +144,7 @@ def init_user(user_id, message=None):
|
|
| 140 |
'video_url': None, # डायरेक्ट लिंक
|
| 141 |
'schedule_time': None, # शेड्यूलिंग
|
| 142 |
'processes':[],
|
|
|
|
| 143 |
'stream_limit': GLOBAL_LIMIT,
|
| 144 |
'file_path': None,
|
| 145 |
'manual_stop': False,
|
|
@@ -178,7 +183,13 @@ 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([
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
|
| 183 |
# --- Live Progress Bar ---
|
| 184 |
async def progress_bar(current, total, msg, start_time, last_update):
|
|
@@ -342,6 +353,28 @@ async def admin_callbacks(client, callback_query: CallbackQuery):
|
|
| 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.**")
|
|
@@ -471,14 +504,74 @@ 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 480 |
|
| 481 |
-
@bot.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 482 |
async def handle_all_messages(client, message):
|
| 483 |
user_id = message.chat.id
|
| 484 |
init_user(user_id, message)
|
|
@@ -595,6 +688,18 @@ async def handle_all_messages(client, message):
|
|
| 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'
|
|
@@ -723,24 +828,94 @@ async def handle_all_messages(client, message):
|
|
| 723 |
|
| 724 |
elif step == 'AWAIT_DATETIME':
|
| 725 |
try:
|
| 726 |
-
|
|
|
|
| 727 |
now_ist = get_ist_time().replace(tzinfo=None)
|
| 728 |
if dt_obj <= now_ist:
|
| 729 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 738 |
except Exception as e:
|
| 739 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 740 |
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 744 |
|
| 745 |
async def process_stream_start(client, message):
|
| 746 |
await handle_video(client, message)
|
|
@@ -754,15 +929,21 @@ def get_direct_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 |
-
|
|
|
|
|
|
|
|
|
|
| 762 |
|
| 763 |
-
file_path =
|
| 764 |
-
video_url =
|
| 765 |
-
custom_audio =
|
| 766 |
|
| 767 |
input_source = file_path
|
| 768 |
if video_url:
|
|
@@ -771,11 +952,11 @@ async def handle_video(client, message):
|
|
| 771 |
if not input_source:
|
| 772 |
return await msg.edit_text("❌ **Failed to extract link!**")
|
| 773 |
|
| 774 |
-
platform =
|
| 775 |
-
loop_hours =
|
| 776 |
-
rtmp_url =
|
| 777 |
-
orientation =
|
| 778 |
-
image_path =
|
| 779 |
|
| 780 |
user_data[user_id]['total_streams'] += 1
|
| 781 |
user_data[user_id]['last_stream'] = get_ist_time()
|
|
@@ -797,7 +978,7 @@ async def handle_video(client, message):
|
|
| 797 |
total_stream_time = vid_duration
|
| 798 |
actual_stream_seconds = vid_duration
|
| 799 |
|
| 800 |
-
is_image_only =
|
| 801 |
|
| 802 |
# --- FFMPEG Command Setup ---
|
| 803 |
command = ["ffmpeg", "-re"]
|
|
@@ -825,7 +1006,7 @@ async def handle_video(client, message):
|
|
| 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 =
|
| 829 |
if wm_text:
|
| 830 |
safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
|
| 831 |
# sin(t/10) और cos(t/15) का मतलब है कि यह बहुत धीरे-धीरे (Slowly) पूरी स्क्रीन पर गोल घूमेगा
|
|
@@ -872,7 +1053,7 @@ async def handle_video(client, message):
|
|
| 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 =
|
| 876 |
|
| 877 |
if wm_text:
|
| 878 |
safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
|
|
@@ -910,12 +1091,15 @@ async def handle_video(client, message):
|
|
| 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))
|
|
|
|
| 4 |
import json
|
| 5 |
import asyncio
|
| 6 |
import subprocess
|
| 7 |
+
import uuid
|
| 8 |
import yt_dlp
|
| 9 |
from datetime import datetime, timedelta
|
| 10 |
from threading import Thread
|
|
|
|
| 44 |
# --- मैसेज डिलीट करने के लिए नया डेटाबेस (टेम्परेरी) ---
|
| 45 |
sent_message_map = {}
|
| 46 |
recent_admin_messages =[]
|
| 47 |
+
GLOBAL_SCHEDULE_TIMES = [] # एडमिन के सेट किये गए प्री-डिफाइंड शेड्यूल्स
|
| 48 |
|
| 49 |
# --- Data Persistence (बॉट रिस्टार्ट होने पर डेटा सेव रखने के लिए) ---
|
| 50 |
DATA_FILE = "bot_config.json"
|
|
|
|
| 59 |
"PASSWORD_LIMIT": PASSWORD_LIMIT,
|
| 60 |
"GLOBAL_LIMIT": GLOBAL_LIMIT,
|
| 61 |
"PASSWORD_USED_BY": {str(k): v for k, v in PASSWORD_USED_BY.items()},
|
| 62 |
+
"authenticated_users": auth_users,
|
| 63 |
+
"GLOBAL_SCHEDULE_TIMES": GLOBAL_SCHEDULE_TIMES
|
| 64 |
}, f)
|
| 65 |
except Exception as e: pass
|
| 66 |
|
|
|
|
| 74 |
PASSWORD_LIMIT = data.get("PASSWORD_LIMIT", 999999)
|
| 75 |
GLOBAL_LIMIT = data.get("GLOBAL_LIMIT", 1)
|
| 76 |
PASSWORD_USED_BY = {int(k): v for k, v in data.get("PASSWORD_USED_BY", {}).items()}
|
| 77 |
+
GLOBAL_SCHEDULE_TIMES = data.get("GLOBAL_SCHEDULE_TIMES", [])
|
| 78 |
|
| 79 |
saved_users = data.get("authenticated_users", {})
|
| 80 |
for uid, name in saved_users.items():
|
|
|
|
| 144 |
'video_url': None, # डायरेक्ट लिंक
|
| 145 |
'schedule_time': None, # शेड्यूलिंग
|
| 146 |
'processes':[],
|
| 147 |
+
'scheduled_tasks': {}, # <--- नया: शेड्यूल्ड टास्क को ट्रैक करने के लिए
|
| 148 |
'stream_limit': GLOBAL_LIMIT,
|
| 149 |
'file_path': None,
|
| 150 |
'manual_stop': False,
|
|
|
|
| 183 |
return InlineKeyboardMarkup([[InlineKeyboardButton("▶️ Start Now", callback_data="sch_now")],[InlineKeyboardButton("⏳ Schedule Time", callback_data="sch_later")]])
|
| 184 |
|
| 185 |
def admin_keyboard():
|
| 186 |
+
return InlineKeyboardMarkup([
|
| 187 |
+
[InlineKeyboardButton("👥 Users List", callback_data="admin_users"), InlineKeyboardButton("🔴 Live Status", callback_data="admin_live")],
|
| 188 |
+
[InlineKeyboardButton("🔑 Change Password", callback_data="admin_pass"), InlineKeyboardButton("⚙️ Set Limit", callback_data="admin_limit")],
|
| 189 |
+
[InlineKeyboardButton("📅 Manage Schedule Times", callback_data="admin_sch_times")],
|
| 190 |
+
[InlineKeyboardButton("📢 Broadcast / Send Msg", callback_data="admin_msg")],
|
| 191 |
+
[InlineKeyboardButton("🗑 Delete Last 5 Broadcasts", callback_data="admin_del")]
|
| 192 |
+
])
|
| 193 |
|
| 194 |
# --- Live Progress Bar ---
|
| 195 |
async def progress_bar(current, total, msg, start_time, last_update):
|
|
|
|
| 353 |
"👉 **किसी एक को भेजने के लिए:** ID स्पेस मेसेज लिखें।\n"
|
| 354 |
"`उदाहरण: 8383838487 Hello kese ho`")
|
| 355 |
|
| 356 |
+
elif data == "admin_sch_times":
|
| 357 |
+
markup = []
|
| 358 |
+
for st in GLOBAL_SCHEDULE_TIMES:
|
| 359 |
+
markup.append([InlineKeyboardButton(f"❌ Delete {st}", callback_data=f"admindelscht_{st}")])
|
| 360 |
+
markup.append([InlineKeyboardButton("➕ Add New Time", callback_data="admin_sch_add")])
|
| 361 |
+
markup.append([InlineKeyboardButton("🔙 Back", callback_data="admin_back")])
|
| 362 |
+
await callback_query.message.edit_text("📅 **Manage Pre-defined Schedule Times:**\nये समय यूज़र्स को बटन के रूप में दिखेंगे।", reply_markup=InlineKeyboardMarkup(markup))
|
| 363 |
+
|
| 364 |
+
elif data == "admin_back":
|
| 365 |
+
await callback_query.message.edit_text("👑 **Welcome to Admin Panel**\nChoose an option below:", reply_markup=admin_keyboard())
|
| 366 |
+
|
| 367 |
+
elif data == "admin_sch_add":
|
| 368 |
+
admin_state = "AWAIT_SCH_TIME"
|
| 369 |
+
await callback_query.message.reply("📅 **कृपया नया शेड्यूल्ड समय भेजें:**\n\n**Format:** `DD/MM/YYYY hh:mm AM/PM`\n**Example:** `24/06/2026 05:50 AM`")
|
| 370 |
+
|
| 371 |
+
elif data.startswith("admindelscht_"):
|
| 372 |
+
time_to_del = data.replace("admindelscht_", "")
|
| 373 |
+
if time_to_del in GLOBAL_SCHEDULE_TIMES:
|
| 374 |
+
GLOBAL_SCHEDULE_TIMES.remove(time_to_del)
|
| 375 |
+
save_data()
|
| 376 |
+
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"))
|
| 377 |
+
|
| 378 |
elif data == "admin_del":
|
| 379 |
if not recent_admin_messages:
|
| 380 |
return await callback_query.message.reply("❌ **No recent messages/broadcasts found to delete.**")
|
|
|
|
| 504 |
user_id = callback_query.message.chat.id
|
| 505 |
if callback_query.data == "sch_now":
|
| 506 |
user_data[user_id]['step'] = 'READY'
|
|
|
|
| 507 |
await callback_query.message.delete()
|
| 508 |
await process_stream_start(client, callback_query.message)
|
| 509 |
else:
|
| 510 |
user_data[user_id]['step'] = 'AWAIT_DATETIME'
|
| 511 |
+
markup = []
|
| 512 |
+
for st in GLOBAL_SCHEDULE_TIMES:
|
| 513 |
+
# Inline button format for specific times
|
| 514 |
+
markup.append([InlineKeyboardButton(f"🗓 {st}", callback_data=f"set_schtime_{st}")])
|
| 515 |
+
|
| 516 |
+
# बटन के बिना मैन्युअल एंट्री का निर्देश
|
| 517 |
+
text = "📅 **Schedule Time चुनें या लिखें!**\n\n"
|
| 518 |
+
if GLOBAL_SCHEDULE_TIMES:
|
| 519 |
+
text += "👇 **एडमिन द्वारा दिए गए समय में से चुनें:**\n"
|
| 520 |
+
text += "\n✍️ **या अपना खुद का समय लिखकर भेजें:**\n**Format:** `DD/MM/YYYY hh:mm AM/PM`\n**Example:** `24/06/2026 05:50 AM`"
|
| 521 |
+
|
| 522 |
+
await callback_query.message.edit_text(text, reply_markup=InlineKeyboardMarkup(markup) if markup else None)
|
| 523 |
|
| 524 |
+
@bot.on_callback_query(filters.regex("^set_schtime_"))
|
| 525 |
+
async def select_predefined_time(client, callback_query: CallbackQuery):
|
| 526 |
+
user_id = callback_query.message.chat.id
|
| 527 |
+
time_str = callback_query.data.replace("set_schtime_", "")
|
| 528 |
+
|
| 529 |
+
# Create fake message to pass to AWAIT_DATETIME logic
|
| 530 |
+
class FakeMessage:
|
| 531 |
+
def __init__(self, text, chat_id):
|
| 532 |
+
self.text = text
|
| 533 |
+
self.chat = type('Chat', (), {'id': chat_id})()
|
| 534 |
+
self.id = callback_query.message.id
|
| 535 |
+
|
| 536 |
+
await callback_query.message.edit_text(f"⏳ Processing schedule for {time_str}...")
|
| 537 |
+
await handle_all_messages(client, FakeMessage(time_str, user_id))
|
| 538 |
+
|
| 539 |
+
@bot.on_message(filters.command("edit"))
|
| 540 |
+
async def edit_cmd(client, message):
|
| 541 |
+
user_id = message.chat.id
|
| 542 |
+
init_user(user_id, message)
|
| 543 |
+
|
| 544 |
+
markup = []
|
| 545 |
+
text = "🛠 **Manage Your Streams:**\nयहाँ से आप अपनी ग़लत बनी हुई या चल रही स्ट्रीम्स को Delete/Stop कर सकते हैं।\n\n"
|
| 546 |
+
|
| 547 |
+
# Running streams
|
| 548 |
+
active_procs = [p for p in user_data[user_id].get('processes', []) if p['process'].poll() is None]
|
| 549 |
+
if active_procs:
|
| 550 |
+
text += "🔴 **Running Streams:**\n"
|
| 551 |
+
for idx, p in enumerate(active_procs, 1):
|
| 552 |
+
p_id = p.get('id', 'unknown')
|
| 553 |
+
plat = p.get('platform', 'Unknown')
|
| 554 |
+
text += f"{idx}. 🟢 {plat} - Started: {p['start_time'].strftime('%I:%M %p')}\n"
|
| 555 |
+
markup.append([InlineKeyboardButton(f"🛑 Stop Running: {plat} Stream {idx}", callback_data=f"user_stop_{p_id}")])
|
| 556 |
+
|
| 557 |
+
# Scheduled streams
|
| 558 |
+
scheduled = user_data[user_id].get('scheduled_tasks', {})
|
| 559 |
+
if scheduled:
|
| 560 |
+
text += "\n⏳ **Scheduled Streams:**\n"
|
| 561 |
+
idx = 1
|
| 562 |
+
for s_id, s_info in list(scheduled.items()):
|
| 563 |
+
time_str = s_info['time_str']
|
| 564 |
+
plat = s_info['config'].get('platform', 'Unknown')
|
| 565 |
+
text += f"{idx}. 🗓 {plat} - Scheduled for: {time_str}\n"
|
| 566 |
+
markup.append([InlineKeyboardButton(f"❌ Cancel Scheduled: {plat} {time_str}", callback_data=f"user_cancelsch_{s_id}")])
|
| 567 |
+
idx += 1
|
| 568 |
+
|
| 569 |
+
if not active_procs and not scheduled:
|
| 570 |
+
text = "⚠️ **आपकी कोई भी स्ट्रीम अभी चल नहीं रही है और ना ही शेड्यूल है।**"
|
| 571 |
+
|
| 572 |
+
await message.reply(text, reply_markup=InlineKeyboardMarkup(markup) if markup else None)
|
| 573 |
+
|
| 574 |
+
@bot.on_message(~filters.command(["start", "stop", "new", "admin", "edit"]))
|
| 575 |
async def handle_all_messages(client, message):
|
| 576 |
user_id = message.chat.id
|
| 577 |
init_user(user_id, message)
|
|
|
|
| 688 |
admin_state = None
|
| 689 |
return
|
| 690 |
|
| 691 |
+
elif admin_state == "AWAIT_SCH_TIME" and message.text:
|
| 692 |
+
try:
|
| 693 |
+
# Validation
|
| 694 |
+
dt_obj = datetime.strptime(message.text.strip(), "%d/%m/%Y %I:%M %p")
|
| 695 |
+
GLOBAL_SCHEDULE_TIMES.append(message.text.strip())
|
| 696 |
+
save_data()
|
| 697 |
+
await message.reply(f"✅ **Time Added Successfully!**\n`{message.text.strip()}`\n\nअब यह समय सभी यूज़र्स को बटन में दिखाई देगा।")
|
| 698 |
+
admin_state = None
|
| 699 |
+
except Exception as e:
|
| 700 |
+
await message.reply("❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `24/06/2026 05:50 AM`")
|
| 701 |
+
return
|
| 702 |
+
|
| 703 |
# === Normal User Handling ===
|
| 704 |
if not user_data[user_id]['authenticated'] and step != 'PASSWORD':
|
| 705 |
user_data[user_id]['step'] = 'PASSWORD'
|
|
|
|
| 828 |
|
| 829 |
elif step == 'AWAIT_DATETIME':
|
| 830 |
try:
|
| 831 |
+
time_str = message.text.strip()
|
| 832 |
+
dt_obj = datetime.strptime(time_str, "%d/%m/%Y %I:%M %p")
|
| 833 |
now_ist = get_ist_time().replace(tzinfo=None)
|
| 834 |
if dt_obj <= now_ist:
|
| 835 |
+
# If pre-defined time is passed, allow it logically but warn if past
|
| 836 |
+
try:
|
| 837 |
+
await client.send_message(user_id, "⚠️ **समय (Time) भविष्य का होना चाहिए!** कृपया आगे का समय भेजें।")
|
| 838 |
+
except:
|
| 839 |
+
await message.reply("⚠️ **समय (Time) भविष्य का होना चाहिए!** कृपया आगे का समय भेजें।")
|
| 840 |
+
return
|
| 841 |
|
|
|
|
| 842 |
user_data[user_id]['step'] = 'READY'
|
|
|
|
| 843 |
delay = (dt_obj - now_ist).total_seconds()
|
|
|
|
| 844 |
|
| 845 |
+
# --- Snapshot Configuration ---
|
| 846 |
+
# (अगर यूज़र दूसरी स्ट्रीम बनायेगा तो पहली स्ट्रीम का डेटा डिलीट न हो)
|
| 847 |
+
stream_config = {
|
| 848 |
+
'file_path': user_data[user_id].get('file_path'),
|
| 849 |
+
'video_url': user_data[user_id].get('video_url'),
|
| 850 |
+
'custom_audio_path': user_data[user_id].get('custom_audio_path'),
|
| 851 |
+
'platform': user_data[user_id].get('platform', 'Custom'),
|
| 852 |
+
'loop_hours': user_data[user_id].get('loop_hours', 0),
|
| 853 |
+
'rtmp_url': user_data[user_id].get('rtmp_url'),
|
| 854 |
+
'orientation': user_data[user_id].get('orientation', 'landscape'),
|
| 855 |
+
'custom_image_path': user_data[user_id].get('custom_image_path'),
|
| 856 |
+
'is_image_only': user_data[user_id].get('is_image_only', False),
|
| 857 |
+
'watermark_text': user_data[user_id].get('watermark_text')
|
| 858 |
+
}
|
| 859 |
+
|
| 860 |
+
s_id = str(uuid.uuid4())[:8]
|
| 861 |
+
task = asyncio.create_task(scheduled_stream_starter(client, message, delay, stream_config, s_id))
|
| 862 |
+
|
| 863 |
+
user_data[user_id]['scheduled_tasks'][s_id] = {
|
| 864 |
+
'task': task,
|
| 865 |
+
'time_str': time_str,
|
| 866 |
+
'config': stream_config
|
| 867 |
+
}
|
| 868 |
+
|
| 869 |
+
try:
|
| 870 |
+
await client.send_message(user_id, f"✅ **Stream Scheduled!**\nयह वीडियो आटोमेटिकली `{time_str}` पर चालू हो जाएगा।\nअगर कोई गलती हुई हो तो `/edit` टाइप करें।")
|
| 871 |
+
except:
|
| 872 |
+
await message.reply(f"✅ **Stream Scheduled!**\nयह वीडियो आटोमेटिकली `{time_str}` पर चालू हो जाएगा।\nअगर कोई गलती हुई हो तो `/edit` टाइप करें।")
|
| 873 |
+
|
| 874 |
except Exception as e:
|
| 875 |
+
try:
|
| 876 |
+
await client.send_message(user_id, "❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `24/06/2026 05:50 AM`")
|
| 877 |
+
except:
|
| 878 |
+
await message.reply("❌ **Invalid Format!**\nकृपया इस फॉर्मेट में भेजें: `24/06/2026 05:50 AM`")
|
| 879 |
+
|
| 880 |
+
@bot.on_callback_query(filters.regex("^user_cancelsch_"))
|
| 881 |
+
async def cancel_sch_callback(client, callback_query: CallbackQuery):
|
| 882 |
+
user_id = callback_query.message.chat.id
|
| 883 |
+
s_id = callback_query.data.replace("user_cancelsch_", "")
|
| 884 |
+
if s_id in user_data[user_id].get('scheduled_tasks', {}):
|
| 885 |
+
user_data[user_id]['scheduled_tasks'][s_id]['task'].cancel()
|
| 886 |
+
del user_data[user_id]['scheduled_tasks'][s_id]
|
| 887 |
+
await callback_query.message.edit_text("✅ **Scheduled Stream Cancelled / Delete हो गई!**\nअब आप `/new` से नई बना सकते हैं।")
|
| 888 |
+
else:
|
| 889 |
+
await callback_query.message.edit_text("❌ **यह शेड्यूल पहले ही कैंसिल हो चुका है या चल चुका है।**")
|
| 890 |
|
| 891 |
+
@bot.on_callback_query(filters.regex("^user_stop_"))
|
| 892 |
+
async def stop_running_callback(client, callback_query: CallbackQuery):
|
| 893 |
+
user_id = callback_query.message.chat.id
|
| 894 |
+
p_id = callback_query.data.replace("user_stop_", "")
|
| 895 |
+
|
| 896 |
+
stopped = False
|
| 897 |
+
for p in user_data[user_id].get('processes', []):
|
| 898 |
+
if p.get('id') == p_id and p['process'].poll() is None:
|
| 899 |
+
user_data[user_id]['manual_stop'] = True
|
| 900 |
+
p['process'].terminate()
|
| 901 |
+
stopped = True
|
| 902 |
+
break
|
| 903 |
+
|
| 904 |
+
if stopped:
|
| 905 |
+
await callback_query.message.edit_text("✅ **Running Stream को सफलतापूर्वक बंद कर दिया गया है!**")
|
| 906 |
+
else:
|
| 907 |
+
await callback_query.message.edit_text("❌ **यह स्ट्रीम पहले ही बंद हो चुकी है।**")
|
| 908 |
+
|
| 909 |
+
async def scheduled_stream_starter(client, message, delay, stream_config, s_id):
|
| 910 |
+
try:
|
| 911 |
+
await asyncio.sleep(delay)
|
| 912 |
+
user_id = message.chat.id
|
| 913 |
+
# Remove from pending queue as it starts
|
| 914 |
+
if s_id in user_data[user_id].get('scheduled_tasks', {}):
|
| 915 |
+
del user_data[user_id]['scheduled_tasks'][s_id]
|
| 916 |
+
await handle_video(client, message, stream_config)
|
| 917 |
+
except asyncio.CancelledError:
|
| 918 |
+
pass # Stream was cancelled by user
|
| 919 |
|
| 920 |
async def process_stream_start(client, message):
|
| 921 |
await handle_video(client, message)
|
|
|
|
| 929 |
except:
|
| 930 |
return url
|
| 931 |
|
| 932 |
+
async def handle_video(client, message, stream_config=None):
|
| 933 |
user_id = message.chat.id
|
| 934 |
|
| 935 |
+
# अगर कॉन्फ़िग स्नैपशॉट (शेड्यूल से) आया है, तो उसका इस्तेमाल करें। नहीं तो करंट डेटा का।
|
| 936 |
+
config = stream_config if stream_config else user_data[user_id]
|
| 937 |
+
|
| 938 |
user_data[user_id]['manual_stop'] = False
|
| 939 |
+
try:
|
| 940 |
+
msg = await client.send_message(user_id, "⏳ **Preparing Stream Environment...**")
|
| 941 |
+
except:
|
| 942 |
+
msg = await message.reply("⏳ **Preparing Stream Environment...**")
|
| 943 |
|
| 944 |
+
file_path = config.get('file_path')
|
| 945 |
+
video_url = config.get('video_url')
|
| 946 |
+
custom_audio = config.get('custom_audio_path')
|
| 947 |
|
| 948 |
input_source = file_path
|
| 949 |
if video_url:
|
|
|
|
| 952 |
if not input_source:
|
| 953 |
return await msg.edit_text("❌ **Failed to extract link!**")
|
| 954 |
|
| 955 |
+
platform = config.get('platform', 'Custom')
|
| 956 |
+
loop_hours = config.get('loop_hours', 0)
|
| 957 |
+
rtmp_url = config.get('rtmp_url')
|
| 958 |
+
orientation = config.get('orientation', 'landscape')
|
| 959 |
+
image_path = config.get('custom_image_path')
|
| 960 |
|
| 961 |
user_data[user_id]['total_streams'] += 1
|
| 962 |
user_data[user_id]['last_stream'] = get_ist_time()
|
|
|
|
| 978 |
total_stream_time = vid_duration
|
| 979 |
actual_stream_seconds = vid_duration
|
| 980 |
|
| 981 |
+
is_image_only = config.get('is_image_only', False)
|
| 982 |
|
| 983 |
# --- FFMPEG Command Setup ---
|
| 984 |
command = ["ffmpeg", "-re"]
|
|
|
|
| 1006 |
vf_filter = "scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=black"
|
| 1007 |
|
| 1008 |
# --- NEW: SLOW SMOOTH MOVING WATERMARK ---
|
| 1009 |
+
wm_text = config.get('watermark_text')
|
| 1010 |
if wm_text:
|
| 1011 |
safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
|
| 1012 |
# sin(t/10) और cos(t/15) का मतलब है कि यह बहुत धीरे-धीरे (Slowly) पूरी स्क्रीन पर गोल घूमेगा
|
|
|
|
| 1053 |
command.extend(["-b:v", "6000k", "-maxrate", "6000k", "-minrate", "6000k", "-bufsize", "12000k", "-nal-hrd", "cbr"])
|
| 1054 |
else:
|
| 1055 |
# YouTube Strict Bitrate & Keyframe (GOP) Fix for Landscape Video
|
| 1056 |
+
wm_text = config.get('watermark_text')
|
| 1057 |
|
| 1058 |
if wm_text:
|
| 1059 |
safe_text = wm_text.replace("'", "\\'").replace(":", "\\:")
|
|
|
|
| 1091 |
start_t = get_ist_time()
|
| 1092 |
end_t = start_t + timedelta(seconds=actual_stream_seconds) if loop_hours > 0 else "Auto (Depends on Video)"
|
| 1093 |
|
| 1094 |
+
unique_p_id = str(uuid.uuid4())[:8]
|
| 1095 |
user_data[user_id]['processes'].append({
|
| 1096 |
+
'id': unique_p_id,
|
| 1097 |
'process': process_obj,
|
| 1098 |
'start_time': start_t,
|
| 1099 |
'end_time': end_t,
|
| 1100 |
'loop_hours': loop_hours,
|
| 1101 |
+
'is_image': is_image_only,
|
| 1102 |
+
'platform': platform
|
| 1103 |
})
|
| 1104 |
|
| 1105 |
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))
|