import asyncio import datetime def get_all_shorteners(admin_settings): import json shorteners = [] if hasattr(admin_settings, "shorteners_list") and admin_settings.shorteners_list: try: shorteners = json.loads(admin_settings.shorteners_list) except Exception: pass if not shorteners and getattr(admin_settings, "shortener_api", None): default_site = admin_settings.shortener_site or "gplinks.in" default_api = admin_settings.shortener_api default_how = admin_settings.how_to_download or "https://telegram.me/FIREBOLTTutorial/23" default_status = getattr(admin_settings, "shortener_status", True) if default_status is None: default_status = True shorteners = [{ "site": default_site, "api": default_api, "how_to_download": default_how, "status": default_status }] return shorteners def parse_duration_to_seconds(val: str): if not val: return None val = val.strip().lower() if val == "off": return 0 if val.endswith("d"): try: return int(float(val[:-1]) * 86400) except ValueError: return None elif val.endswith("h"): try: return int(float(val[:-1]) * 3600) except ValueError: return None elif val.endswith("m"): try: return int(float(val[:-1]) * 60) except ValueError: return None elif val.endswith("s"): try: return int(float(val[:-1])) except ValueError: return None else: try: return int(val) except ValueError: return None import shlex from pyrogram import Client, filters from mfinder.db.settings_sql import ( get_admin_settings, set_repair_mode, set_auto_delete, set_custom_caption, set_force_sub, set_channel_link, get_link, set_username, set_log_channel, get_log_channel, set_how_to_download ) from mfinder.db.ban_sql import is_banned, ban_user, unban_user from mfinder.db.filters_sql import add_filter, rem_filter, list_filters from mfinder.db.files_sql import count_files from mfinder import ADMINS, DB_CHANNELS @Client.on_message(filters.command(["autodelete"]) & filters.user(ADMINS)) async def auto_delete_(bot, update): data = update.text.split() if len(data) == 2: dur_str = data[-1] dur = parse_duration_to_seconds(dur_str) if dur is None or dur < 0: await update.reply_text("āŒ **Invalid format!** Please use e.g. `2m` (2 minutes), `5h` (5 hours), `3600` (seconds), or `off`.") return await set_auto_delete(dur) if dur: if dur >= 86400: desc = f"{dur / 86400:.1f} days" if (dur / 86400) % 1 != 0 else f"{int(dur / 86400)} days" elif dur >= 3600: desc = f"{dur / 3600:.1f} hours" if (dur / 3600) % 1 != 0 else f"{int(dur / 3600)} hours" elif dur >= 60: desc = f"{dur / 60:.1f} minutes" if (dur / 60) % 1 != 0 else f"{int(dur / 60)} minutes" else: desc = f"{dur} seconds" await update.reply_text(f"āœ… File auto delete set to `{dur}` seconds ({desc}).") else: await update.reply_text("āœ… File auto delete disabled.") else: await update.reply_text("Please send in proper format `/autodelete ` (e.g. `/autodelete 2m`, `/autodelete 5h` or `/autodelete off`)") @Client.on_message(filters.command(["repairmode"]) & filters.user(ADMINS)) async def repair_mode_(bot, update): data = update.text.split() if len(data) == 2: toggle = data[-1] if toggle.lower() == "off": mode = False elif toggle.lower() == "on": mode = True else: await update.reply_text( "Please send in proper format `/repairmode `" ) return await set_repair_mode(mode) await update.reply_text(f"Repair mode set to `{toggle.upper()}`") else: await update.reply_text("Please send in proper format `/repairmode on/off`") return @Client.on_message(filters.command(["customcaption"]) & filters.user(ADMINS)) async def custom_caption_(bot, update): data = update.text.split() caption = " ".join(data[1:]) if len(data) >= 2: if caption.lower() == "off": caption = None await set_custom_caption(caption) if caption: await update.reply_text(f"Custom caption set to `{caption}`") else: await update.reply_text("Custom caption disabled") else: await update.reply_text( "Please send in proper format `/customcaption caption/off`" ) return ADMIN_INPUT_STATE = {} async def get_settings_screen(cat: str): from mfinder.db.settings_sql import get_admin_settings from mfinder import ADMINS, DB_CHANNELS admin_settings = await get_admin_settings() auto_delete = admin_settings.auto_delete custom_caption = admin_settings.custom_caption fsub_channel = admin_settings.fsub_channel fsub_channels_list = admin_settings.fsub_channels_list caption_uname = admin_settings.caption_uname invite_link = admin_settings.channel_link repair_mode = admin_settings.repair_mode shortener_site = admin_settings.shortener_site shortener_api = admin_settings.shortener_api shortener_status = admin_settings.shortener_status if shortener_status is None: shortener_status = True token_shortener_enabled = admin_settings.token_shortener_enabled if token_shortener_enabled is None: token_shortener_enabled = False token_timeout = admin_settings.token_timeout if token_timeout is None: token_timeout = 3600 log_channel = admin_settings.log_channel from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton if cat == "main": text = "šŸ› ļø **Admin Settings**\n\nSelect a category to configure:" keyboard = InlineKeyboardMarkup([ [ InlineKeyboardButton("āš™ļø General Settings", callback_data="adm_cat:gen"), InlineKeyboardButton("šŸ”— Shortener Settings", callback_data="adm_cat:sho") ], [ InlineKeyboardButton("šŸ“¢ Channel Settings", callback_data="adm_cat:cha"), InlineKeyboardButton("šŸ“ Caption Settings", callback_data="adm_cat:cap") ], [ InlineKeyboardButton("šŸ“° Newsletter Settings", callback_data="adm_cat:newsl"), InlineKeyboardButton("āŒ Close Menu", callback_data="adm_close") ] ]) return text, keyboard elif cat == "gen": repair_status_label = "🟢 Enabled" if repair_mode else "šŸ”“ Disabled" repair_btn_label = "šŸ”“ Disable Repair Mode" if repair_mode else "🟢 Enable Repair Mode" if auto_delete: parsed_auto_del = int(auto_delete) if parsed_auto_del >= 86400: days = parsed_auto_del / 86400 auto_del_label = f"{days:.1f} days" if days % 1 != 0 else f"{int(days)} days" elif parsed_auto_del >= 3600: hours = parsed_auto_del / 3600 auto_del_label = f"{hours:.1f} hours" if hours % 1 != 0 else f"{int(hours)} hours" elif parsed_auto_del >= 60: mins = parsed_auto_del / 60 auto_del_label = f"{mins:.1f} minutes" if mins % 1 != 0 else f"{int(mins)} minutes" else: auto_del_label = f"{parsed_auto_del} seconds" else: auto_del_label = "Disabled" text = ( "āš™ļø **General Settings**\n\n" f"šŸ› ļø **Repair Mode:** `{repair_status_label}`\n" "↳ When enabled, the bot ignores all incoming search queries.\n\n" f"ā±ļø **Auto Delete:** `{auto_del_label}`\n" "↳ Duration before sent files are deleted from the chat." ) keyboard = InlineKeyboardMarkup([ [InlineKeyboardButton(repair_btn_label, callback_data="adm_toggle:repair")], [InlineKeyboardButton("ā±ļø Set Auto Delete Duration", callback_data="adm_ask:auto_delete")], [InlineKeyboardButton("šŸ”™ Back to Main Settings", callback_data="adm_cat:main")] ]) return text, keyboard elif cat == "sho": status_label = "🟢 ON" if shortener_status else "šŸ”“ OFF" toggle_label = "šŸ”“ Turn OFF Shortener" if shortener_status else "🟢 Turn ON Shortener" smart_label = "🟢 ON" if getattr(admin_settings, "smart_rotator", False) else "šŸ”“ OFF" smart_toggle_btn = "šŸ”“ Turn OFF Smart Rotator" if getattr(admin_settings, "smart_rotator", False) else "🟢 Turn ON Smart Rotator" token_status_label = "🟢 ON" if token_shortener_enabled else "šŸ”“ OFF" token_toggle_label = "šŸ”“ Turn OFF Token Shortener" if token_shortener_enabled else "🟢 Turn ON Token Shortener" parsed_seconds = int(token_timeout) if parsed_seconds >= 3600: hours = parsed_seconds / 3600 time_desc = f"{hours:.1f} hours" if hours % 1 != 0 else f"{int(hours)} hours" elif parsed_seconds >= 60: mins = parsed_seconds / 60 time_desc = f"{mins:.1f} minutes" if mins % 1 != 0 else f"{int(mins)} minutes" else: time_desc = f"{parsed_seconds} seconds" text = ( "šŸ”— **Shortener Settings**\n\n" f"šŸ“„ **Standard Shortener:** `{status_label}`\n" "↳ If ON, standard file-level shortening is applied.\n\n" f"šŸ”„ **Smart Rotator:** `{smart_label}`\n" "↳ Rotates active shorteners per user per day to maximize revenue.\n\n" f"šŸŽŸļø **Token Shortener:** `{token_status_label}`\n" f"ā±ļø **Token Timeout:** `{time_desc}`\n" "↳ If ON, users complete ONE link for direct access for the duration above." ) keyboard = InlineKeyboardMarkup([ [InlineKeyboardButton(toggle_label, callback_data="adm_toggle:shortener")], [InlineKeyboardButton(smart_toggle_btn, callback_data="adm_rot_toggle_smart")], [InlineKeyboardButton(token_toggle_label, callback_data="adm_toggle:token_shortener")], [InlineKeyboardButton("ā±ļø Set Token Timeout", callback_data="adm_ask:token_timeout")], [InlineKeyboardButton("šŸ“‹ Shortener List", callback_data="adm_cat:rot")], [InlineKeyboardButton("šŸ”™ Back to Main Settings", callback_data="adm_cat:main")] ]) return text, keyboard elif cat == "cha": admins_str = "\n".join(f"• `{adm}`" for adm in ADMINS) db_channels_str = "\n".join(f"• `{ch}`" for ch in DB_CHANNELS) if DB_CHANNELS else "• None" fsub_label = fsub_channels_list if fsub_channels_list else (fsub_channel if fsub_channel else "Disabled") link_label = invite_link if invite_link else "Disabled" log_label = log_channel if log_channel else "Disabled" ann_enabled = bool(getattr(admin_settings, "announcement_enabled", False)) ann_status_label = "🟢 Enabled" if ann_enabled else "šŸ”“ Disabled" ann_channel = getattr(admin_settings, "announcement_channel", None) ann_channel_label = f"`{ann_channel}`" if ann_channel else "`Disabled`" text = ( "šŸ“¢ **Channel Settings**\n\n" f"šŸ‘¤ **Admins**:\n{admins_str}\n\n" f"šŸ“¦ **Database Channels**:\n{db_channels_str}\n\n" f"• **Force Sub Channel**: `{fsub_label}`\n" f"• **Channel Link**: `{link_label}`\n" f"• **User Logs Channel**: `{log_label}`\n" f"• **Upload Announcements**: {ann_status_label}\n" f"• **Announcement Channel**: {ann_channel_label}" ) keyboard = InlineKeyboardMarkup([ [ InlineKeyboardButton("šŸ‘¤ Set Admins List", callback_data="adm_ask:admins"), InlineKeyboardButton("šŸ“¦ Set DB Channels", callback_data="adm_ask:db_channels") ], [ InlineKeyboardButton("⚔ Set Force Sub ID", callback_data="adm_ask:force_sub"), InlineKeyboardButton("šŸ”— Set Channel Link", callback_data="adm_ask:channel_link") ], [ InlineKeyboardButton("šŸ“Š Set Log Channel ID", callback_data="adm_ask:log_channel") ], [ InlineKeyboardButton("šŸ“¢ Toggle Announcements", callback_data="adm_toggle:announcement_enabled"), InlineKeyboardButton("šŸ“¢ Set Ann Channel ID", callback_data="adm_ask:announcement_channel") ], [InlineKeyboardButton("šŸ”™ Back to Main Settings", callback_data="adm_cat:main")] ]) return text, keyboard elif cat == "cap": caption_label = custom_caption if custom_caption else "Disabled" uname_label = caption_uname if caption_uname else "Disabled" text = ( "šŸ“ **Caption Settings**\n\n" f"• **Custom Caption**:\n`{caption_label}`\n\n" f"• **Caption Username**: `{uname_label}`" ) keyboard = InlineKeyboardMarkup([ [InlineKeyboardButton("āœļø Set Custom Caption", callback_data="adm_ask:custom_caption")], [InlineKeyboardButton("šŸ·ļø Set Caption Username", callback_data="adm_ask:caption_uname")], [InlineKeyboardButton("šŸ”™ Back to Main Settings", callback_data="adm_cat:main")] ]) return text, keyboard elif cat == "newsl": ns_enabled = bool(getattr(admin_settings, "newsletter_enabled", False)) ns_status_label = "🟢 Enabled" if ns_enabled else "šŸ”“ Disabled" ns_toggle_label = "šŸ”“ Disable Newsletter" if ns_enabled else "🟢 Enable Newsletter" ns_days = getattr(admin_settings, "newsletter_days", "Friday") ns_time = getattr(admin_settings, "newsletter_time", "20:00") ns_count = getattr(admin_settings, "newsletter_count", 10) ns_target = getattr(admin_settings, "newsletter_target", "subscribed") target_label = "šŸ‘„ All Users" if ns_target == "all" else "šŸ”” Subscribed Users Only" target_toggle_label = "šŸ”” Set Target to Subscribed" if ns_target == "all" else "šŸ‘„ Set Target to All Users" text = ( "šŸ“° **Newsletter Settings**\n\n" f"šŸ”„ **Newsletter Status:** `{ns_status_label}`\n" "↳ Toggle automated weekly broadcast feature.\n\n" f"šŸ“… **Send Weekdays:** `{ns_days}`\n" "↳ Weekday(s) to broadcast the newsletter (comma separated).\n\n" f"šŸ•’ **Send Time (24h):** `{ns_time}`\n" "↳ Time of day to broadcast the newsletter.\n\n" f"šŸæ **Max Movies Count:** `{ns_count}`\n" "↳ Max newly uploaded movies to include in newsletter.\n\n" f"šŸŽÆ **Target Audience:** `{target_label}`\n" "↳ Who will receive the auto-broadcast." ) keyboard = InlineKeyboardMarkup([ [InlineKeyboardButton(ns_toggle_label, callback_data="adm_toggle:newsletter_enabled")], [InlineKeyboardButton(target_toggle_label, callback_data="adm_toggle:newsletter_target")], [ InlineKeyboardButton("šŸ“… Set Send Days", callback_data="adm_ask:newsletter_days"), InlineKeyboardButton("šŸ•’ Set Send Time", callback_data="adm_ask:newsletter_time") ], [ InlineKeyboardButton("šŸæ Set Max Movie Count", callback_data="adm_ask:newsletter_count") ], [InlineKeyboardButton("šŸ”™ Back to Main Settings", callback_data="adm_cat:main")] ]) return text, keyboard elif cat == "rot": shorteners = get_all_shorteners(admin_settings) # Sync it back to database if list is migrated in memory if hasattr(admin_settings, "shorteners_list") and not admin_settings.shorteners_list: from mfinder.db.settings_sql import update_shorteners_list asyncio.create_task(update_shorteners_list(json.dumps(shorteners))) text = ( "šŸ“‹ **Shortener List Settings**\n\n" "šŸ“‚ **Configured Shorteners List (Priority Order):**\n\n" ) for i, sh in enumerate(shorteners): status_icon = "🟢 ON" if sh.get("status", True) else "šŸ”“ OFF" site_url = sh.get("site") api_key = sh.get("api") how_to = sh.get("how_to_download") or "https://telegram.me/FIREBOLTTutorial/23" api_masked = api_key[:4] + "*"*(len(api_key)-4) if len(api_key) > 4 else "Set" text += f"**{i+1}.** `{site_url}`\n↳ Key: `{api_masked}`\n↳ Tutorial: `{how_to}`\n↳ Status: `{status_icon}`\n\n" keyboard_buttons = [] for i, sh in enumerate(shorteners): site_url = sh.get("site") keyboard_buttons.append([ InlineKeyboardButton(f"āš™ļø {i+1}. {site_url}", callback_data=f"adm_rot_manage:{i}"), ]) keyboard_buttons.append([InlineKeyboardButton("āž• Add New Shortener", callback_data="adm_rot_add")]) if len(shorteners) > 0: keyboard_buttons.append([InlineKeyboardButton("šŸ—‘ļø Clear All Rotators", callback_data="adm_rot_clear")]) keyboard_buttons.append([InlineKeyboardButton("šŸ”™ Back to Shortener Settings", callback_data="adm_cat:sho")]) keyboard = InlineKeyboardMarkup(keyboard_buttons) return text, keyboard elif cat.startswith("rot_manage:"): idx = int(cat.split(":")[1]) shorteners = get_all_shorteners(admin_settings) if idx >= len(shorteners): return "āš ļø Shortener not found.", InlineKeyboardMarkup([[InlineKeyboardButton("šŸ”™ Back", callback_data="adm_cat:rot")]]) sh = shorteners[idx] site_url = sh.get("site") status_label = "🟢 ON" if sh.get("status", True) else "šŸ”“ OFF" toggle_label = "šŸ”“ Turn OFF" if sh.get("status", True) else "🟢 Turn ON" text = ( f"āš™ļø **Manage Shortener #{idx+1}:** `{site_url}`\n\n" f"šŸ”˜ **Status:** `{status_label}`\n" f"šŸ”‘ **API Key:** `{sh.get('api')}`\n" f"šŸ“– **Download Tutorial:** `{sh.get('how_to_download', 'Not set')}`" ) keyboard = InlineKeyboardMarkup([ [InlineKeyboardButton(toggle_label, callback_data=f"adm_rot_toggle:{idx}")], [InlineKeyboardButton("āœļø Edit Shortener", callback_data=f"adm_rot_edit:{idx}")], [InlineKeyboardButton("šŸ—‘ļø Delete Shortener", callback_data=f"adm_rot_delete:{idx}")], [InlineKeyboardButton("šŸ”™ Back to List", callback_data="adm_cat:rot")] ]) return text, keyboard @Client.on_message(filters.command(["adminsettings"]) & filters.user(ADMINS)) async def admin_settings_(bot, update): text, keyboard = await get_settings_screen("main") await update.reply_text( text=text, reply_markup=keyboard, quote=True ) @Client.on_callback_query(filters.regex(r"^adm_cat:(main|gen|sho|cha|cap|rot|newsl)$") & filters.user(ADMINS)) async def admin_category_callback(bot, query): cat = query.data.split(":")[1] ADMIN_INPUT_STATE.pop(query.from_user.id, None) text, keyboard = await get_settings_screen(cat) try: await query.message.edit_text( text=text, reply_markup=keyboard ) except Exception: pass await query.answer() @Client.on_callback_query(filters.regex(r"^adm_toggle:(repair|shortener|token_shortener|newsletter_enabled|newsletter_target|announcement_enabled)$") & filters.user(ADMINS)) async def admin_toggle_callback(bot, query): setting = query.data.split(":")[1] from mfinder.db.settings_sql import ( get_admin_settings, set_repair_mode, set_shortener_settings, set_token_shortener_state ) admin_settings = await get_admin_settings() if setting == "repair": new_val = not admin_settings.repair_mode await set_repair_mode(new_val) await query.answer(f"Repair mode {'Enabled' if new_val else 'Disabled'}") text, keyboard = await get_settings_screen("gen") elif setting == "shortener": current_status = admin_settings.shortener_status if current_status is None: current_status = True new_val = not current_status await set_shortener_settings(status=new_val) from mfinder.utils.helpers import clear_short_url_cache clear_short_url_cache() try: from mfinder.db.settings_sql import clear_all_user_shortener_usages await clear_all_user_shortener_usages() except Exception: pass if new_val: await set_token_shortener_state(False) await query.answer(f"Shortener {'ON' if new_val else 'OFF'}") text, keyboard = await get_settings_screen("sho") elif setting == "token_shortener": current_status = admin_settings.token_shortener_enabled if current_status is None: current_status = False new_val = not current_status await set_token_shortener_state(new_val) try: from mfinder.db.settings_sql import clear_all_user_shortener_usages await clear_all_user_shortener_usages() except Exception: pass if new_val: await set_shortener_settings(status=False) await query.answer(f"Token shortener {'Enabled' if new_val else 'Disabled'}") text, keyboard = await get_settings_screen("sho") elif setting == "newsletter_enabled": current_status = bool(getattr(admin_settings, "newsletter_enabled", False)) new_val = not current_status from mfinder.db.settings_sql import set_newsletter_settings await set_newsletter_settings(enabled=new_val) await query.answer(f"Newsletter {'Enabled' if new_val else 'Disabled'}") text, keyboard = await get_settings_screen("newsl") elif setting == "newsletter_target": current_target = getattr(admin_settings, "newsletter_target", "subscribed") new_target = "all" if current_target == "subscribed" else "subscribed" from mfinder.db.settings_sql import set_newsletter_settings await set_newsletter_settings(target=new_target) await query.answer(f"Target audience set to {'All Users' if new_target == 'all' else 'Subscribed Users'}") text, keyboard = await get_settings_screen("newsl") elif setting == "announcement_enabled": current_status = bool(getattr(admin_settings, "announcement_enabled", False)) new_val = not current_status from mfinder.db.settings_sql import set_announcement_settings await set_announcement_settings(enabled=new_val) await query.answer(f"Upload Announcements {'Enabled' if new_val else 'Disabled'}") text, keyboard = await get_settings_screen("cha") else: await query.answer("Unknown toggle.") return try: await query.message.edit_text( text=text, reply_markup=keyboard ) except Exception: pass @Client.on_callback_query(filters.regex(r"^adm_del_shortener$") & filters.user(ADMINS)) async def admin_del_shortener_callback(bot, query): from mfinder.db.settings_sql import delete_shortener_settings await delete_shortener_settings() await query.answer("Shortener settings cleared.") text, keyboard = await get_settings_screen("sho") try: await query.message.edit_text( text=text, reply_markup=keyboard ) except Exception: pass @Client.on_callback_query(filters.regex(r"^adm_close$") & filters.user(ADMINS)) async def admin_close_callback(bot, query): ADMIN_INPUT_STATE.pop(query.from_user.id, None) try: await query.message.delete() except Exception: pass await query.answer("Settings menu closed.") @Client.on_callback_query(filters.regex(r"^adm_ask:(\w+)$") & filters.user(ADMINS)) async def admin_ask_callback(bot, query): action = query.data.split(":")[1] user_id = query.from_user.id import time ADMIN_INPUT_STATE[user_id] = {"action": f"set_{action}", "message_id": query.message.id, "timestamp": time.time()} prompt_map = { "add_rotator_shortener": "Send the new shortener site and API key separated by a space (e.g. gplinks.in API_KEY):", "auto_delete": "Send the new Auto Delete duration in seconds (or 'off' to disable):", "custom_caption": "Send the new Custom Caption text (or 'off' to disable):", "caption_uname": "Send the new Caption Username starting with '@' (or 'off' to disable):", "shortener_site": "Send the new Site name (e.g. shortxlinks.com):", "shortener_api": "Send the new Shortener API Key:", "token_timeout": "Send the new Token validity time (e.g., '1h' for 1 hour, '30m' for 30 mins, or raw seconds):", "force_sub": "Send the new Force Subscription Channel ID (or 'off' to disable):", "channel_link": "Send the new Force Subscription Channel Link (or 'off' to disable):", "admins": "Send a comma-separated list of Admin User IDs (e.g. 12345, 67890):", "db_channels": "Send a comma-separated list of DB Channel IDs (e.g. -100123, -100456):", "log_channel": "Send the new User Search Logs Channel ID or username (or 'off' to disable):", "how_to_download": "Send the new How to Download/Verify URL (or 'off' to disable):", "announcement_channel": "Send the new Upload Announcements Channel ID (e.g. -100123456) or 'off' to disable:", "newsletter_days": "Send the days of week separated by comma (e.g. Friday,Sunday):", "newsletter_time": "Send the new Send Time in 24-hour HH:MM format (e.g. 20:00):", "newsletter_count": "Send the maximum number of new uploads to include in the newsletter (e.g. 10):" } prompt = "Send the new details (format: `domain api_key tutorial_url`):" if action.startswith("edit_rotator_shortener") or action == "add_rotator_shortener" else prompt_map.get(action, "Send the new parameter value:") from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton category_map = { "add_rotator_shortener": "rot", "auto_delete": "gen", "custom_caption": "cap", "caption_uname": "cap", "shortener_site": "sho", "shortener_api": "sho", "token_timeout": "sho", "force_sub": "cha", "channel_link": "cha", "admins": "cha", "db_channels": "cha", "log_channel": "cha", "announcement_channel": "cha", "how_to_download": "gen", "newsletter_days": "newsl", "newsletter_time": "newsl", "newsletter_count": "newsl" } cat = "rot" if action.startswith("edit_rotator_shortener") else category_map.get(action, "main") keyboard = InlineKeyboardMarkup([[InlineKeyboardButton("āŒ Cancel Configuration", callback_data=f"adm_cat:{cat}")]]) await query.message.edit_text(text=f"āŒØļø **{prompt}**", reply_markup=keyboard) await query.answer() # Schedule the auto-cancel background task async def cancel_after_timeout(b, u_id, msg_id, cat_name): await asyncio.sleep(90) # 1.30 minutes state = ADMIN_INPUT_STATE.get(u_id) if state and state.get("message_id") == msg_id: import time elapsed = time.time() - state.get("timestamp", 0) if elapsed >= 90: ADMIN_INPUT_STATE.pop(u_id, None) try: text, keyboard = await get_settings_screen(cat_name) alert_text = "āš ļø **Operation cancelled due to timeout (1.30 min)!**\n\n" + text await b.edit_message_text( chat_id=u_id, message_id=msg_id, text=alert_text, reply_markup=keyboard ) except Exception: pass else: asyncio.create_task(cancel_after_timeout_loop(b, u_id, msg_id, cat_name)) async def cancel_after_timeout_loop(b, u_id, msg_id, cat_name): state = ADMIN_INPUT_STATE.get(u_id) if state and state.get("message_id") == msg_id: import time elapsed = time.time() - state.get("timestamp", 0) remaining = 90 - elapsed if remaining > 0: await asyncio.sleep(remaining) state = ADMIN_INPUT_STATE.get(u_id) if state and state.get("message_id") == msg_id: elapsed = time.time() - state.get("timestamp", 0) if elapsed >= 90: ADMIN_INPUT_STATE.pop(u_id, None) try: text, keyboard = await get_settings_screen(cat_name) alert_text = "āš ļø **Operation cancelled due to timeout (1.30 min)!**\n\n" + text await b.edit_message_text( chat_id=u_id, message_id=msg_id, text=alert_text, reply_markup=keyboard ) except Exception: pass asyncio.create_task(cancel_after_timeout(bot, user_id, query.message.id, cat)) async def handle_admin_input(bot, message): user_id = message.from_user.id state = ADMIN_INPUT_STATE.get(user_id) if not state: return action = state.get("action") menu_message_id = state.get("message_id") input_text = message.text.strip() try: await message.delete() except Exception: pass from mfinder.db.settings_sql import ( set_auto_delete, set_custom_caption, set_force_sub, set_username, set_channel_link, set_shortener_settings, update_admins_list, update_db_channels_list, get_link, set_token_timeout, set_log_channel, set_how_to_download ) from mfinder import ADMINS, DB_CHANNELS success = True err_msg = "" success_msg = "" if action == "add_rotator_shortener": parts = input_text.split(None, 2) if len(parts) != 3: success = False err_msg = "Please send domain name, API key, and How to Download URL separated by spaces (e.g. `gplinks.in API_KEY TUTORIAL_URL`)." else: site = parts[0].strip() api = parts[1].strip() how_to_download = parts[2].strip() if " " in site or "." not in site: success = False err_msg = "Invalid domain name. It should not contain spaces and must contain at least one dot (e.g. gplinks.in)." elif not how_to_download.startswith("http://") and not how_to_download.startswith("https://") and not how_to_download.startswith("t.me/") and not how_to_download.startswith("telegram.me/"): success = False err_msg = "Invalid How to Download URL. It must start with http://, https://, t.me/, or telegram.me/." else: import json admin_settings = await get_admin_settings() shorteners = [] if hasattr(admin_settings, "shorteners_list") and admin_settings.shorteners_list: try: shorteners = json.loads(admin_settings.shorteners_list) except Exception: pass # Check if already exists exists = False for sh in shorteners: if sh.get("site").lower() == site.lower(): sh["api"] = api sh["how_to_download"] = how_to_download sh["status"] = True exists = True break if not exists: shorteners.append({"site": site, "api": api, "how_to_download": how_to_download, "status": True}) from mfinder.db.settings_sql import update_shorteners_list await update_shorteners_list(json.dumps(shorteners)) try: from mfinder.db.settings_sql import clear_all_user_shortener_usages await clear_all_user_shortener_usages() except Exception: pass from mfinder.utils.helpers import clear_short_url_cache clear_short_url_cache() success_msg = f"Added shortener `{site}` to the rotator list." elif action.startswith("edit_rotator_shortener:"): idx = int(action.split(":")[1]) parts = input_text.split(None, 2) if len(parts) != 3: success = False err_msg = "Please send domain name, API key, and How to Download URL separated by spaces (e.g. `gplinks.in API_KEY TUTORIAL_URL`)." else: site = parts[0].strip() api = parts[1].strip() how_to_download = parts[2].strip() if " " in site or "." not in site: success = False err_msg = "Invalid domain name. It should not contain spaces and must contain at least one dot (e.g. gplinks.in)." elif not how_to_download.startswith("http://") and not how_to_download.startswith("https://") and not how_to_download.startswith("t.me/") and not how_to_download.startswith("telegram.me/"): success = False err_msg = "Invalid How to Download URL. It must start with http://, https://, t.me/, or telegram.me/." else: import json admin_settings = await get_admin_settings() shorteners = get_all_shorteners(admin_settings) if idx < len(shorteners): shorteners[idx] = { "site": site, "api": api, "how_to_download": how_to_download, "status": shorteners[idx].get("status", True) } from mfinder.db.settings_sql import update_shorteners_list await update_shorteners_list(json.dumps(shorteners)) try: from mfinder.db.settings_sql import clear_all_user_shortener_usages await clear_all_user_shortener_usages() except Exception: pass from mfinder.utils.helpers import clear_short_url_cache clear_short_url_cache() success_msg = f"Updated shortener #{idx+1} (`{site}`) successfully." else: success = False err_msg = "Shortener not found." elif action == "set_auto_delete": dur = parse_duration_to_seconds(input_text) if dur is None or dur < 0: success = False err_msg = "Invalid format. Use e.g. '1h' (1 hour), '2m' (2 minutes), '30s' (30 seconds), or 'off'." else: await set_auto_delete(dur) if dur: if dur >= 86400: desc = f"{dur / 86400:.1f} days" if (dur / 86400) % 1 != 0 else f"{int(dur / 86400)} days" elif dur >= 3600: desc = f"{dur / 3600:.1f} hours" if (dur / 3600) % 1 != 0 else f"{int(dur / 3600)} hours" elif dur >= 60: desc = f"{dur / 60:.1f} minutes" if (dur / 60) % 1 != 0 else f"{int(dur / 60)} minutes" else: desc = f"{dur} seconds" success_msg = f"Auto delete duration set to `{dur}` seconds ({desc})." else: success_msg = "Auto delete duration has been disabled." elif action == "set_custom_caption": if input_text.lower() in ("off", "disable", "disabled"): caption = None success_msg = "Custom caption has been disabled." else: caption = input_text success_msg = "Custom caption updated successfully." await set_custom_caption(caption) elif action == "set_caption_uname": if input_text.lower() in ("off", "disable", "disabled"): uname = None success_msg = "Caption username has been disabled." await set_username(uname) elif input_text.startswith("@"): uname = input_text success_msg = f"Caption username set to `{uname}`." await set_username(uname) else: success = False err_msg = "Must start with '@' (e.g. @mychannel) or 'off' to disable." elif action == "set_shortener_site": if input_text.lower() in ("off", "disable", "disabled"): site = None success_msg = "Shortener site has been cleared/disabled." await set_shortener_settings(site=site) elif " " in input_text or "." not in input_text: success = False err_msg = "Invalid domain name. It should not contain spaces and must contain at least one dot (e.g., shortxlinks.com)." else: site = input_text success_msg = f"Shortener site updated successfully to `{site}`." await set_shortener_settings(site=site) elif action == "set_shortener_api": if input_text.lower() in ("off", "disable", "disabled"): api = None success_msg = "Shortener API key has been cleared/disabled." await set_shortener_settings(api=api) elif not input_text: success = False err_msg = "API key cannot be empty." else: api = input_text success_msg = "Shortener API key updated successfully." await set_shortener_settings(api=api) elif action == "set_force_sub": if input_text.lower() in ("off", "disable", "disabled", "0"): await set_channel_link(None) await set_force_sub(0) success_msg = "Force subscription channel has been disabled." else: try: resolved_ids, links_list = await parse_and_set_forcesub(bot, input_text) success_msg = f"Force subscription channels set to: `{', '.join(resolved_ids)}`. Links: {', '.join(links_list)}" except Exception as e: success = False err_msg = f"Could not find or access channels: {str(e)}. Make sure the channels exist and the bot is added as an administrator." elif action == "set_channel_link": if input_text.lower() in ("off", "disable", "disabled"): await set_channel_link(None) success_msg = "Channel invite link has been removed." elif not (input_text.startswith("http://") or input_text.startswith("https://")): success = False err_msg = "Invalid URL. The link must start with 'http://' or 'https://'." else: await set_channel_link(input_text) success_msg = f"Channel invite link set to `{input_text}`." elif action == "set_admins": try: raw_ids = input_text.split(",") parsed_ids = [] is_list_valid = True invalid_val = "" from mfinder import id_pattern for r_id in raw_ids: r_id = r_id.strip() if not r_id: continue if id_pattern.search(r_id): parsed_ids.append(int(r_id)) else: is_list_valid = False invalid_val = r_id break if not is_list_valid: success = False err_msg = f"Invalid Admin ID `{invalid_val}`. Admins list must contain only comma-separated integer IDs." elif not parsed_ids: success = False err_msg = "Admins list cannot be empty." else: from mfinder import OWNER_ID if OWNER_ID not in parsed_ids: parsed_ids.append(OWNER_ID) admins_str = ",".join(str(i) for i in parsed_ids) await update_admins_list(admins_str) ADMINS.clear() ADMINS.extend(parsed_ids) success_msg = f"Admins list updated successfully with {len(parsed_ids)} admins." except Exception as e: success = False err_msg = f"Error parsing admins: {e}" elif action == "set_db_channels": try: raw_ids = input_text.split(",") parsed_ids = [] is_list_valid = True invalid_val = "" from mfinder import id_pattern for r_id in raw_ids: r_id = r_id.strip() if not r_id: continue if id_pattern.search(r_id): parsed_ids.append(int(r_id)) else: is_list_valid = False invalid_val = r_id break if not is_list_valid: success = False err_msg = f"Invalid Channel ID `{invalid_val}`. DB Channels list must contain only comma-separated integer IDs." else: channels_str = ",".join(str(i) for i in parsed_ids) await update_db_channels_list(channels_str) DB_CHANNELS.clear() DB_CHANNELS.extend(parsed_ids) success_msg = f"Database channels list updated successfully with {len(parsed_ids)} channels." except Exception as e: success = False err_msg = f"Error parsing DB channels: {e}" elif action == "set_token_timeout": val = input_text.lower().strip() parsed_seconds = parse_duration_to_seconds(val) if parsed_seconds is None or parsed_seconds <= 0: success = False err_msg = "Invalid format. Use e.g. '1h' (1 hour), '30m' (30 minutes), or a positive number of seconds." else: await set_token_timeout(parsed_seconds) if parsed_seconds >= 86400: days = parsed_seconds / 86400 time_desc = f"{days:.1f} days" if days % 1 != 0 else f"{int(days)} days" elif parsed_seconds >= 3600: hours = parsed_seconds / 3600 time_desc = f"{hours:.1f} hours" if hours % 1 != 0 else f"{int(hours)} hours" elif parsed_seconds >= 60: mins = parsed_seconds / 60 time_desc = f"{mins:.1f} minutes" if mins % 1 != 0 else f"{int(mins)} minutes" else: time_desc = f"{parsed_seconds} seconds" success_msg = f"Token validity duration set to `{parsed_seconds}` seconds ({time_desc})." elif action == "set_log_channel": if input_text.lower() in ("off", "disable", "disabled", "0"): await set_log_channel(None) success_msg = "User search logging channel has been disabled." else: channel_str = input_text is_valid_id = False if channel_str.startswith("@") and len(channel_str) > 1: is_valid_id = True channel_param = channel_str else: try: channel_param = int(channel_str) is_valid_id = True except ValueError: pass if not is_valid_id: success = False err_msg = "Must be a valid integer ID (e.g., -100123456789) or username starting with '@'." else: try: chat = await bot.get_chat(channel_param) access_hash = None try: resolved = await bot.resolve_peer(chat.id) if getattr(resolved, "access_hash", None): access_hash = resolved.access_hash except Exception: pass await set_log_channel(chat.id, access_hash=access_hash) success_msg = f"User search logging channel set to `{chat.id}` ({chat.title or 'Channel'})." except Exception as e: success = False err_msg = f"Could not find or access the channel: {str(e)}. Make sure the channel exists and the bot is added as an administrator." elif action == "set_how_to_download": if input_text.lower() in ("off", "disable", "disabled"): await set_how_to_download(None) success_msg = "How to Download/Verify URL has been disabled/removed." elif not (input_text.startswith("http://") or input_text.startswith("https://")): success = False err_msg = "Invalid URL. The link must start with 'http://' or 'https://'." else: await set_how_to_download(input_text) success_msg = f"How to Download URL set to `{input_text}`." elif action == "set_newsletter_days": valid_days = {"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"} input_days = [d.strip().lower() for d in input_text.split(",") if d.strip()] if not input_days or any(d not in valid_days for d in input_days): success = False err_msg = "Must be a comma-separated list of valid weekdays (e.g. Friday,Sunday)." else: days_str = ",".join(d.capitalize() for d in input_days) from mfinder.db.settings_sql import set_newsletter_settings await set_newsletter_settings(days=days_str) success_msg = f"Newsletter days updated to `{days_str}`." elif action == "set_newsletter_time": import re if not re.match(r"^(0\d|1\d|2[0-3]):[0-5]\d$", input_text.strip()): success = False err_msg = "Must be in valid 24-hour HH:MM format (e.g. 20:00 or 08:30)." else: time_str = input_text.strip() from mfinder.db.settings_sql import set_newsletter_settings await set_newsletter_settings(time_str=time_str) success_msg = f"Newsletter send time set to `{time_str}`." elif action == "set_newsletter_count": try: val = int(input_text.strip()) if val <= 0 or val > 100: raise ValueError from mfinder.db.settings_sql import set_newsletter_settings await set_newsletter_settings(count=val) success_msg = f"Newsletter max movie count set to `{val}`." except ValueError: success = False err_msg = "Must be a positive integer between 1 and 100." elif action == "set_announcement_channel": if input_text.lower() == "off": from mfinder.db.settings_sql import set_announcement_settings await set_announcement_settings(channel="off") success_msg = "Upload Announcements Channel disabled." else: try: chat = await bot.get_chat(input_text) if chat.type.value not in ("channel", "supergroup"): success = False err_msg = "Target must be a channel or supergroup." else: access_hash = None try: resolved = await bot.resolve_peer(chat.id) if getattr(resolved, "access_hash", None): access_hash = resolved.access_hash except Exception: pass from mfinder.db.settings_sql import set_announcement_settings await set_announcement_settings(channel=chat.id, access_hash=access_hash) success_msg = f"Upload Announcements Channel set to `{chat.title}` ({chat.id}) with access hash resolved!" except Exception as e: success = False err_msg = f"Failed to resolve channel '{input_text}': {e}. Make sure the bot is an admin/member in the channel." else: success = False err_msg = "Unknown action." from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton category_map = { "set_auto_delete": "gen", "set_custom_caption": "cap", "set_caption_uname": "cap", "set_shortener_site": "sho", "set_shortener_api": "sho", "set_token_timeout": "sho", "set_force_sub": "cha", "set_channel_link": "cha", "set_admins": "cha", "set_db_channels": "cha", "set_log_channel": "cha", "set_announcement_channel": "cha", "set_how_to_download": "gen", "set_newsletter_days": "newsl", "set_newsletter_time": "newsl", "set_newsletter_count": "newsl" } cat = "rot" if action.startswith("edit_rotator_shortener") or action == "add_rotator_shortener" else category_map.get(action, "main") if success: ADMIN_INPUT_STATE.pop(user_id, None) keyboard = InlineKeyboardMarkup([ [InlineKeyboardButton("ā¬…ļø Back", callback_data=f"adm_cat:{cat}")] ]) text_to_send = f"āœ… {success_msg}\n\nClick back to configure other settings." else: keyboard = InlineKeyboardMarkup([ [InlineKeyboardButton("āŒ Cancel Configuration", callback_data=f"adm_cat:{cat}")] ]) prompt_map = { "edit_rotator_shortener": "Send the new details (format: `domain api_key tutorial_url`):", "set_auto_delete": "Send the new Auto Delete duration in seconds (or 'off' to disable):", "set_custom_caption": "Send the new Custom Caption text (or 'off' to disable):", "set_caption_uname": "Send the new Caption Username starting with '@' (or 'off' to disable):", "set_shortener_site": "Send the new Site name (e.g. shortxlinks.com):", "set_shortener_api": "Send the new Shortener API Key:", "set_token_timeout": "Send the new Token validity time (e.g., '1h' for 1 hour, '30m' for 30 mins, or raw seconds):", "set_force_sub": "Send the new Force Subscription Channel ID (or 'off' to disable):", "set_channel_link": "Send the new Force Subscription Channel Link (or 'off' to disable):", "set_admins": "Send a comma-separated list of Admin User IDs (e.g. 12345, 67890):", "set_db_channels": "Send a comma-separated list of DB Channel IDs (e.g. -100123, -100456):", "set_log_channel": "Send the new User Search Logs Channel ID or username (or 'off' to disable):", "set_how_to_download": "Send the new How to Download/Verify URL (or 'off' to disable):", "set_announcement_channel": "Send the new Upload Announcements Channel ID (e.g. -100123456) or 'off' to disable:", "set_newsletter_days": "Send the days of week separated by comma (e.g. Friday,Sunday):", "set_newsletter_time": "Send the new Send Time in 24-hour HH:MM format (e.g. 20:00):", "set_newsletter_count": "Send the maximum number of new uploads to include in the newsletter (e.g. 10):" } if action.startswith("edit_rotator_shortener") or action == "add_rotator_shortener": original_prompt = "Send the details (format: `domain api_key tutorial_url`):" else: original_prompt = prompt_map.get(action, "Send the new parameter value:") import time text_to_send = f"āš ļø **Invalid input:** `{err_msg}`\n\nāŒØļø **{original_prompt}**" + ("\u200b" * int(time.time() % 5)) from pyrogram.errors import MessageNotModified try: await bot.edit_message_text( chat_id=user_id, message_id=menu_message_id, text=text_to_send, reply_markup=keyboard ) except MessageNotModified: pass except Exception: try: sent_msg = await bot.send_message(user_id, text_to_send, reply_markup=keyboard) if not success and state: state["message_id"] = sent_msg.id import time state["timestamp"] = time.time() except Exception: pass @Client.on_message(filters.command(["ban"]) & filters.user(ADMINS)) async def banuser(bot, update): data = update.text.split() if len(data) == 2: user_id = data[-1] banned = await is_banned(int(user_id)) if not banned: await ban_user(int(user_id)) await update.reply_text(f"User {user_id} banned") else: await update.reply_text(f"User {user_id} is already banned") else: await update.reply_text("Please send in proper format `/ban user_id`") @Client.on_message(filters.command(["unban"]) & filters.user(ADMINS)) async def unbanuser(bot, update): data = update.text.split() if len(data) == 2: user_id = data[-1] banned = await is_banned(int(user_id)) if banned: await unban_user(int(user_id)) await update.reply_text(f"User {user_id} unbanned") else: await update.reply_text(f"User {user_id} is not in ban list") else: await update.reply_text("Please send in proper format `/unban user_id`") @Client.on_message(filters.command(["addfilter"]) & filters.user(ADMINS)) async def addfilter(bot, update): data = shlex.split(update.text) if len(data) >= 3: fltr = data[1].strip('"').lower() message = " ".join(data[2:]) add = await add_filter(fltr, message) if add: await update.reply_text(f"Filter `{fltr}` added") else: await update.reply_text(f"Filter `{fltr}` already exists") else: await update.reply_text( "Please send in proper format `/addfilter filter message`" ) @Client.on_message(filters.command(["delfilter"]) & filters.user(ADMINS)) async def delfilter(bot, update): data = update.text.split() if len(data) >= 2: fltr = " ".join(data[1:]) rem = await rem_filter(fltr) if rem: await update.reply_text(f"Filter `{fltr}` removed") else: await update.reply_text(f"Filter `{fltr}` not found") else: await update.reply_text("Please send in proper format `/delfilter filter`") @Client.on_message(filters.command(["listfilters"]) & filters.user(ADMINS)) async def list_filter(bot, update): fltr = await list_filters() fltr_msg = "" if fltr: for fltrs in fltr: fltr_msg += "\n" + "`" + fltrs + "`" await update.reply_text(f"**Available Filters:** {fltr_msg}") else: await update.reply_text("No filters found") @Client.on_message(filters.command(["forcesub"]) & filters.user(ADMINS)) async def force_sub(bot, update): data = update.text.split() if len(data) == 2: channel_str = data[-1] if channel_str.lower() == "off": await set_channel_link(None) await set_force_sub(0) await update.reply_text("Force Subscription disabled") return try: resolved_ids, links_list = await parse_and_set_forcesub(bot, channel_str) await update.reply_text(f"Force subscription channels set to: `{', '.join(resolved_ids)}`. Links: {', '.join(links_list)}") except Exception as e: await update.reply_text( f"āŒ Error setting force subscription: {str(e)}. Make sure the channels exist and the bot is added as an administrator." ) else: await update.reply_text( "Please send in proper format `/forcesub channel_id/off`" ) @Client.on_message(filters.command(["logchannel"]) & filters.user(ADMINS)) async def log_channel_(bot, update): data = update.text.split() if len(data) == 2: channel_str = data[-1] if channel_str.lower() == "off": await set_log_channel(None) await update.reply_text("User search logging disabled.") return try: if channel_str.startswith("@"): channel_param = channel_str else: channel_param = int(channel_str) chat = await bot.get_chat(channel_param) access_hash = None try: resolved = await bot.resolve_peer(chat.id) if getattr(resolved, "access_hash", None): access_hash = resolved.access_hash except Exception: pass await set_log_channel(chat.id, access_hash=access_hash) await update.reply_text(f"User search logging channel set to `{chat.id}` ({chat.title or 'Channel'}).") except Exception as e: await update.reply_text( f"āŒ Error setting log channel: {str(e)}. Make sure the channel exists and the bot is added as an administrator." ) else: await update.reply_text( "Please send in proper format `/logchannel channel_id/off`" ) @Client.on_message(filters.command(["checklink"]) & filters.user(ADMINS)) async def testlink(bot, update): link = await get_link() if link: await update.reply_text(f"Invite link for force subscription channel: {link}") else: await update.reply_text( "Force Subscription is disabled, please enable it first" ) @Client.on_message(filters.command(["setusername"]) & filters.user(ADMINS)) async def caption_username(bot, update): data = update.text.split() if len(data) == 2: username = data[-1] if username.lower() == "off": username = 0 elif username.startswith("@"): username = username else: await update.reply_text("This is not a username, please check.") return await set_username(username) if username: await update.reply_text(f"File caption username set to `{username}`") else: await update.reply_text("File caption username disabled") else: await update.reply_text( "Please send in proper format `/setusername username/off`" ) @Client.on_message(filters.command(["total"]) & filters.user(ADMINS)) async def count_f(bot, update): count = await count_files() await update.reply_text(f"**Total no. of files in DB:** `{count}`") async def parse_and_set_forcesub(bot, channel_str): channels = [ch.strip() for ch in channel_str.split(',') if ch.strip()] resolved_ids = [] hashes_map = {} links_list = [] for ch in channels: try: ch_param = int(ch) except ValueError: ch_param = ch if ch.startswith("@") else f"@{ch}" chat = await bot.get_chat(ch_param) resolved_ids.append(str(chat.id)) # Resolve access hash access_hash = None try: resolved = await bot.resolve_peer(chat.id) if getattr(resolved, "access_hash", None): access_hash = resolved.access_hash except Exception: pass if access_hash is not None: hashes_map[str(chat.id)] = access_hash # Link resolving link = None try: link_obj = await bot.create_chat_invite_link(chat.id) link = link_obj.invite_link except Exception: if chat.username: link = f"https://t.me/{chat.username}" if link: links_list.append(link) if not resolved_ids: raise ValueError("No valid channels could be resolved.") from mfinder.db.settings_sql import set_force_sub_channels, set_channel_link await set_force_sub_channels(",".join(resolved_ids), hashes_map) if links_list: await set_channel_link(links_list[0]) return resolved_ids, links_list async def verify_user_token(bot, update, param): import time import asyncio from mfinder import LOGGER from mfinder.db.settings_sql import get_user_token, activate_user_token, get_admin_settings, prune_all_expired_tokens # Clean up any other expired or stale tokens await prune_all_expired_tokens() user_id = update.from_user.id token_str = param tok_rec = await get_user_token(user_id) if not tok_rec or tok_rec.token != token_str: if tok_rec and tok_rec.req_msg_id: try: await bot.delete_messages(chat_id=user_id, message_ids=int(tok_rec.req_msg_id)) except Exception as e: LOGGER.warning(f"Failed to delete original token request message: {e}") err_msg = await update.reply_text( "āŒ **Invalid token link!**\n\nPlease search for a movie again to generate a new verification link.", quote=True ) async def delete_after_delay(b, u_id, err_msg_id, incoming_msg_id): await asyncio.sleep(10) try: await b.delete_messages(chat_id=u_id, message_ids=[err_msg_id, incoming_msg_id]) except Exception: pass asyncio.create_task(delete_after_delay(bot, user_id, err_msg.id, update.id)) return # Check if the token has already been claimed/activated if tok_rec.status == "active": if tok_rec.target_file_id: update.text = f"/start {tok_rec.target_file_id}" from mfinder.plugins.serve import get_files await get_files(bot, update) return elif tok_rec.search_query: from mfinder.plugins.serve import filter_ update.text = tok_rec.search_query await filter_(bot, update) return else: await update.reply_text( "āœ… **Token Verified Successfully!**\n\nYou already have direct, ad-free access to all movies and files.", quote=True ) return current_time = time.time() if tok_rec.status == "pending" and (current_time - float(tok_rec.created_at)) > 1800: if tok_rec.req_msg_id: try: await bot.delete_messages(chat_id=user_id, message_ids=int(tok_rec.req_msg_id)) except Exception as e: LOGGER.warning(f"Failed to delete original token request message: {e}") from mfinder.db.settings_sql import delete_user_token await delete_user_token(user_id) err_msg = await update.reply_text( "āŒ **Verification link has expired!**\n\nThe link is only valid to be claimed within 30 minutes of generation. Please search for a movie again to get a new link.", quote=True ) async def delete_after_delay(b, u_id, err_msg_id, incoming_msg_id): await asyncio.sleep(10) try: await b.delete_messages(chat_id=u_id, message_ids=[err_msg_id, incoming_msg_id]) except Exception: pass asyncio.create_task(delete_after_delay(bot, user_id, err_msg.id, update.id)) return admin_settings = await get_admin_settings() timeout = float(admin_settings.token_timeout) if admin_settings and admin_settings.token_timeout else 3600 expires_at = current_time + timeout success = await activate_user_token(user_id, expires_at) if success: try: from mfinder.db.settings_sql import advance_user_shortener_index await advance_user_shortener_index(user_id) except Exception as rot_err: LOGGER.warning(f"Error advancing smart rotator index: {rot_err}") if not success: await update.reply_text( "āŒ **Verification failed due to database error!** Please try again later.", quote=True ) return if timeout >= 3600: hours = timeout / 3600 time_desc = f"{hours:.1f} hours" if hours % 1 != 0 else f"{int(hours)} hours" elif timeout >= 60: mins = timeout / 60 time_desc = f"{mins:.1f} minutes" if mins % 1 != 0 else f"{int(mins)} minutes" else: time_desc = f"{timeout} seconds" if tok_rec.req_msg_id: try: await bot.delete_messages(chat_id=user_id, message_ids=int(tok_rec.req_msg_id)) except Exception as e: LOGGER.warning(f"Failed to delete original token request message: {e}") verified_msg = await update.reply_text( f"āœ… **Token Verified Successfully!**\n\nYou now have direct, ad-free access to all movies and files for the next **{time_desc}**.\n\nEnjoy!", quote=True ) async def delete_after_delay(b, u_id, success_msg_id, incoming_msg_id): await asyncio.sleep(10) try: await b.delete_messages(chat_id=u_id, message_ids=[success_msg_id, incoming_msg_id]) except Exception as e: LOGGER.warning(f"Failed to auto-delete verification messages: {e}") asyncio.create_task(delete_after_delay(bot, user_id, verified_msg.id, update.id)) search_q = tok_rec.search_query target_f_id = tok_rec.target_file_id if search_q: try: from mfinder.plugins.serve import filter_ update.text = search_q await asyncio.sleep(0.5) await filter_(bot, update) except Exception as e: LOGGER.warning(f"Error auto-resuming search query '{search_q}': {e}") elif target_f_id: try: from mfinder.plugins.serve import get_files update.text = f"/start {target_f_id}" await asyncio.sleep(0.5) await get_files(bot, update) except Exception as e: LOGGER.warning(f"Error auto-resuming file download for '{target_f_id}': {e}") @Client.on_callback_query(filters.regex(r"^adm_rot_toggle_smart$") & filters.user(ADMINS)) async def admin_rot_toggle_smart_callback(bot, query): from mfinder.db.settings_sql import get_admin_settings, set_smart_rotator_state, clear_all_user_shortener_usages admin_settings = await get_admin_settings() new_state = not getattr(admin_settings, "smart_rotator", False) await set_smart_rotator_state(new_state) try: from mfinder.db.settings_sql import clear_all_user_shortener_usages await clear_all_user_shortener_usages() except Exception: pass from mfinder.utils.helpers import clear_short_url_cache clear_short_url_cache() await query.answer(f"Smart Rotator {'ON' if new_state else 'OFF'}") text, keyboard = await get_settings_screen("sho") try: await query.message.edit_text(text=text, reply_markup=keyboard) except Exception: pass @Client.on_callback_query(filters.regex(r"^adm_rot_manage:\d+$") & filters.user(ADMINS)) async def admin_rot_manage_callback(bot, query): idx = int(query.data.split(":")[1]) text, keyboard = await get_settings_screen(f"rot_manage:{idx}") try: await query.message.edit_text(text=text, reply_markup=keyboard) except Exception: pass await query.answer() @Client.on_callback_query(filters.regex(r"^adm_rot_toggle:\d+$") & filters.user(ADMINS)) async def admin_rot_toggle_callback(bot, query): idx = int(query.data.split(":")[1]) import json from mfinder.db.settings_sql import get_admin_settings, update_shorteners_list admin_settings = await get_admin_settings() shorteners = get_all_shorteners(admin_settings) if idx < len(shorteners): shorteners[idx]["status"] = not shorteners[idx].get("status", True) await update_shorteners_list(json.dumps(shorteners)) try: from mfinder.db.settings_sql import clear_all_user_shortener_usages await clear_all_user_shortener_usages() except Exception: pass from mfinder.utils.helpers import clear_short_url_cache clear_short_url_cache() await query.answer("Status toggled!") text, keyboard = await get_settings_screen(f"rot_manage:{idx}") try: await query.message.edit_text(text=text, reply_markup=keyboard) except Exception: pass @Client.on_callback_query(filters.regex(r"^adm_rot_edit:\d+$") & filters.user(ADMINS)) async def admin_rot_edit_callback(bot, query): idx = int(query.data.split(":")[1]) user_id = query.from_user.id from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton ADMIN_INPUT_STATE[user_id] = {"action": f"edit_rotator_shortener:{idx}", "message_id": query.message.id} keyboard = InlineKeyboardMarkup([[InlineKeyboardButton("āŒ Cancel", callback_data=f"adm_rot_manage:{idx}")]]) await query.message.edit_text( text=f"āŒØļø **Send the new details for Shortener #{idx+1} (format: `domain api_key tutorial_url`):**", reply_markup=keyboard ) await query.answer() @Client.on_callback_query(filters.regex(r"^adm_rot_delete:\d+$") & filters.user(ADMINS)) async def admin_rot_delete_callback(bot, query): idx = int(query.data.split(":")[1]) import json from mfinder.db.settings_sql import get_admin_settings, update_shorteners_list admin_settings = await get_admin_settings() shorteners = get_all_shorteners(admin_settings) if idx < len(shorteners): removed = shorteners.pop(idx) await update_shorteners_list(json.dumps(shorteners)) try: from mfinder.db.settings_sql import clear_all_user_shortener_usages await clear_all_user_shortener_usages() except Exception: pass from mfinder.utils.helpers import clear_short_url_cache clear_short_url_cache() await query.answer(f"Removed {removed.get('site')}!") text, keyboard = await get_settings_screen("sho") try: await query.message.edit_text(text=text, reply_markup=keyboard) except Exception: pass @Client.on_callback_query(filters.regex(r"^adm_rot_clear$") & filters.user(ADMINS)) async def admin_rot_clear_callback(bot, query): from mfinder.db.settings_sql import update_shorteners_list await update_shorteners_list("[]") try: from mfinder.db.settings_sql import clear_all_user_shortener_usages await clear_all_user_shortener_usages() except Exception: pass from mfinder.utils.helpers import clear_short_url_cache clear_short_url_cache() await query.answer("All rotator shorteners cleared!") text, keyboard = await get_settings_screen("sho") try: await query.message.edit_text(text=text, reply_markup=keyboard) except Exception: pass @Client.on_callback_query(filters.regex(r"^adm_rot_add$") & filters.user(ADMINS)) async def admin_rot_add_callback(bot, query): user_id = query.from_user.id from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton ADMIN_INPUT_STATE[user_id] = {"action": "add_rotator_shortener", "message_id": query.message.id} keyboard = InlineKeyboardMarkup([[InlineKeyboardButton("āŒ Cancel", callback_data="adm_cat:rot")]]) await query.message.edit_text( text="āŒØļø **Send the new shortener site, API key, and How to Download URL separated by spaces (e.g. `gplinks.in api_key_here https://t.me/tutorial`):**", reply_markup=keyboard ) await query.answer()