FireBolt / mfinder /plugins /admin_settings.py
AngryBird-22's picture
Upload 35 files
5dc4327 verified
Raw
History Blame
74.2 kB
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 <duration>` (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 <on/off>`"
)
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()