├── runtime.txt ├── Procfile ├── start.sh ├── heroku.yml ├── misc ├── __init__.py └── callback.py ├── requirements.txt ├── docker-compose.yml ├── core ├── __init__.py ├── mongo.py ├── db.py ├── database.py └── start.py ├── utils ├── __init__.py ├── logging_setup.py └── helper.py ├── app.py ├── auth ├── __init__.py ├── set │ └── set.py ├── speedtest │ └── speedtest.py ├── sudo │ └── sudo.py ├── logs │ └── logs.py └── restart │ └── restart.py ├── sample.env ├── main.py ├── Dockerfile ├── plugins ├── __init__.py ├── info.py ├── thumb.py ├── pvt.py ├── public.py ├── pbatch.py ├── pvdl.py ├── login.py └── plan.py ├── LICENSE ├── app.json ├── config.py └── README.md /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.10.13 -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: bash start.sh -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python3 main.py -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | web: Dockerfile 4 | 5 | run: 6 | worker: bash start.sh -------------------------------------------------------------------------------- /misc/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from .callback import handle_callback_query -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyrofork 2 | tgcrypto 3 | asyncio 4 | aiofiles 5 | pillow 6 | pyleaves 7 | telegraph 8 | python-dotenv 9 | speedtest-cli 10 | pymongo 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | smarttoolbot: 3 | build: . 4 | container_name: restricteddl 5 | ports: 6 | - "8000:8000" 7 | env_file: 8 | - .env 9 | volumes: 10 | - .:/app -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from .start import setup_start_handler 4 | from .database import prem_plan1, prem_plan2, prem_plan3, user_sessions 5 | from .db import daily_limit, total_users 6 | from .mongo import user_activity_collection -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from .logging_setup import LOGGER 4 | from .helper import ( 5 | getChatMsgID, 6 | processMediaGroup, 7 | get_parsed_msg, 8 | fileSizeLimit, 9 | progressArgs, 10 | send_media, 11 | get_readable_file_size, 12 | get_readable_time 13 | ) -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | #Copyright @ISmartDevs 2 | #Channel t.me/TheSmartDev 3 | from pyrogram import Client 4 | from utils import LOGGER 5 | from config import ( 6 | API_ID, 7 | API_HASH, 8 | BOT_TOKEN 9 | ) 10 | 11 | LOGGER.info("Creating Bot Client From BOT_TOKEN") 12 | 13 | app = Client( 14 | "SmartTools", 15 | api_id=API_ID, 16 | api_hash=API_HASH, 17 | bot_token=BOT_TOKEN, 18 | workers=1000 19 | ) 20 | 21 | LOGGER.info("Bot Client Created Successfully!") -------------------------------------------------------------------------------- /auth/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from .logs.logs import setup_logs_handler 4 | from .restart.restart import setup_restart_handler 5 | from .speedtest.speedtest import setup_speed_handler 6 | from .sudo.sudo import setup_sudo_handler 7 | from .set.set import setup_set_handler 8 | 9 | def setup_auth_handlers(app): 10 | 11 | setup_sudo_handler(app) 12 | setup_restart_handler(app) 13 | setup_speed_handler(app) 14 | setup_logs_handler(app) 15 | setup_set_handler(app) 16 | -------------------------------------------------------------------------------- /sample.env: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | 4 | #Telegram API credentials (required) 5 | API_ID=YOUR_API_ID 6 | API_HASH=YOUR_API_HASH 7 | BOT_TOKEN=YOUR_BOT_TOKEN 8 | 9 | #Admin and owner IDs 10 | DEVELOPER_USER_ID=YOUR_USER_ID 11 | 12 | #Database URLs 13 | MONGO_URL=YOUR_MONGO_URL 14 | 15 | #Primary database URL 16 | DATABASE_URL=YOUR_DATABASE_URL 17 | 18 | #Additional database URL 19 | DB_URL=YOUR_DB_URL 20 | 21 | #Command prefixes for bot commands 22 | COMMAND_PREFIX=!|.|#|,|/ 23 | 24 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from utils import LOGGER 4 | from auth import setup_auth_handlers 5 | from plugins import setup_plugins_handlers 6 | from core import setup_start_handler 7 | from misc import handle_callback_query 8 | from app import app 9 | 10 | setup_plugins_handlers(app) 11 | setup_auth_handlers(app) 12 | setup_start_handler(app) 13 | 14 | @app.on_callback_query() 15 | async def handle_callback(client, callback_query): 16 | await handle_callback_query(client, callback_query) 17 | 18 | LOGGER.info("Bot Successfully Started! 💥") 19 | app.run() -------------------------------------------------------------------------------- /core/mongo.py: -------------------------------------------------------------------------------- 1 | #Copyright @ISmartDevs 2 | #Channel t.me/TheSmartDev 3 | from pymongo import MongoClient 4 | from utils import LOGGER 5 | from config import MONGO_URL 6 | 7 | # Initialize MongoDB Client 8 | LOGGER.info("Creating MONGO_CLIENT From MONGO_URL") 9 | try: 10 | MONGO_CLIENT = MongoClient(MONGO_URL) 11 | LOGGER.info("MONGO_CLIENT Successfully Created!") 12 | except Exception as e: 13 | LOGGER.error(f"Failed to create MONGO_CLIENT: {e}") 14 | raise 15 | 16 | # Access the database and collections 17 | db = MONGO_CLIENT["user_activity_db"] 18 | user_activity_collection = db["user_activity"] -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | #Copyright @ISmartDevs 2 | #Channel t.me/TheSmartDev 3 | # Use an official Python image 4 | FROM python:3.9-slim-buster 5 | 6 | # Install system dependencies 7 | RUN apt update && apt install -y git curl ffmpeg && apt clean 8 | 9 | # Set working directory 10 | WORKDIR /app 11 | 12 | # Copy requirements and install them 13 | COPY requirements.txt . 14 | RUN pip install --no-cache-dir -r requirements.txt 15 | 16 | # Copy rest of the bot files 17 | COPY . . 18 | 19 | # Make start.sh executable 20 | RUN chmod +x start.sh 21 | 22 | # Expose port for Flask 23 | EXPOSE 8000 24 | 25 | # Run the bot and Flask server 26 | CMD ["bash", "start.sh"] -------------------------------------------------------------------------------- /plugins/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from .plan import setup_plan_handler 4 | from .public import setup_public_handler 5 | from .info import setup_info_handler 6 | from .thumb import setup_thumb_handler 7 | from .pvt import setup_pvt_handler 8 | from .login import setup_login_handler 9 | from .pbatch import setup_pbatch_handler 10 | from .pvdl import setup_pvdl_handler 11 | 12 | def setup_plugins_handlers(app): 13 | 14 | setup_plan_handler(app) 15 | setup_public_handler(app) 16 | setup_info_handler(app) 17 | setup_thumb_handler(app) 18 | setup_pvt_handler(app) 19 | setup_login_handler(app) 20 | setup_pbatch_handler(app) 21 | setup_pvdl_handler(app) -------------------------------------------------------------------------------- /core/db.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from pymongo import MongoClient 4 | from config import DB_URL 5 | from utils import LOGGER 6 | 7 | # Log the initialization attempt 8 | LOGGER.info("Creating DB Client From DB_URL") 9 | 10 | try: 11 | # Create MongoDB client using DB_URL from config.py 12 | channel_db_client = MongoClient(DB_URL) 13 | # Access the "ItsSmartTool" database 14 | channel_db = channel_db_client["ItsSmartTool"] 15 | daily_limit = channel_db["daily_limit"] 16 | total_users = channel_db["total_users"] 17 | LOGGER.info("DB Client Successfully Created!") 18 | except Exception as e: 19 | # Log the error with details and raise it to halt execution 20 | LOGGER.error(f"Failed to create DB Client for Group-Channel Bindings: {e}") 21 | raise -------------------------------------------------------------------------------- /utils/logging_setup.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | import logging 4 | from logging.handlers import RotatingFileHandler 5 | 6 | logging.basicConfig( 7 | level=logging.INFO, 8 | format="%(asctime)s - %(levelname)s - %(message)s", 9 | datefmt='%Y-%m-%d %H:%M:%S', 10 | handlers=[ 11 | RotatingFileHandler( 12 | "botlog.txt", 13 | maxBytes=50000000, 14 | backupCount=10 15 | ), 16 | logging.StreamHandler() 17 | ] 18 | ) 19 | 20 | logging.getLogger("pyrogram").setLevel(logging.ERROR) 21 | logging.getLogger("telethon").setLevel(logging.ERROR) 22 | logging.getLogger("aiohttp").setLevel(logging.ERROR) 23 | logging.getLogger("apscheduler").setLevel(logging.ERROR) 24 | 25 | LOGGER = logging.getLogger(__name__) -------------------------------------------------------------------------------- /core/database.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from pymongo import MongoClient 4 | from config import DATABASE_URL 5 | from utils import LOGGER 6 | 7 | # Log the initialization attempt 8 | LOGGER.info("Creating Database Client From DATABASE_URL") 9 | 10 | try: 11 | # Create MongoDB client using DATABASE_URL from config.py 12 | mongo_client = MongoClient(DATABASE_URL) 13 | # Access the "ItsSmartTool" database 14 | db = mongo_client["ItsSmartTool"] 15 | prem_plan1 = db["prem_plan1"] 16 | prem_plan2 = db["prem_plan2"] 17 | prem_plan3 = db["prem_plan3"] 18 | user_sessions = db["user_sessions"] 19 | LOGGER.info("Database Client Successfully Created!") 20 | except Exception as e: 21 | # Log the error with details and raise it to halt execution 22 | LOGGER.error(f"Database Client Create Error: {e}") 23 | raise -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Abir Arafat Chawdhury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /core/start.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from pyrogram import Client, filters 4 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 5 | from pyrogram.enums import ParseMode 6 | from utils import LOGGER 7 | 8 | def setup_start_handler(app: Client): 9 | @app.on_message(filters.command("start")) 10 | async def start(client: Client, message: Message): 11 | user_fullname = f"{message.from_user.first_name} {message.from_user.last_name or ''}".strip() 12 | start_message = f""" 13 | Hi {user_fullname}! Welcome To This Bot 14 | ━━━━━━━━━━━━━━━━━━━ 15 | PrivateContentSaver💥 : The ultimate toolkit on Telegram, offering public, private channels, groups, supergroups & also batch link supported! 16 | ━━━━━━━━━━━━━━━━━━━ 17 | 🔔 Don't Forget To Join Here For Updates! 18 | """ 19 | reply_markup = InlineKeyboardMarkup([ 20 | [InlineKeyboardButton("Menu", callback_data="main_menu")] 21 | ]) 22 | await message.reply_text( 23 | start_message, 24 | parse_mode=ParseMode.HTML, 25 | reply_markup=reply_markup, 26 | disable_web_page_preview=True 27 | ) 28 | LOGGER.info(f"Start command triggered by {message.from_user.id}") -------------------------------------------------------------------------------- /auth/set/set.py: -------------------------------------------------------------------------------- 1 | #Copyright @ISmartDevs 2 | #Channel t.me/TheSmartDev 3 | from pyrogram import filters 4 | from pyrogram.types import BotCommand 5 | from pyrogram.enums import ParseMode 6 | from config import DEVELOPER_USER_ID 7 | from utils import LOGGER 8 | 9 | BOT_COMMANDS = [ 10 | BotCommand("start", "みStart Private Content Downloader Bot↯"), 11 | BotCommand("help", "みGet Help Menu & Commands↯"), 12 | BotCommand("info", "みGet User Info & Plan Info From Database↯"), 13 | BotCommand("plans", "みSee Available Plans & Purchase↯"), 14 | BotCommand("buy", "みPurchase Premium Plans With Star↯"), 15 | BotCommand("dl", "みDownload Restricted Content From Public Source↯"), 16 | BotCommand("pdl", "みDownload Restricted Content From Private Source↯"), 17 | BotCommand("pbdl", "みDownload Batch Media From Private Source↯"), 18 | BotCommand("bdl", "みDownload Batch From Public Source↯"), 19 | BotCommand("login", "みLogin To Account↯"), 20 | BotCommand("logout", "みLog Out From Account↯"), 21 | BotCommand("profile", "みGet Profile Info & Plan Status↯"), 22 | BotCommand("getthumb", "みGet Custom Thumbnail↯"), 23 | BotCommand("setthumb", "みSet Or Change Custom Thumbnail↯"), 24 | BotCommand("rmthumb", "みRemove Custom Thumbnail↯"), 25 | ] 26 | 27 | def setup_set_handler(app): 28 | @app.on_message(filters.command("set") & filters.user(DEVELOPER_USER_ID)) 29 | async def set_commands(client, message): 30 | await client.set_bot_commands(BOT_COMMANDS) 31 | await client.send_message( 32 | chat_id=message.chat.id, 33 | text="み ¡**BotFather Commands Successfully Set**↯", 34 | parse_mode=ParseMode.MARKDOWN 35 | ) 36 | LOGGER.info(f"BotFather commands set by owner {message.from_user.id}") -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RestrictedContentDL", 3 | "description": "A Telegram bot to save restricted content from public channels. Developed by ISmartDevs.", 4 | "repository": "https://github.com/TheSmartDevs/RestrictedContentDL", 5 | "keywords": ["telegram", "bot", "pyrogram", "restricted-content"], 6 | "env": { 7 | "API_ID": { 8 | "description": "Telegram API ID from https://my.telegram.org", 9 | "value": "Your_API_ID_Here", 10 | "required": true 11 | }, 12 | "API_HASH": { 13 | "description": "Telegram API Hash from https://my.telegram.org", 14 | "value": "Your_API_HASH_Here", 15 | "required": true 16 | }, 17 | "BOT_TOKEN": { 18 | "description": "Telegram Bot Token from @BotFather", 19 | "value": "Your_BOT_TOKEN_Here", 20 | "required": true 21 | }, 22 | "DEVELOPER_USER_ID": { 23 | "description": "Telegram User ID of the bot developer/owner", 24 | "value": "Your_Dev_User_ID_Here", 25 | "required": true 26 | }, 27 | "MONGO_URL": { 28 | "description": "MongoDB connection string (e.g., mongodb://localhost:27017)", 29 | "value": "Your_MONGO_URL_Here", 30 | "required": true 31 | }, 32 | "DATABASE_URL": { 33 | "description": "Primary database URL (e.g., PostgreSQL/MySQL)", 34 | "value": "Your_DATABASE_URL_Here", 35 | "required": true 36 | }, 37 | "DB_URL": { 38 | "description": "Additional database URL (if applicable)", 39 | "value": "Your_DB_URL_Here", 40 | "required": true 41 | }, 42 | "COMMAND_PREFIX": { 43 | "description": "Command prefixes for bot commands (e.g., !|.|^|/)", 44 | "value": "!|.|#|,|/", 45 | "required": true 46 | }, 47 | "buildpacks": [ 48 | { 49 | "url": "heroku/python" 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | # Note: Configure via .env (VPS/local), direct edits to this file (VPS), or Heroku config vars (app.json/dashboard). 4 | 5 | import os 6 | from dotenv import load_dotenv 7 | 8 | load_dotenv() 9 | 10 | def get_env_or_default(key, default=None, cast_func=str): 11 | """ 12 | Load environment variable with type casting and default value. 13 | 14 | Args: 15 | key (str): Environment variable name 16 | default: Default value if variable is unset or empty 17 | cast_func: Function to cast the value (e.g., int, str) 18 | 19 | Returns: 20 | The casted value or default if unset/invalid 21 | """ 22 | value = os.getenv(key) 23 | if value is not None and value.strip(): 24 | try: 25 | return cast_func(value) 26 | except (ValueError, TypeError) as e: 27 | print(f"Error casting {key} with value '{value}' to {cast_func.__name__}: {e}") 28 | return default 29 | return default 30 | 31 | # Telegram API Configuration (Pyrogram MTProto) 32 | API_ID = get_env_or_default("API_ID", "Your_API_ID_Here") 33 | API_HASH = get_env_or_default("API_HASH", "Your_API_HASH_Here") 34 | BOT_TOKEN = get_env_or_default("BOT_TOKEN", "Your_BOT_TOKEN_Here") 35 | 36 | # Admin Configuration 37 | DEVELOPER_USER_ID = get_env_or_default("DEVELOPER_USER_ID", "Your_DEVELOPER_USER_ID_Here", int) 38 | 39 | # Database Configuration 40 | MONGO_URL = get_env_or_default("MONGO_URL", "Your_MONGO_URL_Here") 41 | DATABASE_URL = get_env_or_default("DATABASE_URL", "Your_DATABASE_URL_Here") 42 | DB_URL = get_env_or_default("DB_URL", "Your_DB_URL_Here") 43 | 44 | # Command Prefixes 45 | raw_prefixes = get_env_or_default("COMMAND_PREFIX", "!|.|#|,|/") 46 | COMMAND_PREFIX = [prefix.strip() for prefix in raw_prefixes.split("|") if prefix.strip()] 47 | 48 | 49 | # Validate Required Variables 50 | required_vars = { 51 | "API_ID": API_ID, 52 | "API_HASH": API_HASH, 53 | "BOT_TOKEN": BOT_TOKEN, 54 | "DEVELOPER_USER_ID": DEVELOPER_USER_ID, 55 | "MONGO_URL": MONGO_URL, 56 | "DATABASE_URL": DATABASE_URL, 57 | "DB_URL": DB_URL 58 | } 59 | 60 | for var_name, var_value in required_vars.items(): 61 | if var_value is None or var_value == f"Your_{var_name}_Here" or (isinstance(var_value, str) and not var_value.strip()): 62 | raise ValueError(f"Required variable {var_name} is missing or invalid. Set it in .env, config.py, or Heroku config vars.") 63 | 64 | # Logging Configuration for Debugging 65 | print(f"Loaded COMMAND_PREFIX: {COMMAND_PREFIX}") 66 | if not COMMAND_PREFIX: 67 | raise ValueError("No command prefixes found. Set COMMAND_PREFIX in .env, config.py, or Heroku config vars.") -------------------------------------------------------------------------------- /plugins/info.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from pyrogram import Client, filters 4 | from pyrogram.types import Message 5 | from pyrogram.enums import ParseMode 6 | from config import COMMAND_PREFIX 7 | from utils import LOGGER 8 | from core import prem_plan1, prem_plan2, prem_plan3, user_sessions, daily_limit 9 | from pyrogram.handlers import MessageHandler 10 | 11 | def setup_info_handler(app: Client): 12 | async def info_command(client: Client, message: Message): 13 | user_id = message.from_user.id 14 | user = message.from_user 15 | full_name = f"{user.first_name} {getattr(user, 'last_name', '')}".strip() or "Unknown" 16 | username = f"@{user.username}" if user.username else "@N/A" 17 | 18 | # Check membership status 19 | plan1 = prem_plan1.find_one({"user_id": user_id}) 20 | plan2 = prem_plan2.find_one({"user_id": user_id}) 21 | plan3 = prem_plan3.find_one({"user_id": user_id}) 22 | membership = "free" 23 | if plan1: 24 | membership = "✨ Plan 1" 25 | elif plan2: 26 | membership = "🌟 Plan 2" 27 | elif plan3: 28 | membership = "💎 Plan 3" 29 | 30 | # Logged-in accounts 31 | session = user_sessions.find_one({"user_id": user_id}) 32 | logged_in_accounts = 1 if session and session.get("session_string") else 0 33 | 34 | # Total downloads 35 | total_downloads = 0 36 | daily_record = daily_limit.find_one({"user_id": user_id}) 37 | if daily_record: 38 | total_downloads = daily_record.get("total_downloads", 0) 39 | 40 | # Total purchased stars 41 | total_stars = 0 42 | if plan1: 43 | total_stars += 150 44 | if plan2: 45 | total_stars += 500 46 | if plan3: 47 | total_stars += 1000 48 | 49 | info_text = ( 50 | f"🆔 ID: {user_id}\n" 51 | f"👤 Name: {full_name}\n" 52 | f"📛 Username: {username}\n" 53 | f"🎖️ Membership: {membership}\n" 54 | f"🔗 Logged Accounts: {logged_in_accounts}\n" 55 | f"📥 Total Downloads: {total_downloads}\n" 56 | f"⭐ Total Stars: {total_stars}" 57 | ) 58 | 59 | await message.reply_text(info_text, parse_mode=ParseMode.HTML) 60 | LOGGER.info(f"✘ Info command triggered by user ↯ {user_id}") 61 | 62 | async def help_command(client: Client, message: Message): 63 | help_text = ( 64 | "💥 ContentSaver Help Menu\n" 65 | "This bot helps you download restricted content from both public and private sources.\n" 66 | "━━━━━━━━━━━━━━━━\n" 67 | "🌟 Command Center 🌟\n" 68 | "/plans - View premium plans\n" 69 | "/buy - Buy a premium plan\n" 70 | "/profile - View your profile\n" 71 | "/getthumb - Show your thumbnail\n" 72 | "/setthumb - Set a thumbnail\n" 73 | "/rmthumb - Remove thumbnail\n" 74 | "/dl - Download from public sources\n" 75 | "/pdl - Download from private sources\n" 76 | "/bdl - Batch download (public)\n" 77 | "/pbdl - Batch download (private)\n" 78 | "/info - Account info\n" 79 | "/login - Login for premium\n" 80 | "/logout - Logout and clear data\n" 81 | "━━━━━━━━━━━━━━━━" 82 | ) 83 | await message.reply_text(help_text, parse_mode=ParseMode.HTML) 84 | LOGGER.info(f"✘ Help command triggered by user ↯ {message.from_user.id}") 85 | 86 | app.add_handler( 87 | MessageHandler( 88 | info_command, 89 | filters=filters.command(["info", "profile"], prefixes=COMMAND_PREFIX) & (filters.private | filters.group) 90 | ), 91 | group=1 92 | ) 93 | app.add_handler( 94 | MessageHandler( 95 | help_command, 96 | filters=filters.command("help", prefixes=COMMAND_PREFIX) & (filters.private | filters.group) 97 | ), 98 | group=1 99 | ) -------------------------------------------------------------------------------- /misc/callback.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from pyrogram import Client 4 | from pyrogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton 5 | from pyrogram.enums import ParseMode 6 | from utils import LOGGER 7 | 8 | async def handle_callback_query(client: Client, callback_query: CallbackQuery): 9 | user_id = callback_query.from_user.id 10 | chat_id = callback_query.message.chat.id 11 | message_id = callback_query.message.id 12 | user_fullname = f"{callback_query.from_user.first_name} {callback_query.from_user.last_name or ''}".strip() 13 | 14 | if callback_query.data == "main_menu": 15 | menu_message = ( 16 | "🌟 Welcome to Your Content Savior! 🌟\n" 17 | "This bot is your ultimate companion for downloading restricted content from both public and private Telegram Chats! 💥\n" 18 | "━━━━━━━━━━━━━━━━\n" 19 | "⭐️ Command Center ⭐️\n" 20 | "⭐️ /plans — Discover premium plans to unlock advanced features\n" 21 | "⭐️ /buy — Purchase a premium plan for enhanced access\n" 22 | "⭐️ /profile — Check your account status and premium details\n" 23 | "⭐️ /getthumb — View your custom thumbnail for media downloads\n" 24 | "⭐️ /setthumb — Set or update a custom thumbnail for videos\n" 25 | "💀 /rmthumb — Remove your custom thumbnail\n" 26 | "⭐️ /dl — Download media from public restricted sources\n" 27 | "💥 /pdl — Access and download from private restricted sources\n" 28 | "⭐️ /bdl — Batch download from public channels or groups\n" 29 | "💥 /pbdl — Batch download from private sources\n" 30 | "📈 /info — Get detailed account information\n" 31 | "⁉️ /login — Log in to scrape private messages (premium only)\n" 32 | "✅ /logout — Log out and clear your account data\n" 33 | "━━━━━━━━━━━━━━━━" 34 | ) 35 | reply_markup = InlineKeyboardMarkup([ 36 | [ 37 | InlineKeyboardButton("Back", callback_data="menu_back"), 38 | InlineKeyboardButton("Close", callback_data="menu_close") 39 | ] 40 | ]) 41 | try: 42 | await client.edit_message_text( 43 | chat_id=chat_id, 44 | message_id=message_id, 45 | text=menu_message, 46 | parse_mode=ParseMode.HTML, 47 | reply_markup=reply_markup, 48 | disable_web_page_preview=True 49 | ) 50 | await callback_query.answer() 51 | LOGGER.info(f"Menu displayed for user {user_id}") 52 | except Exception as e: 53 | await callback_query.message.reply_text( 54 | "❌ Error displaying menu. Please try again.", 55 | parse_mode=ParseMode.HTML, 56 | disable_web_page_preview=True 57 | ) 58 | LOGGER.error(f"Error displaying menu for user {user_id}: {e}") 59 | 60 | elif callback_query.data == "menu_back": 61 | start_message = ( 62 | f"Hi {user_fullname}! Welcome To This Bot\n" 63 | "━━━━━━━━━━━━━━━━━━━\n" 64 | " PrivateContentSaver💥 : The ultimate toolkit on Telegram, offering public, private channels, groups, supergroups & also batch link supported!\n" 65 | "━━━━━━━━━━━━━━━━━━━\n" 66 | '🔔 Don\'t Forget To Join Here For Updates!' 67 | ) 68 | reply_markup = InlineKeyboardMarkup([ 69 | [InlineKeyboardButton("Menu", callback_data="main_menu")] 70 | ]) 71 | try: 72 | await client.edit_message_text( 73 | chat_id=chat_id, 74 | message_id=message_id, 75 | text=start_message, 76 | parse_mode=ParseMode.HTML, 77 | reply_markup=reply_markup, 78 | disable_web_page_preview=True 79 | ) 80 | await callback_query.answer() 81 | LOGGER.info(f"Returned to start message for user {user_id}") 82 | except Exception as e: 83 | await callback_query.message.reply_text( 84 | "❌ Error returning to start message. Please try again.", 85 | parse_mode=ParseMode.HTML, 86 | disable_web_page_preview=True 87 | ) 88 | LOGGER.error(f"Error returning to start message for user {user_id}: {e}") 89 | 90 | elif callback_query.data == "menu_close": 91 | try: 92 | await callback_query.message.delete() 93 | await callback_query.answer() 94 | LOGGER.info(f"Menu closed for user {user_id}") 95 | except Exception as e: 96 | await callback_query.message.reply_text( 97 | "❌ Error closing menu. Please try again.", 98 | parse_mode=ParseMode.HTML, 99 | disable_web_page_preview=True 100 | ) 101 | LOGGER.error(f"Error closing menu for user {user_id}: {e}") -------------------------------------------------------------------------------- /plugins/thumb.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | import os 4 | from pyrogram import Client, filters 5 | from pyrogram.types import Message 6 | from pyrogram.enums import ParseMode 7 | from config import COMMAND_PREFIX 8 | from utils import LOGGER 9 | from pyrogram.handlers import MessageHandler 10 | from core import user_activity_collection 11 | 12 | def setup_thumb_handler(app: Client): 13 | async def setthumb_command(client: Client, message: Message): 14 | user_id = message.from_user.id 15 | if not message.reply_to_message or not message.reply_to_message.photo: 16 | await message.reply_text( 17 | "**Kindly reply to a photo to set it as default thumbnail**", 18 | parse_mode=ParseMode.MARKDOWN 19 | ) 20 | return 21 | 22 | photo = message.reply_to_message.photo 23 | # Ensure Assets directory exists 24 | os.makedirs("Assets", exist_ok=True) 25 | # Define thumbnail path 26 | thumb_path = f"Assets/{user_id}_thumb.jpg" 27 | # Download the thumbnail 28 | try: 29 | await client.download_media( 30 | photo.file_id, 31 | file_name=thumb_path 32 | ) 33 | # Store the thumbnail path in user_activity_collection 34 | user_activity_collection.update_one( 35 | {"user_id": user_id}, 36 | {"$set": {"thumbnail_path": thumb_path}}, 37 | upsert=True 38 | ) 39 | await message.reply_text( 40 | "**✅ Thumbnail set successfully!**", 41 | parse_mode=ParseMode.MARKDOWN 42 | ) 43 | LOGGER.info(f"Thumbnail set for user {user_id} at {thumb_path}") 44 | except Exception as e: 45 | await message.reply_text( 46 | "**❌ Error setting thumbnail!**", 47 | parse_mode=ParseMode.MARKDOWN 48 | ) 49 | LOGGER.error(f"Error setting thumbnail for user {user_id}: {e}") 50 | 51 | async def rmthumb_command(client: Client, message: Message): 52 | user_id = message.from_user.id 53 | user_data = user_activity_collection.find_one({"user_id": user_id}) 54 | if not user_data or "thumbnail_path" not in user_data: 55 | await message.reply_text( 56 | "**❌ You don't have any thumbnail to delete**", 57 | parse_mode=ParseMode.MARKDOWN 58 | ) 59 | return 60 | 61 | thumb_path = user_data["thumbnail_path"] 62 | try: 63 | if os.path.exists(thumb_path): 64 | os.remove(thumb_path) 65 | user_activity_collection.update_one( 66 | {"user_id": user_id}, 67 | {"$unset": {"thumbnail_path": ""}} 68 | ) 69 | await message.reply_text( 70 | "**✅ Thumbnail removed successfully!**", 71 | parse_mode=ParseMode.MARKDOWN 72 | ) 73 | LOGGER.info(f"Thumbnail removed for user {user_id}") 74 | except Exception as e: 75 | await message.reply_text( 76 | "**❌ Error removing thumbnail!**", 77 | parse_mode=ParseMode.MARKDOWN 78 | ) 79 | LOGGER.error(f"Error removing thumbnail for user {user_id}: {e}") 80 | 81 | async def getthumb_command(client: Client, message: Message): 82 | user_id = message.from_user.id 83 | user_data = user_activity_collection.find_one({"user_id": user_id}) 84 | if not user_data or "thumbnail_path" not in user_data: 85 | await message.reply_text( 86 | "**❌ Sorry, you don't have any thumbnail set**", 87 | parse_mode=ParseMode.MARKDOWN 88 | ) 89 | return 90 | 91 | thumb_path = user_data["thumbnail_path"] 92 | if os.path.exists(thumb_path): 93 | try: 94 | await client.send_photo( 95 | chat_id=message.chat.id, 96 | photo=thumb_path, 97 | caption="**✅ Your current thumbnail**", 98 | parse_mode=ParseMode.MARKDOWN 99 | ) 100 | LOGGER.info(f"Thumbnail retrieved for user {user_id}") 101 | except Exception as e: 102 | await message.reply_text( 103 | "**❌ Error retrieving thumbnail!**", 104 | parse_mode=ParseMode.MARKDOWN 105 | ) 106 | LOGGER.error(f"Error retrieving thumbnail for user {user_id}: {e}") 107 | else: 108 | await message.reply_text( 109 | "**❌ Thumbnail file missing! Please set a new one.**", 110 | parse_mode=ParseMode.MARKDOWN 111 | ) 112 | LOGGER.error(f"Thumbnail file missing for user {user_id} at {thumb_path}") 113 | 114 | app.add_handler( 115 | MessageHandler( 116 | setthumb_command, 117 | filters=filters.command("setthumb", prefixes=COMMAND_PREFIX) & (filters.private | filters.group) 118 | ), 119 | group=1 120 | ) 121 | app.add_handler( 122 | MessageHandler( 123 | rmthumb_command, 124 | filters=filters.command("rmthumb", prefixes=COMMAND_PREFIX) & (filters.private | filters.group) 125 | ), 126 | group=1 127 | ) 128 | app.add_handler( 129 | MessageHandler( 130 | getthumb_command, 131 | filters=filters.command("getthumb", prefixes=COMMAND_PREFIX) & (filters.private | filters.group) 132 | ), 133 | group=1 134 | ) -------------------------------------------------------------------------------- /auth/speedtest/speedtest.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | import asyncio 4 | import subprocess 5 | import json 6 | from concurrent.futures import ThreadPoolExecutor 7 | from pyrogram import Client, filters 8 | from pyrogram.handlers import MessageHandler 9 | from pyrogram.enums import ParseMode 10 | from pyrogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup 11 | from pyrogram.errors import FloodWait 12 | from config import DEVELOPER_USER_ID, COMMAND_PREFIX 13 | from utils import LOGGER 14 | 15 | # Helper function to convert speed to human-readable format 16 | def speed_convert(size: float, is_mbps: bool = False) -> str: 17 | if is_mbps: 18 | return f"{size:.2f} Mbps" 19 | power = 2**10 20 | n = 0 21 | power_labels = {0: '', 1: 'K', 2: 'M', 3: 'G', 4: 'T'} 22 | while size > power: 23 | size /= power 24 | n += 1 25 | return f"{size:.2f} {power_labels[n]}bps" 26 | 27 | # Helper function to convert bytes to human-readable file size 28 | def get_readable_file_size(size_in_bytes: int) -> str: 29 | if size_in_bytes < 1024: 30 | return f"{size_in_bytes} B" 31 | power = 1024 32 | n = 0 33 | power_labels = {0: 'B', 1: 'KB', 2: 'MB', 3: 'GB', 4: 'TB'} 34 | while size_in_bytes >= power: 35 | size_in_bytes /= power 36 | n += 1 37 | return f"{size_in_bytes:.2f} {power_labels[n]}" 38 | 39 | # Function to perform speed test 40 | def run_speedtest(): 41 | try: 42 | # Use speedtest-cli for detailed JSON output 43 | result = subprocess.run(["speedtest-cli", "--secure", "--json"], capture_output=True, text=True) 44 | if result.returncode != 0: 45 | raise Exception("Speedtest failed.") 46 | data = json.loads(result.stdout) 47 | return data 48 | except Exception as e: 49 | LOGGER.error(f"Speedtest error: {e}") 50 | return {"error": str(e)} 51 | 52 | # Async function to handle speed test logic 53 | async def run_speedtest_task(client: Client, chat_id: int, status_message: Message): 54 | # Run speed test in background thread 55 | with ThreadPoolExecutor() as pool: 56 | try: 57 | result = await asyncio.get_running_loop().run_in_executor(pool, run_speedtest) 58 | except Exception as e: 59 | LOGGER.error(f"Error running speedtest task: {e}") 60 | try: 61 | await status_message.edit_text( 62 | "**❌ Speed Test API Unavailable! ↯**", 63 | parse_mode=ParseMode.MARKDOWN 64 | ) 65 | except FloodWait as e: 66 | LOGGER.warning(f"FloodWait during API error message: waiting {e.value + 5} seconds") 67 | await asyncio.sleep(e.value + 5) 68 | await status_message.edit_text( 69 | "**❌ Speed Test API Unavailable! ↯**", 70 | parse_mode=ParseMode.MARKDOWN 71 | ) 72 | return 73 | 74 | if "error" in result: 75 | try: 76 | await status_message.edit_text( 77 | f"**❌ Speed Test Failed↯**", 78 | parse_mode=ParseMode.MARKDOWN 79 | ) 80 | except FloodWait as e: 81 | LOGGER.warning(f"FloodWait during failure message: waiting {e.value + 5} seconds") 82 | await asyncio.sleep(e.value + 5) 83 | await status_message.edit_text( 84 | f"**❌ Speed Test Failed↯**", 85 | parse_mode=ParseMode.MARKDOWN 86 | ) 87 | return 88 | 89 | # Format the results with project-themed design 90 | response_text = ( 91 | "**✘《 Restricted Content Downloader Speedtest ↯ 》**\n" 92 | "**✘━━━━━━━━━━━━━━━━━━━━━━━↯**\n" 93 | f"**✘ Upload Speed:** `{speed_convert(result['upload'])}`\n" 94 | f"**✘ Download Speed:** `{speed_convert(result['download'])}`\n" 95 | f"**✘ Ping:**`{result['ping']:.2f} ms`\n" 96 | f"**✘ Timestamp:** `{result['timestamp']}`\n" 97 | f"**✘ Data Sent:** `{get_readable_file_size(int(result['bytes_sent']))}`\n" 98 | f"**✘ Data Received:** `{get_readable_file_size(int(result['bytes_received']))}`\n" 99 | "**✘《 Server Info ↯ 》**\n" 100 | f"**✘ Name:** `{result['server']['name']}`\n" 101 | f"**✘ Country:** `{result['server']['country']}, {result['server']['cc']}`\n" 102 | f"**✘ Sponsor:** `{result['server']['sponsor']}`\n" 103 | f"**✘ Latency:** `{result['server']['latency']:.2f} ms`\n" 104 | f"**✘ Latitude:** `{result['server']['lat']}`\n" 105 | f"**✘ Longitude:**`{result['server']['lon']}`\n" 106 | "**✘《 Client Info ↯ 》**\n" 107 | f"**✘ IP Address:** `{result['client']['ip']}`\n" 108 | f"**✘ Latitude:** `{result['client']['lat']}`\n" 109 | f"**✘ Longitude:** `{result['client']['lon']}`\n" 110 | f"**✘ Country:** `{result['client']['country']}`\n" 111 | f"**✘ ISP:** `{result['client']['isp']}`\n" 112 | f"**✘ ISP Rating:** `{result['client'].get('isprating', 'N/A')}`\n" 113 | "**✘━━━━━━━━━━━━━━━━━━━━━━━↯**\n" 114 | "**✘ Powered by @TheSmartDev ↯**" 115 | ) 116 | 117 | # Create inline keyboard with Updates Channel button 118 | keyboard = InlineKeyboardMarkup([ 119 | [InlineKeyboardButton("✘ Updates Channel ↯", url="https://t.me/TheSmartDevs")] 120 | ]) 121 | 122 | # Delete the status message 123 | try: 124 | await status_message.delete() 125 | except FloodWait as e: 126 | LOGGER.warning(f"FloodWait during status message deletion: waiting {e.value + 5} seconds") 127 | await asyncio.sleep(e.value + 5) 128 | await status_message.delete() 129 | 130 | # Send the final result 131 | try: 132 | await client.send_message( 133 | chat_id=chat_id, 134 | text=response_text, 135 | parse_mode=ParseMode.MARKDOWN, 136 | reply_markup=keyboard 137 | ) 138 | except FloodWait as e: 139 | LOGGER.warning(f"FloodWait during result message: waiting {e.value + 5} seconds") 140 | await asyncio.sleep(e.value + 5) 141 | await client.send_message( 142 | chat_id=chat_id, 143 | text=response_text, 144 | parse_mode=ParseMode.MARKDOWN, 145 | reply_markup=keyboard 146 | ) 147 | 148 | # Handler for speed test command 149 | async def speedtest_handler(client: Client, message: Message): 150 | user_id = message.from_user.id 151 | LOGGER.info(f"/speedtest command from user {user_id}") 152 | 153 | if user_id != DEVELOPER_USER_ID: 154 | LOGGER.info("User is not developer, sending restricted message") 155 | try: 156 | await client.send_message( 157 | chat_id=message.chat.id, 158 | text="**❌ Kids Are Disallowed To Do This ↯**", 159 | parse_mode=ParseMode.MARKDOWN 160 | ) 161 | except FloodWait as e: 162 | LOGGER.warning(f"FloodWait during unauthorized message: waiting {e.value + 5} seconds") 163 | await asyncio.sleep(e.value + 5) 164 | await client.send_message( 165 | chat_id=message.chat.id, 166 | text="**❌ Kids Are Disallowed To Do This ↯**", 167 | parse_mode=ParseMode.MARKDOWN 168 | ) 169 | return 170 | 171 | # Send initial status message 172 | try: 173 | status_message = await client.send_message( 174 | chat_id=message.chat.id, 175 | text="**✘ Running Speedtest Wait ↯**", 176 | parse_mode=ParseMode.MARKDOWN 177 | ) 178 | except FloodWait as e: 179 | LOGGER.warning(f"FloodWait during status message: waiting {e.value + 5} seconds") 180 | await asyncio.sleep(e.value + 5) 181 | status_message = await client.send_message( 182 | chat_id=message.chat.id, 183 | text="**✘ Running Speedtest Wait ↯**", 184 | parse_mode=ParseMode.MARKDOWN 185 | ) 186 | 187 | # Schedule the speed test task 188 | asyncio.create_task(run_speedtest_task(client, message.chat.id, status_message)) 189 | 190 | # Setup function to add the speed test handler 191 | def setup_speed_handler(app: Client): 192 | app.add_handler(MessageHandler( 193 | speedtest_handler, 194 | filters.command("speedtest", prefixes=COMMAND_PREFIX) & (filters.private | filters.group) 195 | )) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## RestrictedContentDL 🌟 2 | 3 | A powerful Telegram bot for downloading and managing content from restricted public and private Telegram channels and groups. Built with Pyrofork, it supports seamless deployment on Heroku, VPS, or Docker, offering robust features for media scraping and user management. 💥 4 | 5 | --- 6 | 7 | ## Features 🌟 8 | 9 | - **Public & Private Content Download**: Download media from public (`/dl`) and private (`/pdl`) restricted Telegram sources. ➕ 10 | - **Batch Downloading**: Efficiently download multiple media files from public (`/bdl`) and private (`/pbdl`) channels or groups. 📈 11 | - **Custom Thumbnail Support**: Set (`/setthumb`), view (`/getthumb`), or remove (`/rmthumb`) custom thumbnails for media downloads. ⚙️ 12 | - **Premium Plan Management**: Explore plans (`/plans`), purchase (`/buy`), and check account status (`/profile`, `/info`). 💥 13 | - **Admin Controls**: Broadcast messages (`/acast`, `/gcast`), manage logs (`/logs`), restart the bot (`/restart`, `/reload`, `/reboot`), view stats (`/stats`), and manage premium users (`/add`, `/rm`). 🌐 14 | - **Dynamic Command Prefixes**: Supports multiple prefixes (`!`, `.`, `#`, `,`, `/`) for flexible command execution. ➕ 15 | - **Secure Login/Logout**: Premium users can log in (`/login`) to scrape private messages and log out (`/logout`) to clear data. 💀 16 | - **BotFather Command Customization**: Owners can set commands using `/set` within the bot. ⚙️ 17 | - **Multi-Platform Deployment**: Deploy effortlessly on Heroku, VPS, or Docker Compose with clear setup instructions. 🌐 18 | 19 | --- 20 | 21 | ## Commands 📈 22 | 23 | | Command | Description | Access | 24 | |------------|--------------------------------------------------|----------------| 25 | | `/start` | Start the Private Content Forwarder | All Users ✅ | 26 | | `/help` | Get help about commands | All Users ✅ | 27 | | `/plans` | Discover premium plans to unlock advanced features| All Users ✅ | 28 | | `/buy` | Purchase a premium plan for enhanced access | All Users ✅ | 29 | | `/profile` | Check account status and premium details | All Users ✅ | 30 | | `/getthumb`| View your custom thumbnail for media downloads | All Users ✅ | 31 | | `/setthumb`| Set or update a custom thumbnail for videos | All Users ✅ | 32 | | `/rmthumb` | Remove your custom thumbnail | All Users ✅ | 33 | | `/dl` | Download media from public restricted sources | All Users ✅ | 34 | | `/pdl` | Access and download from private restricted sources | Premium Users 💥 | 35 | | `/bdl` | Batch download from public channels or groups | All Users ✅ | 36 | | `/pbdl` | Batch download from private sources | Premium Users 💥 | 37 | | `/info` | Get detailed account information | All Users ✅ | 38 | | `/login` | Log in to scrape private messages | Premium Users 💥 | 39 | | `/logout` | Log out and clear your account data | All Users ✅ | 40 | | `/acast` | Send broadcast | Admin Only 🌐 | 41 | | `/gcast` | Send broadcast | Admin Only 🌐 | 42 | | `/logs` | Get logs of the bot | Admin Only 🌐 | 43 | | `/restart` | Restart the bot | Admin Only 🌐 | 44 | | `/reload` | Restart the bot | Admin Only 🌐 | 45 | | `/reboot` | Restart the bot | Admin Only 🌐 | 46 | | `/stats` | View total bot users | Admin Only 🌐 | 47 | | `/add` | Add user to premium | Admin Only 🌐 | 48 | | `/rm` | Remove user from premium | Admin Only 🌐 | 49 | | `/set` | Set BotFather commands | Owner Only ⚙️ | 50 | 51 | **Note**: The bot owner can customize commands using the `/set` command within the bot. ⚙️ 52 | 53 | --- 54 | 55 | ## Deployment Options 🌐 56 | 57 | ### 1. Deploy on Heroku (One-Click) 💥 58 | 59 | Deploy the bot to Heroku with a single click using the button below. Ensure you have a Heroku account and configure the environment variables as shown in the `.env` sample. ✅ 60 | 61 | [![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/TheSmartDevs/RestrictedContentDL) 62 | 63 | #### Heroku CLI Deployment ➕ 64 | 65 | 1. **Clone the Repository**: 66 | ```bash 67 | git clone https://github.com/TheSmartDevs/RestrictedContentDL.git 68 | cd RestrictedContentDL 69 | ``` 70 | 71 | 2. **Log in to Heroku**: 72 | ```bash 73 | heroku login 74 | ``` 75 | 76 | 3. **Create a Heroku App**: 77 | ```bash 78 | heroku create your-app-name 79 | ``` 80 | 81 | 4. **Set Environment Variables**: 82 | Configure the required variables from `sample.env`: 83 | ```bash 84 | heroku config:set API_ID=your_api_id 85 | heroku config:set API_HASH=your_api_hash 86 | heroku config:set BOT_TOKEN=your_bot_token 87 | heroku config:set DEVELOPER_USER_ID=your_user_id 88 | heroku config:set MONGO_URL=your_mongo_url 89 | heroku config:set DATABASE_URL=your_database_url 90 | heroku config:set DB_URL=your_db_url 91 | heroku config:set COMMAND_PREFIX="!|.|#|,|/" 92 | ``` 93 | 94 | 5. **Deploy to Heroku**: 95 | ```bash 96 | git push heroku main 97 | ``` 98 | 99 | 6. **Scale the Dyno**: 100 | ```bash 101 | heroku ps:scale worker=1 102 | ``` 103 | 104 | --- 105 | 106 | ### 2. Deploy on VPS with Screen 🌐 107 | 108 | 1. **Clone the Repository**: 109 | ```bash 110 | git clone https://github.com/TheSmartDevs/RestrictedContentDL.git 111 | cd RestrictedContentDL 112 | ``` 113 | 114 | 2. **Install Dependencies**: 115 | ```bash 116 | pip3 install -r requirements.txt 117 | ``` 118 | 119 | 3. **Set Up Environment Variables**: 120 | Copy `sample.env` to `.env` and fill in the required values: 121 | ```bash 122 | cp sample.env .env 123 | nano .env 124 | ``` 125 | 126 | 4. **Run the Bot with Screen**: 127 | ```bash 128 | screen -S restrictedcontentdl 129 | python3 -m bot 130 | ``` 131 | 132 | 5. **Detach from Screen**: 133 | Press `Ctrl+A` followed by `Ctrl+D`. 134 | 135 | 6. **Stop the Bot**: 136 | Reattach to the session and stop: 137 | ```bash 138 | screen -r restrictedcontentdl 139 | Ctrl+C 140 | ``` 141 | 142 | --- 143 | 144 | ### 3. Deploy with Docker Compose ⚙️ 145 | 146 | 1. **Install Docker and Docker Compose**: 147 | Ensure Docker and Docker Compose are installed on your system. ✅ 148 | 149 | 2. **Clone the Repository**: 150 | ```bash 151 | git clone https://github.com/TheSmartDevs/RestrictedContentDL.git 152 | cd RestrictedContentDL 153 | ``` 154 | 155 | 3. **Set Up Environment Variables**: 156 | Copy `sample.env` to `.env` and configure the required values: 157 | ```bash 158 | cp sample.env .env 159 | nano .env 160 | ``` 161 | 162 | 4. **Run the Bot**: 163 | ```bash 164 | docker compose up --build --remove-orphans 165 | ``` 166 | 167 | 5. **Stop the Bot**: 168 | ```bash 169 | docker compose down 170 | ``` 171 | 172 | --- 173 | 174 | ## Configuration ⚙️ 175 | 176 | The bot uses environment variables for configuration. Copy `sample.env` to `.env` and fill in the required values: 177 | 178 | ```env 179 | # Telegram API credentials (required) 180 | API_ID=YOUR_API_ID 181 | API_HASH=YOUR_API_HASH 182 | BOT_TOKEN=YOUR_BOT_TOKEN 183 | 184 | # Admin and owner IDs 185 | DEVELOPER_USER_ID=YOUR_USER_ID 186 | 187 | # Database URLs 188 | MONGO_URL=YOUR_MONGO_URL 189 | DATABASE_URL=YOUR_DATABASE_URL 190 | DB_URL=YOUR_DB_URL 191 | 192 | # Command prefixes for bot commands 193 | COMMAND_PREFIX=!|.|#|,|/ 194 | ``` 195 | 196 | For Heroku, set these variables via the Heroku dashboard or CLI as shown above. ✅ 197 | 198 | --- 199 | 200 | ## Main Author 🧑‍💻 201 | 202 | The Main Author is **Abir Arafat Chawdhury**, who led the base development of RestrictedContentDL. 🌟 203 | 204 | - **Name**: Abir Arafat Chawdhury 🌟 205 | - **Telegram Contact**: [@ISmartDevs](https://t.me/ISmartDevs) ✅ 206 | - **Telegram Channel**: [@TheSmartDev](https://t.me/TheSmartDev) ✅ 207 | 208 | --- 209 | 210 | ## Special Thanks 💥 211 | 212 | A special thanks to [**@TheSmartBisnu**](https://github.com/bisnuray/RestrictedContentDL) for their contributions to [**RestrictedContentDL**](https://github.com/bisnuray/RestrictedContentDL), particularly for the helper functions in [`utils.py`](https://github.com/bisnuray/RestrictedContentDL/blob/main/helpers/utils.py), which were instrumental in building this project. ✅ 213 | 214 | -------------------------------------------------------------------------------- /auth/sudo/sudo.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from pyrogram import Client, filters 4 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 5 | from pyrogram.enums import ParseMode 6 | from pyrogram.errors import ChatWriteForbidden, UserIsBlocked, InputUserDeactivated, FloodWait 7 | from datetime import datetime, timedelta 8 | import asyncio 9 | from config import COMMAND_PREFIX, DEVELOPER_USER_ID 10 | from utils import LOGGER 11 | from core import total_users 12 | 13 | def setup_sudo_handler(app: Client): 14 | async def update_user_activity(user_id: int): 15 | """Update or insert user activity timestamp in total_users collection.""" 16 | current_time = datetime.utcnow() 17 | total_users.update_one( 18 | {"user_id": user_id}, 19 | {"$set": {"user_id": user_id, "last_active": current_time}}, 20 | upsert=True 21 | ) 22 | 23 | async def get_active_users(): 24 | """Calculate active users based on time periods.""" 25 | current_time = datetime.utcnow() 26 | daily_active = total_users.count_documents({"last_active": {"$gte": current_time - timedelta(days=1)}}) 27 | weekly_active = total_users.count_documents({"last_active": {"$gte": current_time - timedelta(days=7)}}) 28 | monthly_active = total_users.count_documents({"last_active": {"$gte": current_time - timedelta(days=30)}}) 29 | annual_active = total_users.count_documents({"last_active": {"$gte": current_time - timedelta(days=365)}}) 30 | total = total_users.count_documents({}) 31 | return daily_active, weekly_active, monthly_active, annual_active, total 32 | 33 | @app.on_message(filters.command("stats", prefixes=COMMAND_PREFIX) & filters.private) 34 | async def stats_command(client: Client, message: Message): 35 | user_id = message.from_user.id 36 | if user_id != DEVELOPER_USER_ID: 37 | return # Silently ignore for non-developers 38 | 39 | await update_user_activity(user_id) 40 | LOGGER.info(f"/stats command received from developer {user_id}") 41 | 42 | daily_active, weekly_active, monthly_active, annual_active, total = await get_active_users() 43 | 44 | stats_message = ( 45 | "**✘《 Private Content Forwarder ↯ 》**\n" 46 | "**✘━━━━━━━━━━━↯**\n" 47 | f"**✘ Daily Active: {daily_active} ↯**\n" 48 | f"**✘ Weekly Active: {weekly_active} ↯**\n" 49 | f"**✘ Monthly Active: {monthly_active} ↯**\n" 50 | f"**✘ Annual Active: {annual_active} ↯**\n" 51 | "**✘━━━━━━━━━━━↯**\n" 52 | f"**✘ Total Users: {total} ↯**" 53 | ) 54 | 55 | await message.reply_text(stats_message, parse_mode=ParseMode.MARKDOWN) 56 | 57 | @app.on_message(filters.command("gcast", prefixes=COMMAND_PREFIX) & filters.private) 58 | async def gcast_command(client: Client, message: Message): 59 | user_id = message.from_user.id 60 | if user_id != DEVELOPER_USER_ID: 61 | return # Silently ignore for non-developers 62 | 63 | await update_user_activity(user_id) 64 | LOGGER.info(f"/gcast command received from developer {user_id}") 65 | 66 | if not message.reply_to_message: 67 | await message.reply_text( 68 | "**❌ Please reply to a message to broadcast!**", 69 | parse_mode=ParseMode.MARKDOWN 70 | ) 71 | return 72 | 73 | broadcast_message = message.reply_to_message 74 | start_time = datetime.utcnow() 75 | success_count = 0 76 | blocked_count = 0 77 | failed_count = 0 78 | deactivated_count = 0 79 | flood_wait_count = 0 80 | 81 | # Get all user IDs 82 | users = total_users.find({}, {"user_id": 1}) 83 | user_ids = [user["user_id"] for user in users] 84 | 85 | # Prepare inline button for gcast 86 | buttons = InlineKeyboardMarkup([[ 87 | InlineKeyboardButton("Updates Channel", url="https://t.me/TheSmartDev") 88 | ]]) 89 | 90 | for target_user_id in user_ids: 91 | while True: 92 | try: 93 | sent_message = await client.copy_message( 94 | chat_id=target_user_id, 95 | from_chat_id=user_id, 96 | message_id=broadcast_message.id, 97 | reply_markup=buttons, 98 | parse_mode=ParseMode.MARKDOWN if broadcast_message.text or broadcast_message.caption else None 99 | ) 100 | try: 101 | await client.pin_chat_message(target_user_id, sent_message.id, both_sides=True) 102 | except Exception as e: 103 | LOGGER.warning(f"Failed to pin gcast message for user {target_user_id}: {e}") 104 | success_count += 1 105 | await asyncio.sleep(0.5) # Prevent rate-limiting 106 | break 107 | except FloodWait as e: 108 | flood_wait_count += 1 109 | wait_time = e.value + 5 # Add buffer 110 | LOGGER.warning(f"FloodWait triggered for user {target_user_id} during gcast: waiting {wait_time} seconds") 111 | await asyncio.sleep(wait_time) 112 | continue 113 | except UserIsBlocked: 114 | blocked_count += 1 115 | LOGGER.info(f"User {target_user_id} blocked the bot during gcast") 116 | break 117 | except InputUserDeactivated: 118 | deactivated_count += 1 119 | LOGGER.info(f"User {target_user_id} is deactivated during gcast") 120 | break 121 | except Exception as e: 122 | failed_count += 1 123 | LOGGER.error(f"Failed to send gcast to user {target_user_id}: {e}") 124 | break 125 | 126 | # Send report to developer 127 | time_taken = (datetime.utcnow() - start_time).total_seconds() 128 | report_message = ( 129 | "**📢 Global Broadcast Report ↯**\n" 130 | "**✘━━━━━━━━━━━↯**\n" 131 | f"**✘ Successful: {success_count} ↯**\n" 132 | f"**✘ Blocked: {blocked_count} ↯**\n" 133 | f"**✘ Deactivated: {deactivated_count} ↯**\n" 134 | f"**✘ Failed: {failed_count} ↯**\n" 135 | f"**✘ Flood Waits: {flood_wait_count} ↯**\n" 136 | f"**✘ Time Taken: {int(time_taken)} seconds ↯**\n" 137 | "**✘━━━━━━━━━━━↯**\n" 138 | "**✅ Broadcast completed!**" 139 | ) 140 | 141 | await client.send_message( 142 | chat_id=user_id, 143 | text=report_message, 144 | parse_mode=ParseMode.MARKDOWN 145 | ) 146 | LOGGER.info(f"Gcast completed: {success_count} successes, {blocked_count} blocked, {deactivated_count} deactivated, {failed_count} failed, {flood_wait_count} flood waits") 147 | 148 | @app.on_message(filters.command("acast", prefixes=COMMAND_PREFIX) & filters.private) 149 | async def acast_command(client: Client, message: Message): 150 | user_id = message.from_user.id 151 | if user_id != DEVELOPER_USER_ID: 152 | return # Silently ignore for non-developers 153 | 154 | await update_user_activity(user_id) 155 | LOGGER.info(f"/acast command received from developer {user_id}") 156 | 157 | if not message.reply_to_message: 158 | await message.reply_text( 159 | "**❌ Please reply to a message to broadcast!**", 160 | parse_mode=ParseMode.MARKDOWN 161 | ) 162 | return 163 | 164 | broadcast_message = message.reply_to_message 165 | start_time = datetime.utcnow() 166 | success_count = 0 167 | blocked_count = 0 168 | failed_count = 0 169 | deactivated_count = 0 170 | flood_wait_count = 0 171 | 172 | # Get all user IDs 173 | users = total_users.find({}, {"user_id": 1}) 174 | user_ids = [user["user_id"] for user in users] 175 | 176 | for target_user_id in user_ids: 177 | while True: 178 | try: 179 | sent_message = await client.forward_messages( 180 | chat_id=target_user_id, 181 | from_chat_id=user_id, 182 | message_ids=broadcast_message.id 183 | ) 184 | try: 185 | await client.pin_chat_message(target_user_id, sent_message.id, both_sides=True) 186 | except Exception as e: 187 | LOGGER.warning(f"Failed to pin acast message for user {target_user_id}: {e}") 188 | success_count += 1 189 | await asyncio.sleep(0.5) # Prevent rate-limiting 190 | break 191 | except FloodWait as e: 192 | flood_wait_count += 1 193 | wait_time = e.value + 5 # Add buffer 194 | LOGGER.warning(f"FloodWait triggered for user {target_user_id} during acast: waiting {wait_time} seconds") 195 | await asyncio.sleep(wait_time) 196 | continue 197 | except UserIsBlocked: 198 | blocked_count += 1 199 | LOGGER.info(f"User {target_user_id} blocked the bot during acast") 200 | break 201 | except InputUserDeactivated: 202 | deactivated_count += 1 203 | LOGGER.info(f"User {target_user_id} is deactivated during acast") 204 | break 205 | except Exception as e: 206 | failed_count += 1 207 | LOGGER.error(f"Failed to send acast to user {target_user_id}: {e}") 208 | break 209 | 210 | # Send report to developer 211 | time_taken = (datetime.utcnow() - start_time).total_seconds() 212 | report_message = ( 213 | "**📢 Admin Broadcast Report ↯**\n" 214 | "**✘━━━━━━━━━━━↯**\n" 215 | f"**✘ Successful: {success_count} ↯**\n" 216 | f"**✘ Blocked: {blocked_count} ↯**\n" 217 | f"**✘ Deactivated: {deactivated_count} ↯**\n" 218 | f"**✘ Failed: {failed_count} ↯**\n" 219 | f"**✘ Flood Waits: {flood_wait_count} ↯**\n" 220 | f"**✘ Time Taken: {int(time_taken)} seconds ↯**\n" 221 | "**✘━━━━━━━━━━━↯**\n" 222 | "**✅ Broadcast completed!**" 223 | ) 224 | 225 | await client.send_message( 226 | chat_id=user_id, 227 | text=report_message, 228 | parse_mode=ParseMode.MARKDOWN 229 | ) 230 | LOGGER.info(f"Acast completed: {success_count} successes, {blocked_count} blocked, {deactivated_count} deactivated, {failed_count} failed, {flood_wait_count} flood waits") 231 | 232 | app.add_handler(app.on_message, group=1) -------------------------------------------------------------------------------- /auth/logs/logs.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | import os 4 | import asyncio 5 | import logging 6 | from pyrogram import Client, filters 7 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery 8 | from pyrogram.enums import ParseMode 9 | from telegraph import Telegraph 10 | from config import DEVELOPER_USER_ID, COMMAND_PREFIX 11 | from utils import LOGGER 12 | 13 | # Setup logging 14 | logging.basicConfig(level=logging.INFO) 15 | logger = LOGGER 16 | 17 | # Initialize Telegraph client 18 | telegraph = Telegraph() 19 | telegraph.create_account( 20 | short_name="RestrictedContentDL", 21 | author_name="Restricted Content Downloader", 22 | author_url="https://t.me/TheSmartDevs" 23 | ) 24 | 25 | def setup_logs_handler(app: Client): 26 | """Set up handlers for logs command and callback queries.""" 27 | 28 | async def create_telegraph_page(content: str) -> list: 29 | """Create Telegraph pages with the given content, each under 20 KB, and return list of URLs.""" 30 | try: 31 | truncated_content = content[:40000] # Limit to avoid Telegraph issues 32 | content_bytes = truncated_content.encode('utf-8') 33 | max_size_bytes = 20 * 1024 # 20 KB limit per page 34 | pages = [] 35 | page_content = "" 36 | current_size = 0 37 | lines = truncated_content.splitlines(keepends=True) 38 | 39 | for line in lines: 40 | line_bytes = line.encode('utf-8') 41 | if current_size + len(line_bytes) > max_size_bytes and page_content: 42 | response = telegraph.create_page( 43 | title="RestrictedContentLogs", 44 | html_content=f"
{page_content}
", 45 | author_name="Restricted Content Downloader", 46 | author_url="https://t.me/TheSmartDevs" 47 | ) 48 | pages.append(f"https://telegra.ph/{response['path']}") 49 | page_content = "" 50 | current_size = 0 51 | page_content += line 52 | current_size += len(line_bytes) 53 | 54 | if page_content: 55 | response = telegraph.create_page( 56 | title="RestrictedContentLogs", 57 | html_content=f"
{page_content}
", 58 | author_name="Restricted Content Downloader", 59 | author_url="https://t.me/TheSmartDevs" 60 | ) 61 | pages.append(f"https://telegra.ph/{response['path']}") 62 | 63 | return pages 64 | except Exception as e: 65 | logger.error(f"Failed to create Telegraph page: {e}") 66 | return [] 67 | 68 | @app.on_message(filters.command(["logs"], prefixes=COMMAND_PREFIX) & (filters.private | filters.group)) 69 | async def logs_command(client: Client, message): 70 | """Handle /logs command to send or display bot logs.""" 71 | user_id = message.from_user.id 72 | logger.info(f"/logs command received from user {user_id}") 73 | 74 | if user_id != DEVELOPER_USER_ID: 75 | logger.info("User is not developer, sending restricted message") 76 | await client.send_message( 77 | chat_id=message.chat.id, 78 | text="**❌ Unauthorized Access Denied! Only the Developer Can View Logs! ↯**", 79 | parse_mode=ParseMode.MARKDOWN 80 | ) 81 | return 82 | 83 | loading_message = await client.send_message( 84 | chat_id=message.chat.id, 85 | text="**📜 Fetching Restricted Content Logs... ↯**", 86 | parse_mode=ParseMode.MARKDOWN 87 | ) 88 | 89 | await asyncio.sleep(2) 90 | 91 | if not os.path.exists("botlog.txt"): 92 | await loading_message.edit_text( 93 | text="**❌ No Logs Found! ↯**", 94 | parse_mode=ParseMode.MARKDOWN 95 | ) 96 | await asyncio.sleep(3) 97 | await loading_message.delete() 98 | return 99 | 100 | logger.info("User is developer, sending log document") 101 | response = await client.send_document( 102 | chat_id=message.chat.id, 103 | document="botlog.txt", 104 | caption=( 105 | "**✘ Restricted Content Downloader Logs ↯**\n" 106 | "**✘━━━━━━━━━━━━━━━━━━━━━━━↯**\n" 107 | "**✘ Logs Exported Successfully! ↯**\n" 108 | "**✘ Access Restricted to Developer Only ↯**\n" 109 | "**✘━━━━━━━━━━━━━━━━━━━━━━━↯**\n" 110 | "**✘ Choose an Option to View Logs:**\n" 111 | "**✘ Fastest Access via Inline Display or Web Paste ↯**\n" 112 | "**✘━━━━━━━━━━━━━━━━━━━━━━━↯**\n" 113 | "**✘ Developer Access Granted ↯**" 114 | ), 115 | parse_mode=ParseMode.MARKDOWN, 116 | reply_markup=InlineKeyboardMarkup([ 117 | [ 118 | InlineKeyboardButton("✘ Show Logs ↯", callback_data="display_logs"), 119 | InlineKeyboardButton("✘ Web Paste ↯", callback_data="web_paste$") 120 | ], 121 | [InlineKeyboardButton("✘ Close ↯", callback_data="close_doc$")] 122 | ]) 123 | ) 124 | 125 | await loading_message.delete() 126 | return response 127 | 128 | @app.on_callback_query(filters.regex(r"^(close_doc\$|close_logs\$|web_paste\$|display_logs)$")) 129 | async def handle_callback(client: Client, query: CallbackQuery): 130 | """Handle callback queries for log actions.""" 131 | user_id = query.from_user.id 132 | data = query.data 133 | logger.info(f"Callback query from user {user_id}, data: {data}") 134 | 135 | if user_id != DEVELOPER_USER_ID: 136 | logger.info("User is not developer, sending callback answer") 137 | await query.answer( 138 | text="❌ Unauthorized! Only the Developer Can Access Logs! ↯", 139 | show_alert=True 140 | ) 141 | return 142 | 143 | logger.info("User is developer, processing callback") 144 | if data == "close_doc$": 145 | await query.message.delete() 146 | await query.answer() 147 | return 148 | elif data == "close_logs$": 149 | await query.message.delete() 150 | await query.answer() 151 | return 152 | elif data == "web_paste$": 153 | await query.answer("Uploading logs to Telegraph...") 154 | await query.message.edit_caption( 155 | caption="**✘ Uploading Logs to Telegraph ↯**", 156 | parse_mode=ParseMode.MARKDOWN 157 | ) 158 | if not os.path.exists("botlog.txt"): 159 | await query.message.edit_caption( 160 | caption="**❌ No Logs Found! ↯**", 161 | parse_mode=ParseMode.MARKDOWN 162 | ) 163 | await query.answer() 164 | return 165 | try: 166 | with open("botlog.txt", "r", encoding="utf-8") as f: 167 | logs_content = f.read() 168 | telegraph_urls = await create_telegraph_page(logs_content) 169 | if telegraph_urls: 170 | buttons = [] 171 | for i in range(0, len(telegraph_urls), 2): 172 | row = [ 173 | InlineKeyboardButton(f"✘ Web Part {i+1} ↯", url=telegraph_urls[i]) 174 | ] 175 | if i + 1 < len(telegraph_urls): 176 | row.append(InlineKeyboardButton(f"✘ Web Part {i+2} ↯", url=telegraph_urls[i+1])) 177 | buttons.append(row) 178 | buttons.append([InlineKeyboardButton("✘ Close ↯", callback_data="close_doc$")]) 179 | await query.message.edit_caption( 180 | caption=( 181 | "**✘ Restricted Content Downloader Logs ↯**\n" 182 | "**✘━━━━━━━━━━━━━━━━━━━━━━━↯**\n" 183 | "**✘ Logs Uploaded to Telegraph! ↯**\n" 184 | "**✘ Access Restricted to Developer Only ↯**\n" 185 | "**✘━━━━━━━━━━━━━━━━━━━━━━━↯**\n" 186 | "**✘ Select a Page to View Logs:**\n" 187 | "**✘ Web Paste for Easy Access ↯**\n" 188 | "**✘━━━━━━━━━━━━━━━━━━━━━━━↯**\n" 189 | "**✘ Developer Access Granted ↯**" 190 | ), 191 | parse_mode=ParseMode.MARKDOWN, 192 | reply_markup=InlineKeyboardMarkup(buttons) 193 | ) 194 | else: 195 | await query.message.edit_caption( 196 | caption="**❌ Unable to Upload to Telegraph! ↯**", 197 | parse_mode=ParseMode.MARKDOWN 198 | ) 199 | except Exception as e: 200 | logger.error(f"Error uploading to Telegraph: {e}") 201 | await query.message.edit_caption( 202 | caption="**❌ Unable to Upload to Telegraph! ↯**", 203 | parse_mode=ParseMode.MARKDOWN 204 | ) 205 | return 206 | elif data == "display_logs": 207 | await send_logs_page(client, query.message.chat.id, query) 208 | return 209 | 210 | async def send_logs_page(client: Client, chat_id: int, query: CallbackQuery): 211 | """Send the last 20 lines of botlog.txt, respecting Telegram's 4096-character limit.""" 212 | logger.info(f"Sending latest logs to chat {chat_id}") 213 | if not os.path.exists("botlog.txt"): 214 | await client.send_message( 215 | chat_id=chat_id, 216 | text="**❌ No Logs Found! ↯**", 217 | parse_mode=ParseMode.MARKDOWN 218 | ) 219 | return 220 | try: 221 | with open("botlog.txt", "r", encoding="utf-8") as f: 222 | logs = f.readlines() 223 | latest_logs = logs[-20:] if len(logs) > 20 else logs 224 | text = "".join(latest_logs) 225 | if len(text) > 4096: 226 | text = text[-4096:] 227 | await client.send_message( 228 | chat_id=chat_id, 229 | text=text if text else "**❌ No Logs Available! ↯**", 230 | parse_mode=ParseMode.DISABLED, 231 | reply_markup=InlineKeyboardMarkup([ 232 | [InlineKeyboardButton("✘ Back ↯", callback_data="close_logs$")] 233 | ]) 234 | ) 235 | except Exception as e: 236 | logger.error(f"Error sending logs: {e}") 237 | await client.send_message( 238 | chat_id=chat_id, 239 | text="**❌ Server Error While Fetching Logs! ↯**", 240 | parse_mode=ParseMode.MARKDOWN 241 | ) -------------------------------------------------------------------------------- /plugins/pvt.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | import os 4 | from time import time 5 | from pyrogram import Client, filters 6 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 7 | from pyrogram.enums import ParseMode 8 | from pyrogram.errors import PeerIdInvalid, BadRequest, SessionPasswordNeeded 9 | from pyleaves import Leaves 10 | from datetime import datetime 11 | from utils import ( 12 | getChatMsgID, 13 | processMediaGroup, 14 | get_parsed_msg, 15 | fileSizeLimit, 16 | progressArgs, 17 | send_media, 18 | get_readable_file_size, 19 | get_readable_time, 20 | ) 21 | from utils.logging_setup import LOGGER 22 | from config import COMMAND_PREFIX 23 | from core import prem_plan1, prem_plan2, prem_plan3, user_sessions, user_activity_collection 24 | 25 | # Client for user session (initialized dynamically) 26 | user = None 27 | pdl_data = {} 28 | 29 | def setup_pvt_handler(app: Client): 30 | async def is_premium_user(user_id: int) -> bool: 31 | current_time = datetime.utcnow() 32 | for plan_collection in [prem_plan1, prem_plan2, prem_plan3]: 33 | plan = plan_collection.find_one({"user_id": user_id}) 34 | if plan and plan.get("expiry_date", current_time) > current_time: 35 | return True 36 | return False 37 | 38 | async def get_user_client(user_id: int, session_id: str) -> Client: 39 | global user 40 | user_session = user_sessions.find_one({"user_id": user_id}) 41 | if not user_session or not user_session.get("sessions"): 42 | return None 43 | session = next((s for s in user_session["sessions"] if s["session_id"] == session_id), None) 44 | if not session: 45 | return None 46 | if user is not None: 47 | try: 48 | await user.stop() 49 | except Exception as e: 50 | LOGGER.error(f"Error stopping existing user client for user {user_id}: {e}") 51 | user = None 52 | try: 53 | user = Client( 54 | f"user_session_{user_id}_{session_id}", 55 | workers=1000, 56 | session_string=session["session_string"] 57 | ) 58 | await user.start() 59 | return user 60 | except Exception as e: 61 | LOGGER.error(f"Failed to initialize user client for user {user_id}, session {session_id}: {e}") 62 | return None 63 | 64 | async def show_account_selection(client: Client, message: Message, post_url: str = None): 65 | user_id = message.from_user.id 66 | user_session = user_sessions.find_one({"user_id": user_id}) 67 | if not user_session or not user_session.get("sessions"): 68 | return None 69 | 70 | sessions = user_session.get("sessions", []) 71 | if len(sessions) == 1: 72 | return sessions[0]["session_id"] # Auto-select if only one account 73 | 74 | # Store pdl data 75 | pdl_data[message.chat.id] = {"post_url": post_url, "message_id": message.id} 76 | 77 | # Create inline buttons (two per row) 78 | buttons = [] 79 | for i in range(0, len(sessions), 2): 80 | row = [] 81 | for session in sessions[i:i+2]: 82 | row.append(InlineKeyboardButton( 83 | session["account_name"], 84 | callback_data=f"pdl_select_{session['session_id']}" 85 | )) 86 | buttons.append(row) 87 | buttons.append([InlineKeyboardButton("Cancel", callback_data="pdl_cancel")]) 88 | 89 | await message.reply_text( 90 | "**📤 Select an account to use for private download:**", 91 | reply_markup=InlineKeyboardMarkup(buttons), 92 | parse_mode=ParseMode.MARKDOWN 93 | ) 94 | return None 95 | 96 | @app.on_message(filters.command("pdl", prefixes=COMMAND_PREFIX) & filters.private) 97 | async def handle_pdl(bot: Client, message: Message): 98 | user_id = message.from_user.id 99 | 100 | # Check if user is premium 101 | if not await is_premium_user(user_id): 102 | LOGGER.warning(f"Non-premium user {user_id} attempted /pdl") 103 | await message.reply_text( 104 | "**❌ Only premium users can use /pdl! Please upgrade: /plans**", 105 | parse_mode=ParseMode.MARKDOWN 106 | ) 107 | return 108 | 109 | # Check if user has logged-in accounts 110 | user_session = user_sessions.find_one({"user_id": user_id}) 111 | if not user_session or not user_session.get("sessions"): 112 | await message.reply_text( 113 | "**❌ You must log in with /login to use /pdl!**", 114 | parse_mode=ParseMode.MARKDOWN 115 | ) 116 | LOGGER.warning(f"User {user_id} not logged in for /pdl") 117 | return 118 | 119 | post_url = message.command[1] if len(message.command) > 1 else None 120 | if post_url and "?" in post_url: 121 | post_url = post_url.split("?", 1)[0] 122 | 123 | # Show account selection if multiple accounts 124 | selected_session_id = await show_account_selection(bot, message, post_url) 125 | if selected_session_id: 126 | await process_pdl(bot, message, selected_session_id, post_url) 127 | 128 | @app.on_callback_query(filters.regex(r"^(pdl_select_|pdl_cancel)")) 129 | async def pdl_callback_handler(client, callback_query): 130 | data = callback_query.data 131 | chat_id = callback_query.message.chat.id 132 | user_id = callback_query.from_user.id 133 | 134 | if data == "pdl_cancel": 135 | await callback_query.message.edit_text( 136 | "**❌ Private download cancelled.**", 137 | parse_mode=ParseMode.MARKDOWN 138 | ) 139 | if chat_id in pdl_data: 140 | del pdl_data[chat_id] 141 | return 142 | 143 | if data.startswith("pdl_select_"): 144 | session_id = data.split("_", 2)[2] 145 | pdl_info = pdl_data.get(chat_id, {}) 146 | post_url = pdl_info.get("post_url") 147 | original_message_id = pdl_info.get("message_id") 148 | 149 | # Fetch original message to reply to it 150 | original_message = await client.get_messages(chat_id, original_message_id) 151 | await callback_query.message.delete() # Remove selection message 152 | 153 | await process_pdl(client, original_message, session_id, post_url) 154 | if chat_id in pdl_data: 155 | del pdl_data[chat_id] 156 | 157 | async def process_pdl(bot: Client, message: Message, session_id: str, post_url: str): 158 | user_id = message.from_user.id 159 | 160 | # Get user client 161 | user_client = await get_user_client(user_id, session_id) 162 | if user_client is None: 163 | await message.reply_text( 164 | "**❌ Failed to initialize user client! Please try logging in again.**", 165 | parse_mode=ParseMode.MARKDOWN 166 | ) 167 | LOGGER.warning(f"Failed to initialize user client for user {user_id}, session {session_id}") 168 | return 169 | 170 | if not post_url: 171 | await message.reply_text( 172 | "**❌ Invalid format! Usage: /pdl {post_url}**", 173 | parse_mode=ParseMode.MARKDOWN 174 | ) 175 | LOGGER.warning(f"Invalid /pdl format by user {user_id}") 176 | return 177 | 178 | try: 179 | chat_id, message_id = getChatMsgID(post_url) 180 | chat_message = await user_client.get_messages(chat_id=chat_id, message_ids=message_id) 181 | 182 | LOGGER.info(f"Downloading media from URL: {post_url} for user {user_id}") 183 | 184 | if chat_message.document or chat_message.video or chat_message.audio: 185 | file_size = ( 186 | chat_message.document.file_size 187 | if chat_message.document 188 | else chat_message.video.file_size 189 | if chat_message.video 190 | else chat_message.audio.file_size 191 | ) 192 | 193 | if not await fileSizeLimit( 194 | file_size, message, "download", await is_premium_user(user_id) 195 | ): 196 | return 197 | 198 | parsed_caption = await get_parsed_msg( 199 | chat_message.caption or "", chat_message.caption_entities 200 | ) 201 | parsed_text = await get_parsed_msg( 202 | chat_message.text or "", chat_message.entities 203 | ) 204 | 205 | if chat_message.media_group_id: 206 | if not await processMediaGroup(chat_message, bot, message): 207 | await message.reply_text( 208 | "**❌ Could not extract any valid media from the media group.**", 209 | parse_mode=ParseMode.MARKDOWN 210 | ) 211 | return 212 | 213 | elif chat_message.media: 214 | start_time = time() 215 | progress_message = await message.reply_text("**📥 Downloading Progress...**", parse_mode=ParseMode.MARKDOWN) 216 | 217 | media_path = await chat_message.download( 218 | progress=Leaves.progress_for_pyrogram, 219 | progress_args=progressArgs( 220 | "📥 Downloading Progress", progress_message, start_time 221 | ), 222 | ) 223 | 224 | LOGGER.info(f"Downloaded media: {media_path} for user {user_id}") 225 | 226 | # Fetch user's thumbnail path 227 | user_data = user_activity_collection.find_one({"user_id": user_id}) 228 | thumbnail_path = user_data.get("thumbnail_path") if user_data else None 229 | 230 | media_type = ( 231 | "photo" 232 | if chat_message.photo 233 | else "video" 234 | if chat_message.video 235 | else "audio" 236 | if chat_message.audio 237 | else "document" 238 | ) 239 | await send_media( 240 | bot, 241 | message, 242 | media_path, 243 | media_type, 244 | parsed_caption, 245 | progress_message, 246 | start_time, 247 | thumbnail_path=thumbnail_path 248 | ) 249 | 250 | if os.path.exists(media_path): 251 | os.remove(media_path) 252 | await progress_message.delete() 253 | 254 | # Send reminder for premium users 255 | if await is_premium_user(user_id): 256 | await message.reply_text( 257 | "**✅ As a premium user, you have unlimited credits!**", 258 | parse_mode=ParseMode.MARKDOWN 259 | ) 260 | 261 | elif chat_message.text or chat_message.caption: 262 | await message.reply_text(parsed_text or parsed_caption, parse_mode=ParseMode.MARKDOWN) 263 | else: 264 | await message.reply_text( 265 | "**❌ No media or text found in the post URL.**", 266 | parse_mode=ParseMode.MARKDOWN 267 | ) 268 | 269 | except (PeerIdInvalid, BadRequest): 270 | await message.reply_text( 271 | "**❌ Make sure the user client is part of the chat.**", 272 | parse_mode=ParseMode.MARKDOWN 273 | ) 274 | LOGGER.error(f"User {user_id} not part of chat for URL: {post_url}") 275 | except Exception as e: 276 | error_message = f"**❌ {str(e)}**" 277 | await message.reply_text(error_message, parse_mode=ParseMode.MARKDOWN) 278 | LOGGER.error(f"Error in /pdl for user {user_id}: {e}") 279 | 280 | app.add_handler(app.on_message, group=1) 281 | app.add_handler(app.on_callback_query, group=2) -------------------------------------------------------------------------------- /plugins/public.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from pyrogram import Client, filters 4 | from pyrogram.types import Message 5 | from pyrogram.enums import ParseMode, ChatType 6 | from pyrogram.handlers import MessageHandler 7 | from pyrogram.errors import ChannelInvalid, ChannelPrivate, PeerIdInvalid, FileReferenceExpired 8 | from config import COMMAND_PREFIX 9 | from utils import LOGGER 10 | from core import daily_limit, prem_plan1, prem_plan2, prem_plan3, user_activity_collection 11 | from datetime import datetime, timedelta 12 | import re 13 | import asyncio 14 | 15 | def setup_public_handler(app: Client): 16 | async def dl_command(client: Client, message: Message): 17 | user_id = message.from_user.id 18 | chat_id = message.chat.id 19 | 20 | # Extract URL from command 21 | if len(message.command) < 2: 22 | await message.reply_text( 23 | "**Please provide a valid URL! Usage: /dl {url}**", 24 | parse_mode=ParseMode.MARKDOWN 25 | ) 26 | LOGGER.warning(f"No URL provided in /dl command by user {user_id}") 27 | return 28 | 29 | url = message.command[1] 30 | # Handle t.me and telegram.me URLs, including private links (t.me/c/) 31 | match = re.match(r"(?:https?://)?(?:t\.me|telegram\.me)/(?:c/)?([a-zA-Z0-9_]+)/(\d+)", url) 32 | if not match: 33 | await message.reply_text( 34 | "**Invalid URL! Please use a valid Telegram message link**", 35 | parse_mode=ParseMode.MARKDOWN 36 | ) 37 | LOGGER.warning(f"Invalid URL format: {url} by user {user_id}") 38 | return 39 | 40 | channel_username, message_id = match.groups() 41 | message_id = int(message_id) 42 | is_private = "c/" in url 43 | 44 | # Check if it's a private link 45 | if is_private: 46 | await message.reply_text( 47 | "**Private links require a premium plan and login! Use /login and upgrade: /plans**", 48 | parse_mode=ParseMode.MARKDOWN 49 | ) 50 | LOGGER.warning(f"Private link {url} attempted by user {user_id} without login") 51 | return 52 | 53 | # Handle public links 54 | if not channel_username.startswith("@"): 55 | channel_username = f"@{channel_username}" 56 | 57 | # Check if user has a premium plan 58 | is_premium = ( 59 | prem_plan1.find_one({"user_id": user_id}) or 60 | prem_plan2.find_one({"user_id": user_id}) or 61 | prem_plan3.find_one({"user_id": user_id}) 62 | ) 63 | 64 | # Send processing message 65 | processing_msg = await message.reply_text( 66 | "**Downloading restricted media ⏳**", 67 | parse_mode=ParseMode.MARKDOWN 68 | ) 69 | await asyncio.sleep(0.1) # Small delay to prevent rate-limiting 70 | 71 | # Check channel accessibility 72 | try: 73 | chat = await client.get_chat(channel_username) 74 | if chat.type not in [ChatType.CHANNEL, ChatType.SUPERGROUP]: 75 | await client.edit_message_text( 76 | chat_id=chat_id, 77 | message_id=processing_msg.id, 78 | text="**This command only supports channels or supergroups!**", 79 | parse_mode=ParseMode.MARKDOWN 80 | ) 81 | LOGGER.error(f"Invalid chat type for {channel_username}: {chat.type}") 82 | return 83 | 84 | except (ChannelInvalid, PeerIdInvalid): 85 | await client.edit_message_text( 86 | chat_id=chat_id, 87 | message_id=processing_msg.id, 88 | text="**Invalid channel or group! Ensure it's public and accessible.**", 89 | parse_mode=ParseMode.MARKDOWN 90 | ) 91 | LOGGER.error(f"Invalid channel or group: {channel_username}") 92 | return 93 | except ChannelPrivate: 94 | if not is_premium: 95 | await client.edit_message_text( 96 | chat_id=chat_id, 97 | message_id=processing_msg.id, 98 | text="**This channel is private! Upgrade to premium and use /login: /plans**", 99 | parse_mode=ParseMode.MARKDOWN 100 | ) 101 | LOGGER.error(f"Private channel {channel_username} attempted by free user {user_id}") 102 | return 103 | except Exception as e: 104 | await client.edit_message_text( 105 | chat_id=chat_id, 106 | message_id=processing_msg.id, 107 | text="**Error accessing the channel! Ensure the link is correct and the channel is accessible.**", 108 | parse_mode=ParseMode.MARKDOWN 109 | ) 110 | LOGGER.error(f"Failed to fetch chat {channel_username}: {e}") 111 | return 112 | 113 | # Check daily limit for free users 114 | if not is_premium: 115 | today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) 116 | user_limit = daily_limit.find_one({"user_id": user_id}) 117 | if user_limit and user_limit.get("date") >= today: 118 | downloads = user_limit.get("downloads", 0) 119 | if downloads >= 10: 120 | await client.edit_message_text( 121 | chat_id=chat_id, 122 | message_id=processing_msg.id, 123 | text="**Daily limit of 10 downloads reached! Upgrade to premium for more: /plans**", 124 | parse_mode=ParseMode.MARKDOWN 125 | ) 126 | LOGGER.info(f"Daily limit reached for user {user_id}") 127 | return 128 | daily_limit.update_one( 129 | {"user_id": user_id}, 130 | { 131 | "$set": {"downloads": downloads + 1, "date": today}, 132 | "$inc": {"total_downloads": 1} 133 | }, 134 | upsert=True 135 | ) 136 | else: 137 | daily_limit.update_one( 138 | {"user_id": user_id}, 139 | { 140 | "$set": {"downloads": 1, "date": today}, 141 | "$inc": {"total_downloads": 1} 142 | }, 143 | upsert=True 144 | ) 145 | remaining = 10 - (user_limit.get("downloads", 0) + 1 if user_limit else 1) 146 | else: 147 | daily_limit.update_one( 148 | {"user_id": user_id}, 149 | {"$inc": {"total_downloads": 1}}, 150 | upsert=True 151 | ) 152 | remaining = None # No limit for premium users 153 | 154 | # Fetch and copy the message 155 | try: 156 | source_message = await client.get_messages(channel_username, message_id) 157 | if not source_message: 158 | await client.edit_message_text( 159 | chat_id=chat_id, 160 | message_id=processing_msg.id, 161 | text="Message not found or deleted!", 162 | parse_mode=ParseMode.MARKDOWN 163 | ) 164 | LOGGER.error(f"Message {message_id} not found in {channel_username}") 165 | return 166 | 167 | # Check if the message contains a video 168 | if source_message.video: 169 | user_data = user_activity_collection.find_one({"user_id": user_id}) 170 | thumbnail_file_id = user_data.get("thumbnail_file_id") if user_data else None 171 | try: 172 | if thumbnail_file_id: 173 | # Validate thumbnail by sending it as a photo 174 | try: 175 | test_photo = await client.send_photo( 176 | chat_id=user_id, 177 | photo=thumbnail_file_id, 178 | caption="Validating thumbnail...", 179 | parse_mode=ParseMode.MARKDOWN 180 | ) 181 | await test_photo.delete() # Clean up test message 182 | except Exception as e: 183 | thumbnail_file_id = None 184 | LOGGER.warning(f"Invalid thumbnail file_id for user {user_id}: {e}") 185 | await message.reply_text( 186 | "**Invalid or expired thumbnail! Please set a new one with /setthumb.**", 187 | parse_mode=ParseMode.MARKDOWN 188 | ) 189 | 190 | await client.send_video( 191 | chat_id=chat_id, 192 | video=source_message.video.file_id, 193 | caption=source_message.caption or "", 194 | parse_mode=ParseMode.MARKDOWN if source_message.caption else None, 195 | thumb=thumbnail_file_id if thumbnail_file_id else None 196 | ) 197 | LOGGER.info(f"Sent video with {'custom' if thumbnail_file_id else 'default'} thumbnail for user {user_id}") 198 | except FileReferenceExpired: 199 | LOGGER.error(f"Thumbnail file reference expired for user {user_id}") 200 | await client.send_video( 201 | chat_id=chat_id, 202 | video=source_message.video.file_id, 203 | caption=source_message.caption or "", 204 | parse_mode=ParseMode.MARKDOWN if source_message.caption else None 205 | ) 206 | await message.reply_text( 207 | "Custom thumbnail expired! Please set a new one with /setthumb.", 208 | parse_mode=ParseMode.MARKDOWN 209 | ) 210 | LOGGER.info(f"Sent video with default thumbnail for user {user_id} due to expired thumbnail") 211 | except Exception as e: 212 | LOGGER.error(f"Failed to send video with thumbnail for user {user_id}: {e}") 213 | await client.send_video( 214 | chat_id=chat_id, 215 | video=source_message.video.file_id, 216 | caption=source_message.caption or "", 217 | parse_mode=ParseMode.MARKDOWN if source_message.caption else None 218 | ) 219 | await message.reply_text( 220 | "Error applying thumbnail! Using default thumbnail.", 221 | parse_mode=ParseMode.MARKDOWN 222 | ) 223 | LOGGER.info(f"Sent video with default thumbnail for user {user_id} due to error") 224 | else: 225 | # Copy non-video messages directly 226 | await client.copy_message( 227 | chat_id=chat_id, 228 | from_chat_id=channel_username, 229 | message_id=message_id 230 | ) 231 | 232 | reminder_text = ( 233 | f"**Congratulations 🎉 You have received the content ✅**\n\n" 234 | f"**Download from private channel/group in premium plan, check /plans 😍**\n\n" 235 | f"**Daily limit left: {remaining}/10**" 236 | ) if not is_premium else ( 237 | "**Congratulations 🎉 You have received the content ✅**\n\n" 238 | "**As a premium user, enjoy unlimited downloads!**" 239 | ) 240 | await client.edit_message_text( 241 | chat_id=chat_id, 242 | message_id=processing_msg.id, 243 | text=reminder_text, 244 | parse_mode=ParseMode.MARKDOWN 245 | ) 246 | LOGGER.info(f"Successfully copied message {message_id} from {channel_username} for user {user_id}") 247 | 248 | except ChannelInvalid: 249 | await client.edit_message_text( 250 | chat_id=chat_id, 251 | message_id=processing_msg.id, 252 | text="Invalid channel or group! Ensure it's public and accessible.", 253 | parse_mode=ParseMode.MARKDOWN 254 | ) 255 | LOGGER.error(f"Invalid channel: {channel_username}") 256 | except ChannelPrivate: 257 | if not is_premium: 258 | await client.edit_message_text( 259 | chat_id=chat_id, 260 | message_id=processing_msg.id, 261 | text="This channel is private! Upgrade to premium and use /login: /plans", 262 | parse_mode=ParseMode.MARKDOWN 263 | ) 264 | LOGGER.error(f"Private channel {channel_username} attempted by free user {user_id}") 265 | except PeerIdInvalid: 266 | await client.edit_message_text( 267 | chat_id=chat_id, 268 | message_id=processing_msg.id, 269 | text="Invalid chat ID! Please check the URL and try again.", 270 | parse_mode=ParseMode.MARKDOWN 271 | ) 272 | LOGGER.error(f"Invalid chat ID: {channel_username}") 273 | except Exception as e: 274 | await client.edit_message_text( 275 | chat_id=chat_id, 276 | message_id=processing_msg.id, 277 | text=f"Error copying the message: {str(e)}", 278 | parse_mode=ParseMode.MARKDOWN 279 | ) 280 | LOGGER.error(f"Failed to copy message {message_id} from {channel_username}: {e}") 281 | 282 | app.add_handler( 283 | MessageHandler( 284 | dl_command, 285 | filters=filters.command("dl", prefixes=COMMAND_PREFIX) & (filters.private | filters.group) 286 | ), 287 | group=1 288 | ) -------------------------------------------------------------------------------- /utils/helper.py: -------------------------------------------------------------------------------- 1 | # Copyright @TheSmartBisnu 2 | # Channel t.me/ItsSmartDev 3 | # Update Author @ISmartDevs 4 | # Channel t.me/TheSmartDev 5 | from asyncio.subprocess import PIPE 6 | import os 7 | from time import time 8 | from typing import Optional 9 | from asyncio import create_subprocess_exec, create_subprocess_shell, wait_for 10 | from PIL import Image 11 | from pyleaves import Leaves 12 | from pyrogram.parser import Parser 13 | from pyrogram.utils import get_channel_id 14 | from pyrogram.types import ( 15 | InputMediaPhoto, 16 | InputMediaVideo, 17 | InputMediaDocument, 18 | InputMediaAudio, 19 | Voice, 20 | ) 21 | 22 | from .logging_setup import LOGGER 23 | 24 | SIZE_UNITS = ["B", "KB", "MB", "GB", "TB", "PB"] 25 | 26 | def get_readable_file_size(size_in_bytes: Optional[float]) -> str: 27 | if size_in_bytes is None or size_in_bytes < 0: 28 | return "0B" 29 | 30 | for unit in SIZE_UNITS: 31 | if size_in_bytes < 1024: 32 | return f"{size_in_bytes:.2f} {unit}" 33 | size_in_bytes /= 1024 34 | 35 | return "File too large" 36 | 37 | def get_readable_time(seconds: int) -> str: 38 | result = "" 39 | (days, remainder) = divmod(seconds, 86400) 40 | days = int(days) 41 | if days != 0: 42 | result += f"{days}d" 43 | (hours, remainder) = divmod(remainder, 3600) 44 | hours = int(hours) 45 | if hours != 0: 46 | result += f"{hours}h" 47 | (minutes, seconds) = divmod(remainder, 60) 48 | minutes = int(minutes) 49 | if minutes != 0: 50 | result += f"{minutes}m" 51 | seconds = int(seconds) 52 | result += f"{seconds}s" 53 | return result 54 | 55 | async def fileSizeLimit(file_size, message, action_type="download", is_premium=False): 56 | MAX_FILE_SIZE = 2 * 2097152000 if is_premium else 2097152000 57 | if file_size > MAX_FILE_SIZE: 58 | await message.reply( 59 | f"The file size exceeds the {get_readable_file_size(MAX_FILE_SIZE)} limit and cannot be {action_type}ed." 60 | ) 61 | return False 62 | return True 63 | 64 | async def get_parsed_msg(text, entities): 65 | return Parser.unparse(text, entities or [], is_html=False) 66 | 67 | # Progress bar template 68 | PROGRESS_BAR = """ 69 | Percentage: {percentage:.2f}% | {current}/{total} 70 | Speed: {speed}/s 71 | Estimated Time Left: {est_time} seconds 72 | """ 73 | 74 | def getChatMsgID(link: str): 75 | linkps = link.split("/") 76 | chat_id, message_thread_id, message_id = None, None, None 77 | 78 | try: 79 | if len(linkps) == 7 and linkps[3] == "c": 80 | # https://t.me/c/1192302355/322/487 81 | chat_id = get_channel_id(int(linkps[4])) 82 | message_thread_id = int(linkps[5]) 83 | message_id = int(linkps[6]) 84 | elif len(linkps) == 6: 85 | if linkps[3] == "c": 86 | # https://t.me/c/1387666944/609282 87 | chat_id = get_channel_id(int(linkps[4])) 88 | message_id = int(linkps[5]) 89 | else: 90 | # https://t.me/TheForum/322/487 91 | chat_id = linkps[3] 92 | message_thread_id = int(linkps[4]) 93 | message_id = int(linkps[5]) 94 | 95 | elif len(linkps) == 5: 96 | # https://t.me/pyrogramchat/609282 97 | chat_id = linkps[3] 98 | if chat_id == "m": 99 | raise ValueError("Invalid ClientType used to parse this message link") 100 | message_id = int(linkps[4]) 101 | 102 | except (ValueError, TypeError): 103 | raise ValueError("Invalid post URL. Must end with a numeric ID.") 104 | 105 | if not chat_id or not message_id: 106 | raise ValueError("Please send a valid Telegram post URL.") 107 | 108 | return chat_id, message_id 109 | 110 | async def cmd_exec(cmd, shell=False): 111 | if shell: 112 | proc = await create_subprocess_shell(cmd, stdout=PIPE, stderr=PIPE) 113 | else: 114 | proc = await create_subprocess_exec(*cmd, stdout=PIPE, stderr=PIPE) 115 | stdout, stderr = await proc.communicate() 116 | try: 117 | stdout = stdout.decode().strip() 118 | except: 119 | stdout = "Unable to decode the response!" 120 | try: 121 | stderr = stderr.decode().strip() 122 | except: 123 | stderr = "Unable to decode the error!" 124 | return stdout, stderr, proc.returncode 125 | 126 | async def get_media_info(path): 127 | try: 128 | result = await cmd_exec( 129 | [ 130 | "ffprobe", 131 | "-hide_banner", 132 | "-loglevel", 133 | "error", 134 | "-print_format", 135 | "json", 136 | "-show_format", 137 | path, 138 | ] 139 | ) 140 | except Exception as e: 141 | LOGGER.error(f"Get Media Info: {e}. Mostly File not found! - File: {path}") 142 | return 0, None, None 143 | if result[0] and result[2] == 0: 144 | fields = eval(result[0]).get("format") 145 | if fields is None: 146 | LOGGER.error(f"get_media_info: {result}") 147 | return 0, None, None 148 | duration = round(float(fields.get("duration", 0))) 149 | tags = fields.get("tags", {}) 150 | artist = tags.get("artist") or tags.get("ARTIST") or tags.get("Artist") 151 | title = tags.get("title") or tags.get("TITLE") or tags.get("Title") 152 | return duration, artist, title 153 | return 0, None, None 154 | 155 | async def get_video_thumbnail(video_file, duration): 156 | output = os.path.join("Assets", "video_thumb.jpg") 157 | if duration is None: 158 | duration = (await get_media_info(video_file))[0] 159 | if duration == 0: 160 | duration = 3 161 | duration = duration // 2 162 | cmd = [ 163 | "ffmpeg", 164 | "-hide_banner", 165 | "-loglevel", 166 | "error", 167 | "-ss", 168 | f"{duration}", 169 | "-i", 170 | video_file, 171 | "-vf", 172 | "thumbnail", 173 | "-q:v", 174 | "1", 175 | "-frames:v", 176 | "1", 177 | "-threads", 178 | f"{os.cpu_count() // 2}", 179 | output, 180 | ] 181 | try: 182 | _, err, code = await wait_for(cmd_exec(cmd), timeout=60) 183 | if code != 0 or not os.path.exists(output): 184 | LOGGER.error( 185 | f"Error while extracting thumbnail from video. Name: {video_file} stderr: {err}" 186 | ) 187 | return None 188 | except Exception as e: 189 | LOGGER.error( 190 | f"Error while extracting thumbnail from video. Name: {video_file}. Error: {e}" 191 | ) 192 | return None 193 | return output 194 | 195 | # Generate progress bar for downloading/uploading 196 | def progressArgs(action: str, progress_message, start_time): 197 | return (action, progress_message, start_time, PROGRESS_BAR, "▓", "░") 198 | 199 | async def send_media( 200 | bot, message, media_path, media_type, caption, progress_message, start_time, thumbnail_path=None 201 | ): 202 | file_size = os.path.getsize(media_path) 203 | 204 | if not await fileSizeLimit(file_size, message, "upload"): 205 | await progress_message.delete() # Delete progress if size limit exceeded 206 | return 207 | 208 | progress_args = progressArgs("📥 Uploading Progress", progress_message, start_time) 209 | LOGGER.info(f"Uploading media: {media_path} ({media_type})") 210 | 211 | try: 212 | if media_type == "photo": 213 | await message.reply_photo( 214 | media_path, 215 | caption=caption or "", 216 | progress=Leaves.progress_for_pyrogram, 217 | progress_args=progress_args, 218 | ) 219 | await progress_message.delete() # Delete after upload 220 | elif media_type == "video": 221 | duration = (await get_media_info(media_path))[0] 222 | thumb = thumbnail_path # Use user-set thumbnail if available 223 | width, height = 480, 320 # Default dimensions 224 | 225 | if thumb is None: # Fallback to generated thumbnail 226 | if os.path.exists("Assets/video_thumb.jpg"): 227 | os.remove("Assets/video_thumb.jpg") 228 | thumb = await get_video_thumbnail(media_path, duration) 229 | 230 | if thumb and os.path.exists(thumb): 231 | with Image.open(thumb) as img: 232 | width, height = img.size 233 | else: 234 | thumb = None # Fallback to no thumbnail if file missing 235 | 236 | await message.reply_video( 237 | media_path, 238 | duration=duration, 239 | width=width, 240 | height=height, 241 | thumb=thumb, 242 | caption=caption or "", 243 | progress=Leaves.progress_for_pyrogram, 244 | progress_args=progress_args, 245 | ) 246 | await progress_message.delete() # Delete after upload 247 | elif media_type == "audio": 248 | duration, artist, title = await get_media_info(media_path) 249 | await message.reply_audio( 250 | media_path, 251 | duration=duration, 252 | performer=artist, 253 | title=title, 254 | thumb=thumbnail_path, # Use thumbnail path 255 | caption=caption or "", 256 | progress=Leaves.progress_for_pyrogram, 257 | progress_args=progress_args, 258 | ) 259 | await progress_message.delete() # Delete after upload 260 | elif media_type == "document": 261 | await message.reply_document( 262 | media_path, 263 | thumb=thumbnail_path, # Use thumbnail path 264 | caption=caption or "", 265 | progress=Leaves.progress_for_pyrogram, 266 | progress_args=progress_args, 267 | ) 268 | await progress_message.delete() # Delete after upload 269 | except Exception as e: 270 | LOGGER.error(f"Error uploading media {media_path}: {e}") 271 | await progress_message.delete() # Delete on error 272 | raise 273 | 274 | async def processMediaGroup(chat_message, bot, message): 275 | media_group_messages = await chat_message.get_media_group() 276 | valid_media = [] 277 | temp_paths = [] 278 | invalid_paths = [] 279 | 280 | start_time = time() 281 | progress_message = await message.reply("📥 Downloading media group...") 282 | LOGGER.info( 283 | f"Downloading media group with {len(media_group_messages)} items..." 284 | ) 285 | 286 | for msg in media_group_messages: 287 | if msg.photo or msg.video or msg.document or msg.audio: 288 | try: 289 | media_path = await msg.download( 290 | progress=Leaves.progress_for_pyrogram, 291 | progress_args=progressArgs( 292 | "📥 Downloading Progress", progress_message, start_time 293 | ), 294 | ) 295 | temp_paths.append(media_path) 296 | 297 | if msg.photo: 298 | valid_media.append( 299 | InputMediaPhoto( 300 | media=media_path, 301 | caption=await get_parsed_msg( 302 | msg.caption or "", msg.caption_entities 303 | ), 304 | ) 305 | ) 306 | elif msg.video: 307 | valid_media.append( 308 | InputMediaVideo( 309 | media=media_path, 310 | caption=await get_parsed_msg( 311 | msg.caption or "", msg.caption_entities 312 | ), 313 | ) 314 | ) 315 | elif msg.document: 316 | valid_media.append( 317 | InputMediaDocument( 318 | media=media_path, 319 | caption=await get_parsed_msg( 320 | msg.caption or "", msg.caption_entities 321 | ), 322 | ) 323 | ) 324 | elif msg.audio: 325 | valid_media.append( 326 | InputMediaAudio( 327 | media=media_path, 328 | caption=await get_parsed_msg( 329 | msg.caption or "", msg.caption_entities 330 | ), 331 | ) 332 | ) 333 | 334 | except Exception as e: 335 | LOGGER.info(f"Error downloading media: {e}") 336 | if media_path and os.path.exists(media_path): 337 | invalid_paths.append(media_path) 338 | continue 339 | 340 | LOGGER.info(f"Valid media count: {len(valid_media)}") 341 | 342 | if valid_media: 343 | try: 344 | await bot.send_media_group(chat_id=message.chat.id, media=valid_media) 345 | await progress_message.delete() 346 | except Exception: 347 | await message.reply( 348 | "**❌ Failed to send media group, trying individual uploads**" 349 | ) 350 | for media in valid_media: 351 | try: 352 | if isinstance(media, InputMediaPhoto): 353 | await bot.send_photo( 354 | chat_id=message.chat.id, 355 | photo=media.media, 356 | caption=media.caption, 357 | ) 358 | elif isinstance(media, InputMediaVideo): 359 | await bot.send_video( 360 | chat_id=message.chat.id, 361 | video=media.media, 362 | caption=media.caption, 363 | ) 364 | elif isinstance(media, InputMediaDocument): 365 | await bot.send_document( 366 | chat_id=message.chat.id, 367 | document=media.media, 368 | caption=media.caption, 369 | ) 370 | elif isinstance(media, InputMediaAudio): 371 | await bot.send_audio( 372 | chat_id=message.chat.id, 373 | audio=media.media, 374 | caption=media.caption, 375 | ) 376 | elif isinstance(media, Voice): 377 | await bot.send_voice( 378 | chat_id=message.chat.id, 379 | voice=media.media, 380 | caption=media.caption, 381 | ) 382 | except Exception as individual_e: 383 | await message.reply( 384 | f"Failed to upload individual media: {individual_e}" 385 | ) 386 | 387 | await progress_message.delete() 388 | 389 | for path in temp_paths: 390 | if os.path.exists(path): 391 | os.remove(path) 392 | for path in invalid_paths: 393 | if os.path.exists(path): 394 | os.remove(path) 395 | 396 | return True 397 | 398 | await progress_message.delete() 399 | await message.reply("❌ No valid media found in the media group.") 400 | for path in invalid_paths: 401 | if os.path.exists(path): 402 | os.remove(path) 403 | return False -------------------------------------------------------------------------------- /auth/restart/restart.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | import os 4 | import shutil 5 | import asyncio 6 | import logging 7 | import subprocess 8 | from pyrogram import Client, filters 9 | from pyrogram.enums import ParseMode 10 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton 11 | from pyrogram.errors import FloodWait 12 | from config import DEVELOPER_USER_ID, COMMAND_PREFIX 13 | from utils import LOGGER 14 | 15 | # Setup logging 16 | logging.basicConfig(level=logging.INFO) 17 | logger = LOGGER 18 | 19 | def check_session_permissions(session_file: str) -> bool: 20 | """Check if the session file is writable.""" 21 | if not os.path.exists(session_file): 22 | logger.warning(f"Session file {session_file} does not exist") 23 | return True 24 | if not os.access(session_file, os.W_OK): 25 | logger.error(f"Session file {session_file} is not writable") 26 | try: 27 | os.chmod(session_file, 0o600) 28 | logger.info(f"Fixed permissions for {session_file}") 29 | return os.access(session_file, os.W_OK) 30 | except Exception as e: 31 | logger.error(f"Failed to fix permissions for {session_file}: {e}") 32 | return False 33 | return True 34 | 35 | def setup_restart_handler(app: Client): 36 | """Set up handlers for restart and stop commands.""" 37 | 38 | @app.on_message(filters.command(["restart", "reboot", "reload"], prefixes=COMMAND_PREFIX) & (filters.private | filters.group)) 39 | async def restart(client: Client, message): 40 | """Handle /restart, /reboot, /reload commands to restart the bot.""" 41 | user_id = message.from_user.id 42 | logger.info(f"/restart command from user {user_id}") 43 | 44 | try: 45 | response = await client.send_message( 46 | chat_id=message.chat.id, 47 | text="**✘ Restarting Restricted Content Downloader... ↯**", 48 | parse_mode=ParseMode.MARKDOWN 49 | ) 50 | except FloodWait as e: 51 | logger.warning(f"FloodWait during restart message: waiting {e.value + 5} seconds") 52 | await asyncio.sleep(e.value + 5) 53 | response = await client.send_message( 54 | chat_id=message.chat.id, 55 | text="**✘ Restarting Restricted Content Downloader... ↯**", 56 | parse_mode=ParseMode.MARKDOWN 57 | ) 58 | 59 | if user_id != DEVELOPER_USER_ID: 60 | logger.info("User is not developer, sending restricted message") 61 | try: 62 | await client.edit_message_text( 63 | chat_id=message.chat.id, 64 | message_id=response.id, 65 | text="**❌ Unauthorized! Only the Developer Can Restart! ↯**", 66 | parse_mode=ParseMode.MARKDOWN, 67 | reply_markup=InlineKeyboardMarkup([ 68 | [ 69 | InlineKeyboardButton("✘ Updates Channel ↯", url="https://t.me/TheSmartDevs"), 70 | InlineKeyboardButton("✘ Source Code ↯", url="https://github.com/TheSmartDevs/RestrictedContentDL") 71 | ] 72 | ]) 73 | ) 74 | except FloodWait as e: 75 | logger.warning(f"FloodWait during unauthorized message edit: waiting {e.value + 5} seconds") 76 | await asyncio.sleep(e.value + 5) 77 | await client.edit_message_text( 78 | chat_id=message.chat.id, 79 | message_id=response.id, 80 | text="**❌ Unauthorized! Only the Developer Can Restart! ↯**", 81 | parse_mode=ParseMode.MARKDOWN, 82 | reply_markup=InlineKeyboardMarkup([ 83 | [ 84 | InlineKeyboardButton("✘ Updates Channel ↯", url="https://t.me/TheSmartDevs"), 85 | InlineKeyboardButton("✘ Source Code ↯", url="https://github.com/TheSmartDevs/RestrictedContentDL") 86 | ] 87 | ]) 88 | ) 89 | return 90 | 91 | session_file = "RestrictedContentDL.session" 92 | if not check_session_permissions(session_file): 93 | try: 94 | await client.edit_message_text( 95 | chat_id=message.chat.id, 96 | message_id=response.id, 97 | text="**❌ Restart Failed: Session File Not Writable! ↯**", 98 | parse_mode=ParseMode.MARKDOWN 99 | ) 100 | except FloodWait as e: 101 | logger.warning(f"FloodWait during session error message: waiting {e.value + 5} seconds") 102 | await asyncio.sleep(e.value + 5) 103 | await client.edit_message_text( 104 | chat_id=message.chat.id, 105 | message_id=response.id, 106 | text="**❌ Restart Failed: Session File Not Writable! ↯**", 107 | parse_mode=ParseMode.MARKDOWN 108 | ) 109 | return 110 | 111 | directories = ["downloads", "Assets"] 112 | deleted_dirs = [] 113 | failed_dirs = [] 114 | for directory in directories: 115 | try: 116 | if os.path.exists(directory): 117 | shutil.rmtree(directory) 118 | deleted_dirs.append(directory) 119 | logger.info(f"Deleted directory: {directory}") 120 | except Exception as e: 121 | failed_dirs.append(directory) 122 | logger.error(f"Failed to delete directory {directory}: {e}") 123 | 124 | log_file = "botlog.txt" 125 | if os.path.exists(log_file): 126 | try: 127 | os.remove(log_file) 128 | logger.info(f"Deleted log file: {log_file}") 129 | except Exception as e: 130 | logger.error(f"Failed to delete log file {log_file}: {e}") 131 | 132 | start_script = "start.sh" 133 | if not os.path.exists(start_script): 134 | logger.error("Start script not found") 135 | try: 136 | await client.edit_message_text( 137 | chat_id=message.chat.id, 138 | message_id=response.id, 139 | text="**❌ Restart Failed: Start Script Not Found! ↯**", 140 | parse_mode=ParseMode.MARKDOWN 141 | ) 142 | except FloodWait as e: 143 | logger.warning(f"FloodWait during script error message: waiting {e.value + 5} seconds") 144 | await asyncio.sleep(e.value + 5) 145 | await client.edit_message_text( 146 | chat_id=message.chat.id, 147 | message_id=response.id, 148 | text="**❌ Restart Failed: Start Script Not Found! ↯**", 149 | parse_mode=ParseMode.MARKDOWN 150 | ) 151 | return 152 | 153 | try: 154 | await asyncio.sleep(4) 155 | await client.edit_message_text( 156 | chat_id=message.chat.id, 157 | message_id=response.id, 158 | text="**✘ Restricted Content Downloader Restarted Successfully! ↯**", 159 | parse_mode=ParseMode.MARKDOWN 160 | ) 161 | except FloodWait as e: 162 | logger.warning(f"FloodWait during success message: waiting {e.value + 5} seconds") 163 | await asyncio.sleep(e.value + 5) 164 | await client.edit_message_text( 165 | chat_id=message.chat.id, 166 | message_id=response.id, 167 | text="**✘ RestrictedDL Restarted Successfully! ↯**", 168 | parse_mode=ParseMode.MARKDOWN 169 | ) 170 | except Exception as e: 171 | logger.error(f"Failed to edit restart message: {e}") 172 | try: 173 | await client.edit_message_text( 174 | chat_id=message.chat.id, 175 | message_id=response.id, 176 | text="**❌ Restart Failed! ↯**", 177 | parse_mode=ParseMode.MARKDOWN 178 | ) 179 | except FloodWait as e: 180 | logger.warning(f"FloodWait during failure message: waiting {e.value + 5} seconds") 181 | await asyncio.sleep(e.value + 5) 182 | await client.edit_message_text( 183 | chat_id=message.chat.id, 184 | message_id=response.id, 185 | text="**❌ Restart Failed! ↯**", 186 | parse_mode=ParseMode.MARKDOWN 187 | ) 188 | return 189 | 190 | try: 191 | subprocess.run(["bash", start_script], check=True) 192 | os._exit(0) 193 | except Exception as e: 194 | logger.error(f"Failed to execute restart command: {e}") 195 | try: 196 | await client.edit_message_text( 197 | chat_id=message.chat.id, 198 | message_id=response.id, 199 | text="**❌ Restart Failed! ↯**", 200 | parse_mode=ParseMode.MARKDOWN 201 | ) 202 | except FloodWait as e: 203 | logger.warning(f"FloodWait during final failure message: waiting {e.value + 5} seconds") 204 | await asyncio.sleep(e.value + 5) 205 | await client.edit_message_text( 206 | chat_id=message.chat.id, 207 | message_id=response.id, 208 | text="**❌ Restart Failed! ↯**", 209 | parse_mode=ParseMode.MARKDOWN 210 | ) 211 | 212 | @app.on_message(filters.command(["stop", "kill", "off"], prefixes=COMMAND_PREFIX) & (filters.private | filters.group)) 213 | async def stop(client: Client, message): 214 | """Handle /stop, /kill, /off commands to stop the bot.""" 215 | user_id = message.from_user.id 216 | logger.info(f"/stop command from user {user_id}") 217 | 218 | try: 219 | response = await client.send_message( 220 | chat_id=message.chat.id, 221 | text="**✘ Stopping Restricted Content Downloader... ↯**", 222 | parse_mode=ParseMode.MARKDOWN 223 | ) 224 | except FloodWait as e: 225 | logger.warning(f"FloodWait during stop message: waiting {e.value + 5} seconds") 226 | await asyncio.sleep(e.value + 5) 227 | response = await client.send_message( 228 | chat_id=message.chat.id, 229 | text="**✘ Stopping Restricted Content Downloader... ↯**", 230 | parse_mode=ParseMode.MARKDOWN 231 | ) 232 | 233 | if user_id != DEVELOPER_USER_ID: 234 | logger.info("User is not developer, sending restricted message") 235 | try: 236 | await client.edit_message_text( 237 | chat_id=message.chat.id, 238 | message_id=response.id, 239 | text="**❌ Unauthorized! Only the Developer Can Stop! ↯**", 240 | parse_mode=ParseMode.MARKDOWN, 241 | reply_markup=InlineKeyboardMarkup([ 242 | [ 243 | InlineKeyboardButton("✘ Updates Channel ↯", url="https://t.me/TheSmartDevs"), 244 | InlineKeyboardButton("✘ Source Code ↯", url="https://github.com/TheSmartDevs/RestrictedContentDL") 245 | ] 246 | ]) 247 | ) 248 | except FloodWait as e: 249 | logger.warning(f"FloodWait during unauthorized stop message: waiting {e.value + 5} seconds") 250 | await asyncio.sleep(e.value + 5) 251 | await client.edit_message_text( 252 | chat_id=message.chat.id, 253 | message_id=response.id, 254 | text="**❌ Unauthorized! Only the Developer Can Stop! ↯**", 255 | parse_mode=ParseMode.MARKDOWN, 256 | reply_markup=InlineKeyboardMarkup([ 257 | [ 258 | InlineKeyboardButton("✘ Updates Channel ↯", url="https://t.me/TheSmartDevs"), 259 | InlineKeyboardButton("✘ Source Code ↯", url="https://github.com/TheSmartDevs/RestrictedContentDL") 260 | ] 261 | ]) 262 | ) 263 | return 264 | 265 | directories = ["downloads", "Assets"] 266 | deleted_dirs = [] 267 | failed_dirs = [] 268 | for directory in directories: 269 | try: 270 | if os.path.exists(directory): 271 | shutil.rmtree(directory) 272 | deleted_dirs.append(directory) 273 | logger.info(f"Deleted directory: {directory}") 274 | except Exception as e: 275 | failed_dirs.append(directory) 276 | logger.error(f"Failed to delete directory {directory}: {e}") 277 | 278 | log_file = "botlog.txt" 279 | if os.path.exists(log_file): 280 | try: 281 | os.remove(log_file) 282 | logger.info(f"Deleted log file: {log_file}") 283 | except Exception as e: 284 | logger.error(f"Failed to delete log file {log_file}: {e}") 285 | 286 | try: 287 | await client.edit_message_text( 288 | chat_id=message.chat.id, 289 | message_id=response.id, 290 | text="**✘ Restricted Content Downloader Stopped Successfully! ↯**", 291 | parse_mode=ParseMode.MARKDOWN 292 | ) 293 | except FloodWait as e: 294 | logger.warning(f"FloodWait during stop success message: waiting {e.value + 5} seconds") 295 | await asyncio.sleep(e.value + 5) 296 | await client.edit_message_text( 297 | chat_id=message.chat.id, 298 | message_id=response.id, 299 | text="**✘ Restricted Content Downloader Stopped Successfully! ↯**", 300 | parse_mode=ParseMode.MARKDOWN 301 | ) 302 | except Exception as e: 303 | logger.error(f"Failed to edit stop message: {e}") 304 | try: 305 | await client.edit_message_text( 306 | chat_id=message.chat.id, 307 | message_id=response.id, 308 | text="**❌ Stop Failed! ↯**", 309 | parse_mode=ParseMode.MARKDOWN 310 | ) 311 | except FloodWait as e: 312 | logger.warning(f"FloodWait during stop failure message: waiting {e.value + 5} seconds") 313 | await asyncio.sleep(e.value + 5) 314 | await client.edit_message_text( 315 | chat_id=message.chat.id, 316 | message_id=response.id, 317 | text="**❌ Stop Failed! ↯**", 318 | parse_mode=ParseMode.MARKDOWN 319 | ) 320 | return 321 | 322 | try: 323 | subprocess.run(["pkill", "-f", "main.py"], check=True) 324 | os._exit(0) 325 | except Exception as e: 326 | logger.error(f"Failed to stop bot: {e}") 327 | try: 328 | await client.edit_message_text( 329 | chat_id=message.chat.id, 330 | message_id=response.id, 331 | text="**❌ Stop Failed! ↯**", 332 | parse_mode=ParseMode.MARKDOWN 333 | ) 334 | except FloodWait as e: 335 | logger.warning(f"FloodWait during final stop failure message: waiting {e.value + 5} seconds") 336 | await asyncio.sleep(e.value + 5) 337 | await client.edit_message_text( 338 | chat_id=message.chat.id, 339 | message_id=response.id, 340 | text="**❌ Stop Failed! ↯**", 341 | parse_mode=ParseMode.MARKDOWN 342 | ) -------------------------------------------------------------------------------- /plugins/pbatch.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | from pyrogram import Client, filters 4 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 5 | from pyrogram.enums import ParseMode, ChatType 6 | from pyrogram.errors import ChannelInvalid, ChannelPrivate, PeerIdInvalid, FileReferenceExpired 7 | from config import COMMAND_PREFIX 8 | from utils import LOGGER 9 | from core import daily_limit, prem_plan1, prem_plan2, prem_plan3, user_activity_collection 10 | from datetime import datetime 11 | import re 12 | import asyncio 13 | 14 | batch_data = {} 15 | 16 | def setup_pbatch_handler(app: Client): 17 | async def get_batch_limits(user_id: int) -> tuple[bool, int]: 18 | current_time = datetime.utcnow() 19 | if prem_plan3.find_one({"user_id": user_id, "expiry_date": {"$gt": current_time}}): 20 | return True, 10000 # Plan3: 10000 messages 21 | elif prem_plan2.find_one({"user_id": user_id, "expiry_date": {"$gt": current_time}}): 22 | return True, 5000 # Plan2: 5000 messages 23 | elif prem_plan1.find_one({"user_id": user_id, "expiry_date": {"$gt": current_time}}): 24 | return True, 2000 # Plan1: 2000 messages 25 | return False, 10 # Free user: 10 messages 26 | 27 | async def is_premium_user(user_id: int) -> bool: 28 | current_time = datetime.utcnow() 29 | for plan_collection in [prem_plan1, prem_plan2, prem_plan3]: 30 | plan = plan_collection.find_one({"user_id": user_id}) 31 | if plan and plan.get("expiry_date", current_time) > current_time: 32 | return True 33 | return False 34 | 35 | @app.on_message(filters.command("bdl", prefixes=COMMAND_PREFIX) & filters.private) 36 | async def bdl_command(client: Client, message: Message): 37 | user_id = message.from_user.id 38 | chat_id = message.chat.id 39 | LOGGER.info(f"/bdl command received from user {user_id}") 40 | 41 | # Extract URL from command 42 | if len(message.command) < 2: 43 | await message.reply_text( 44 | "**❌ Please Provide A Valid URL! Useage: /bdl {url}**", 45 | parse_mode=ParseMode.MARKDOWN 46 | ) 47 | LOGGER.warning(f"No URL provided in /bdl command by user {user_id}") 48 | return 49 | 50 | url = message.command[1] 51 | # Handle t.me and telegram.me URLs 52 | match = re.match(r"(?:https?://)?(?:t\.me|telegram\.me)/(?:c/)?([a-zA-Z0-9_]+)/(\d+)", url) 53 | if not match: 54 | await message.reply_text( 55 | "**❌ Sorry Invalid URL Provided**", 56 | parse_mode=ParseMode.MARKDOWN 57 | ) 58 | LOGGER.warning(f"Invalid URL format: {url} by user {user_id}") 59 | return 60 | 61 | channel_username, message_id = match.groups() 62 | message_id = int(message_id) 63 | is_private = "c/" in url 64 | 65 | # Check if it's a private link 66 | if is_private: 67 | await message.reply_text( 68 | "**🚨 Private Links Detected & They Require Premium Plan & Login Kindly Use /plans To Upgrade**", 69 | parse_mode=ParseMode.MARKDOWN 70 | ) 71 | LOGGER.warning(f"Private link {url} attempted by user {user_id} without login") 72 | return 73 | 74 | # Handle public links 75 | if not channel_username.startswith("@"): 76 | channel_username = f"@{channel_username}" 77 | 78 | # Check channel accessibility 79 | try: 80 | chat = await client.get_chat(channel_username) 81 | if chat.type not in [ChatType.CHANNEL, ChatType.SUPERGROUP]: 82 | await message.reply_text( 83 | "**❌ This Command Only Can DL From Channel & Group!**", 84 | parse_mode=ParseMode.MARKDOWN 85 | ) 86 | LOGGER.error(f"Invalid chat type for {channel_username}: {chat.type}") 87 | return 88 | except (ChannelInvalid, PeerIdInvalid, ChannelPrivate): 89 | await message.reply_text( 90 | "**❌ Invalid Media Link Provided Or Media Private**", 91 | parse_mode=ParseMode.MARKDOWN 92 | ) 93 | LOGGER.error(f"Invalid or private channel: {channel_username}") 94 | return 95 | except Exception as e: 96 | await message.reply_text( 97 | "**❌ Error Accessing The Channel Kindly Check The URL.**", 98 | parse_mode=ParseMode.MARKDOWN 99 | ) 100 | LOGGER.error(f"Failed to fetch chat {channel_username}: {e}") 101 | return 102 | 103 | # Prompt for number of messages 104 | batch_data[chat_id] = { 105 | "user_id": user_id, 106 | "channel_username": channel_username, 107 | "start_message_id": message_id, 108 | "stage": "await_count" 109 | } 110 | await message.reply_text( 111 | "**📥 How Many Messages You Want To Scrape?**", 112 | reply_markup=InlineKeyboardMarkup([[ 113 | InlineKeyboardButton("Confirm", callback_data=f"bdl_confirm_{chat_id}"), 114 | InlineKeyboardButton("Cancel", callback_data=f"bdl_cancel_{chat_id}") 115 | ]]), 116 | parse_mode=ParseMode.MARKDOWN 117 | ) 118 | 119 | @app.on_message(filters.text & filters.create(lambda _, __, message: message.chat.id in batch_data and batch_data[message.chat.id].get("stage") == "await_count")) 120 | async def count_handler(client, message: Message): 121 | chat_id = message.chat.id 122 | user_id = message.from_user.id 123 | batch_info = batch_data.get(chat_id) 124 | if not batch_info or batch_info["user_id"] != user_id: 125 | return 126 | 127 | try: 128 | count = int(message.text) 129 | is_premium, max_messages = await get_batch_limits(user_id) 130 | if count < 1: 131 | await message.reply_text( 132 | "**❌Please Enter A Valid Integer Greater Than 0!**", 133 | parse_mode=ParseMode.MARKDOWN 134 | ) 135 | return 136 | if count > max_messages: 137 | await message.reply_text( 138 | f"**❌ You Can Only Scrape{max_messages} Messages! {'ᴜᴘɢʀᴀᴅᴇ: /plans' if not is_premium else ''}**", 139 | parse_mode=ParseMode.MARKDOWN 140 | ) 141 | return 142 | 143 | batch_info["count"] = count 144 | batch_info["stage"] = "confirmed" 145 | await message.reply_text( 146 | f"**✅ You Have Selected {count}Message{'s' if count > 1 else ''} To Scrape Press Confirm To Start**", 147 | reply_markup=InlineKeyboardMarkup([[ 148 | InlineKeyboardButton("Confirm", callback_data=f"bdl_confirm_{chat_id}"), 149 | InlineKeyboardButton("Cancel", callback_data=f"bdl_cancel_{chat_id}") 150 | ]]), 151 | parse_mode=ParseMode.MARKDOWN 152 | ) 153 | except ValueError: 154 | await message.reply_text( 155 | "**❌ Please Provide A Valid Integer!**", 156 | parse_mode=ParseMode.MARKDOWN 157 | ) 158 | 159 | @app.on_callback_query(filters.regex(r"^bdl_(confirm|cancel)_(\d+)$")) 160 | async def bdl_callback_handler(client, callback_query): 161 | data = callback_query.data 162 | chat_id = int(data.split("_")[2]) 163 | user_id = callback_query.from_user.id 164 | batch_info = batch_data.get(chat_id) 165 | 166 | if not batch_info or batch_info["user_id"] != user_id: 167 | await callback_query.message.edit_text( 168 | "**❌ Invalid Or Expired Batch Session Bro !**", 169 | parse_mode=ParseMode.MARKDOWN 170 | ) 171 | return 172 | 173 | if data.startswith("bdl_cancel_"): 174 | await callback_query.message.edit_text( 175 | "**❌Batch Restricted Content DL Cancelled**", 176 | parse_mode=ParseMode.MARKDOWN 177 | ) 178 | del batch_data[chat_id] 179 | LOGGER.info(f"Batch download cancelled by user {user_id}") 180 | return 181 | 182 | if data.startswith("bdl_confirm_"): 183 | if batch_info.get("stage") != "confirmed": 184 | await callback_query.message.edit_text( 185 | "**❌ Please Enter The Message Number Fast!**", 186 | parse_mode=ParseMode.MARKDOWN 187 | ) 188 | return 189 | 190 | channel_username = batch_info["channel_username"] 191 | start_message_id = batch_info["start_message_id"] 192 | count = batch_info["count"] 193 | is_premium = await is_premium_user(user_id) 194 | 195 | # Update daily limit 196 | today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) 197 | if not is_premium: 198 | user_limit = daily_limit.find_one({"user_id": user_id}) 199 | downloads = user_limit.get("downloads", 0) if user_limit and user_limit.get("date") >= today else 0 200 | if downloads + count > 10: 201 | await callback_query.message.edit_text( 202 | f"**🚫 Daily Limits 10 Downloads Will Be Exceeded! Upgrade To Premium: /plans**", 203 | parse_mode=ParseMode.MARKDOWN 204 | ) 205 | del batch_data[chat_id] 206 | LOGGER.info(f"Daily limit exceeded for user {user_id}") 207 | return 208 | daily_limit.update_one( 209 | {"user_id": user_id}, 210 | { 211 | "$set": {"downloads": downloads + count, "date": today}, 212 | "$inc": {"total_downloads": count} 213 | }, 214 | upsert=True 215 | ) 216 | else: 217 | daily_limit.update_one( 218 | {"user_id": user_id}, 219 | {"$inc": {"total_downloads": count}}, 220 | upsert=True 221 | ) 222 | 223 | # Process batch download 224 | await callback_query.message.edit_text( 225 | "**📥 Processing Batch Download...**", 226 | parse_mode=ParseMode.MARKDOWN 227 | ) 228 | 229 | try: 230 | # Fetch messages (start_message_id to start_message_id + count - 1) 231 | message_ids = list(range(start_message_id, start_message_id + count)) 232 | messages = await client.get_messages(channel_username, message_ids) 233 | 234 | user_data = user_activity_collection.find_one({"user_id": user_id}) 235 | thumbnail_file_id = user_data.get("thumbnail_file_id") if user_data else None 236 | 237 | for source_message in messages: 238 | if not source_message: 239 | continue 240 | 241 | if source_message.video: 242 | try: 243 | if thumbnail_file_id: 244 | try: 245 | test_photo = await client.send_photo( 246 | chat_id=user_id, 247 | photo=thumbnail_file_id, 248 | caption="**Validating thumbnail...**", 249 | parse_mode=ParseMode.MARKDOWN 250 | ) 251 | await test_photo.delete() 252 | except Exception as e: 253 | thumbnail_file_id = None 254 | LOGGER.warning(f"Invalid thumbnail file_id for user {user_id}: {e}") 255 | await client.send_message( 256 | chat_id=chat_id, 257 | text="**⚠️Sorry Thumbnail Not Found Use /setthumb**", 258 | parse_mode=ParseMode.MARKDOWN 259 | ) 260 | 261 | await client.send_video( 262 | chat_id=chat_id, 263 | video=source_message.video.file_id, 264 | caption=source_message.caption or "", 265 | parse_mode=ParseMode.MARKDOWN if source_message.caption else None, 266 | thumb=thumbnail_file_id if thumbnail_file_id else None 267 | ) 268 | LOGGER.info(f"Sent video with {'custom' if thumbnail_file_id else 'default'} thumbnail for user {user_id}") 269 | except FileReferenceExpired: 270 | await client.send_video( 271 | chat_id=chat_id, 272 | video=source_message.video.file_id, 273 | caption=source_message.caption or "", 274 | parse_mode=ParseMode.MARKDOWN if source_message.caption else None 275 | ) 276 | await client.send_message( 277 | chat_id=chat_id, 278 | text="**⚠️Sorry Thumbnail Not Found Use /setthumb**", 279 | parse_mode=ParseMode.MARKDOWN 280 | ) 281 | LOGGER.info(f"Sent video with default thumbnail for user {user_id} due to expired thumbnail") 282 | except Exception as e: 283 | await client.send_video( 284 | chat_id=chat_id, 285 | video=source_message.video.file_id, 286 | caption=source_message.caption or "", 287 | parse_mode=ParseMode.MARKDOWN if source_message.caption else None 288 | ) 289 | await client.send_message( 290 | chat_id=chat_id, 291 | text="**Using Default Thumbnail**", 292 | parse_mode=ParseMode.MARKDOWN 293 | ) 294 | LOGGER.info(f"Sent video with default thumbnail for user {user_id} due to error: {e}") 295 | else: 296 | await client.copy_message( 297 | chat_id=chat_id, 298 | from_chat_id=channel_username, 299 | message_id=source_message.id 300 | ) 301 | 302 | await asyncio.sleep(0.5) # Prevent rate-limiting 303 | 304 | # Send and pin completion message 305 | completion_msg = await client.send_message( 306 | chat_id=chat_id, 307 | text="**✅ Batch Process Completed Successfully**", 308 | parse_mode=ParseMode.MARKDOWN 309 | ) 310 | try: 311 | await client.pin_chat_message(chat_id, completion_msg.id, both_sides=True) 312 | except Exception as e: 313 | LOGGER.warning(f"Failed to pin completion message for user {user_id}: {e}") 314 | 315 | del batch_data[chat_id] 316 | LOGGER.info(f"Batch download completed for user {user_id}: {count} messages from {channel_username}") 317 | 318 | except Exception as e: 319 | await client.send_message( 320 | chat_id=chat_id, 321 | text=f"**❌Error Processing Batch DL {str(e)}**", 322 | parse_mode=ParseMode.MARKDOWN 323 | ) 324 | del batch_data[chat_id] 325 | LOGGER.error(f"Failed to process batch download for user {user_id}: {e}") 326 | 327 | app.add_handler(app.on_message, group=1) 328 | app.add_handler(app.on_callback_query, group=2) -------------------------------------------------------------------------------- /plugins/pvdl.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | import os 4 | from time import time 5 | from pyrogram import Client, filters 6 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 7 | from pyrogram.enums import ParseMode 8 | from pyrogram.errors import PeerIdInvalid, BadRequest 9 | from pyleaves import Leaves 10 | from datetime import datetime 11 | import re 12 | import asyncio 13 | from utils import ( 14 | getChatMsgID, 15 | processMediaGroup, 16 | get_parsed_msg, 17 | fileSizeLimit, 18 | progressArgs, 19 | send_media, 20 | LOGGER 21 | ) 22 | from config import COMMAND_PREFIX 23 | from core import prem_plan1, prem_plan2, prem_plan3, user_sessions, user_activity_collection 24 | 25 | pbdl_data = {} 26 | user = None 27 | 28 | def setup_pvdl_handler(app: Client): 29 | async def get_batch_limits(user_id: int) -> tuple[bool, int]: 30 | current_time = datetime.utcnow() 31 | if prem_plan3.find_one({"user_id": user_id, "expiry_date": {"$gt": current_time}}): 32 | return True, 300 # Plan3: 300 messages 33 | elif prem_plan2.find_one({"user_id": user_id, "expiry_date": {"$gt": current_time}}): 34 | return True, 200 # Plan2: 200 messages 35 | elif prem_plan1.find_one({"user_id": user_id, "expiry_date": {"$gt": current_time}}): 36 | return True, 100 # Plan1: 100 messages 37 | return False, 0 # Non-premium: 0 messages 38 | 39 | async def is_premium_user(user_id: int) -> bool: 40 | current_time = datetime.utcnow() 41 | for plan_collection in [prem_plan1, prem_plan2, prem_plan3]: 42 | plan = plan_collection.find_one({"user_id": user_id}) 43 | if plan and plan.get("expiry_date", current_time) > current_time: 44 | return True 45 | return False 46 | 47 | async def get_user_client(user_id: int, session_id: str) -> Client: 48 | global user 49 | user_session = user_sessions.find_one({"user_id": user_id}) 50 | if not user_session or not user_session.get("sessions"): 51 | return None 52 | session = next((s for s in user_session["sessions"] if s["session_id"] == session_id), None) 53 | if not session: 54 | return None 55 | if user is not None: 56 | try: 57 | await user.stop() 58 | except Exception as e: 59 | LOGGER.error(f"Error stopping existing user client for user {user_id}: {e}") 60 | user = None 61 | try: 62 | user = Client( 63 | f"user_session_{user_id}_{session_id}", 64 | workers=1000, 65 | session_string=session["session_string"] 66 | ) 67 | await user.start() 68 | return user 69 | except Exception as e: 70 | LOGGER.error(f"Failed to initialize user client for user {user_id}, session {session_id}: {e}") 71 | return None 72 | 73 | async def show_account_selection(client: Client, message: Message, post_url: str = None): 74 | user_id = message.from_user.id 75 | user_session = user_sessions.find_one({"user_id": user_id}) 76 | if not user_session or not user_session.get("sessions"): 77 | return None 78 | 79 | sessions = user_session.get("sessions", []) 80 | if len(sessions) == 1: 81 | return sessions[0]["session_id"] # Auto-select if only one account 82 | 83 | pbdl_data[message.chat.id] = {"post_url": post_url, "message_id": message.id, "stage": "select_account"} 84 | 85 | buttons = [] 86 | for i in range(0, len(sessions), 2): 87 | row = [] 88 | for session in sessions[i:i+2]: 89 | row.append(InlineKeyboardButton( 90 | session["account_name"], 91 | callback_data=f"pbdl_select_{session['session_id']}" 92 | )) 93 | buttons.append(row) 94 | buttons.append([InlineKeyboardButton("Cancel", callback_data="pbdl_cancel_account")]) 95 | 96 | await message.reply_text( 97 | "**📤 Select an account to use for private batch download:**", 98 | reply_markup=InlineKeyboardMarkup(buttons), 99 | parse_mode=ParseMode.MARKDOWN 100 | ) 101 | return None 102 | 103 | @app.on_message(filters.command("pbdl", prefixes=COMMAND_PREFIX) & filters.private) 104 | async def pbdl_command(client: Client, message: Message): 105 | user_id = message.from_user.id 106 | chat_id = message.chat.id 107 | LOGGER.info(f"/pbdl command received from user {user_id}") 108 | 109 | # Check premium status 110 | if not await is_premium_user(user_id): 111 | await message.reply_text( 112 | "**❌ Only premium users can use /pbdl! Upgrade: /plans**", 113 | parse_mode=ParseMode.MARKDOWN 114 | ) 115 | LOGGER.warning(f"Non-premium user {user_id} attempted /pbdl") 116 | return 117 | 118 | # Check if user has logged-in accounts 119 | user_session = user_sessions.find_one({"user_id": user_id}) 120 | if not user_session or not user_session.get("sessions"): 121 | await message.reply_text( 122 | "**❌ You must log in with /login to use /pbdl!**", 123 | parse_mode=ParseMode.MARKDOWN 124 | ) 125 | LOGGER.warning(f"User {user_id} not logged in for /pbdl") 126 | return 127 | 128 | # Extract URL 129 | if len(message.command) < 2: 130 | await message.reply_text( 131 | "**❌ Please provide a valid URL! Usage: /pbdl {url}**", 132 | parse_mode=ParseMode.MARKDOWN 133 | ) 134 | LOGGER.warning(f"No URL provided in /pbdl command by user {user_id}") 135 | return 136 | 137 | post_url = message.command[1] 138 | if "?" in post_url: 139 | post_url = post_url.split("?", 1)[0] 140 | 141 | # Validate URL format 142 | match = re.match(r"(?:https?://)?(?:t\.me|telegram\.me)/(?:c/)?([a-zA-Z0-9_]+|\d+)/(\d+)", post_url) 143 | if not match: 144 | await message.reply_text( 145 | "**❌ Invalid URL! Please use a valid Telegram message link (e.g., https://t.me/c/123/456)**", 146 | parse_mode=ParseMode.MARKDOWN 147 | ) 148 | LOGGER.warning(f"Invalid URL format: {post_url} by user {user_id}") 149 | return 150 | 151 | # Show account selection 152 | selected_session_id = await show_account_selection(client, message, post_url) 153 | if selected_session_id: 154 | await prompt_message_count(client, message, selected_session_id, post_url) 155 | 156 | @app.on_callback_query(filters.regex(r"^(pbdl_(select_|cancel_account|confirm|cancel)_)")) 157 | async def pbdl_callback_handler(client, callback_query): 158 | data = callback_query.data 159 | chat_id = callback_query.message.chat.id 160 | user_id = callback_query.from_user.id 161 | pbdl_info = pbdl_data.get(chat_id) 162 | 163 | if not pbdl_info or pbdl_info["user_id"] != user_id: 164 | await callback_query.message.edit_text( 165 | "**❌ Invalid or expired batch session!**", 166 | parse_mode=ParseMode.MARKDOWN 167 | ) 168 | return 169 | 170 | if data == "pbdl_cancel_account" or data.startswith("pbdl_cancel_"): 171 | await callback_query.message.edit_text( 172 | "**❌ Private batch download cancelled.**", 173 | parse_mode=ParseMode.MARKDOWN 174 | ) 175 | if chat_id in pbdl_data: 176 | del pbdl_data[chat_id] 177 | LOGGER.info(f"Private batch download cancelled by user {user_id}") 178 | return 179 | 180 | if data.startswith("pbdl_select_"): 181 | session_id = data.split("_", 2)[2] 182 | post_url = pbdl_info.get("post_url") 183 | original_message_id = pbdl_info.get("message_id") 184 | 185 | original_message = await client.get_messages(chat_id, original_message_id) 186 | await callback_query.message.delete() 187 | 188 | await prompt_message_count(client, original_message, session_id, post_url) 189 | 190 | elif data.startswith("pbdl_confirm_"): 191 | if pbdl_info.get("stage") != "confirmed": 192 | await callback_query.message.edit_text( 193 | "**❌ Please enter the number of messages first!**", 194 | parse_mode=ParseMode.MARKDOWN 195 | ) 196 | return 197 | 198 | await process_batch_download(client, callback_query.message, pbdl_info) 199 | if chat_id in pbdl_data: 200 | del pbdl_data[chat_id] 201 | 202 | async def prompt_message_count(client: Client, message: Message, session_id: str, post_url: str): 203 | chat_id = message.chat.id 204 | user_id = message.from_user.id 205 | pbdl_data[chat_id] = { 206 | "user_id": user_id, 207 | "session_id": session_id, 208 | "post_url": post_url, 209 | "stage": "await_count" 210 | } 211 | await message.reply_text( 212 | "**📥 How many messages do you want to scrape?**", 213 | reply_markup=InlineKeyboardMarkup([[ 214 | InlineKeyboardButton("Confirm", callback_data=f"pbdl_confirm_{chat_id}"), 215 | InlineKeyboardButton("Cancel", callback_data=f"pbdl_cancel_{chat_id}") 216 | ]]), 217 | parse_mode=ParseMode.MARKDOWN 218 | ) 219 | 220 | @app.on_message(filters.text & filters.create(lambda _, __, message: message.chat.id in pbdl_data and pbdl_data[message.chat.id].get("stage") == "await_count")) 221 | async def count_handler(client, message: Message): 222 | chat_id = message.chat.id 223 | user_id = message.from_user.id 224 | pbdl_info = pbdl_data.get(chat_id) 225 | if not pbdl_info or pbdl_info["user_id"] != user_id: 226 | return 227 | 228 | try: 229 | count = int(message.text) 230 | is_premium, max_messages = await get_batch_limits(user_id) 231 | if not is_premium: 232 | await message.reply_text( 233 | "**❌ Only premium users can use /pbdl! Upgrade: /plans**", 234 | parse_mode=ParseMode.MARKDOWN 235 | ) 236 | return 237 | if count < 1: 238 | await message.reply_text( 239 | "**❌ Please enter a valid number greater than 0!**", 240 | parse_mode=ParseMode.MARKDOWN 241 | ) 242 | return 243 | if count > max_messages: 244 | await message.reply_text( 245 | f"**❌ You can only scrape up to {max_messages} messages! Upgrade: /plans**", 246 | parse_mode=ParseMode.MARKDOWN 247 | ) 248 | return 249 | 250 | pbdl_info["count"] = count 251 | pbdl_info["stage"] = "confirmed" 252 | await message.reply_text( 253 | f"**✅ You have selected {count} message{'s' if count > 1 else ''} to scrape. Press confirm to start.**", 254 | reply_markup=InlineKeyboardMarkup([[ 255 | InlineKeyboardButton("Confirm", callback_data=f"pbdl_confirm_{chat_id}"), 256 | InlineKeyboardButton("Cancel", callback_data=f"pbdl_cancel_{chat_id}") 257 | ]]), 258 | parse_mode=ParseMode.MARKDOWN 259 | ) 260 | except ValueError: 261 | await message.reply_text( 262 | "**❌ Please enter valid integer!**", 263 | parse_mode=ParseMode.MARKDOWN 264 | ) 265 | 266 | async def process_batch_download(bot: Client, message: Message, pbdl_info: dict): 267 | user_id = pbdl_info["user_id"] 268 | chat_id = message.chat.id 269 | session_id = pbdl_info["session_id"] 270 | post_url = pbdl_info["post_url"] 271 | count = pbdl_info["count"] 272 | 273 | user_client = await get_user_client(user_id, session_id) 274 | if user_client is None: 275 | await message.reply_text( 276 | "**❌ Failed to initialize user client! Please try logging in again.**", 277 | parse_mode=ParseMode.MARKDOWN 278 | ) 279 | LOGGER.warning(f"Failed to initialize user client for user {user_id}, session {session_id}") 280 | return 281 | 282 | await message.edit_text( 283 | "**📥 Processing private batch download...**", 284 | parse_mode=ParseMode.MARKDOWN 285 | ) 286 | 287 | try: 288 | chat_id_from_url, start_message_id = getChatMsgID(post_url) 289 | message_ids = list(range(start_message_id, start_message_id + count)) 290 | messages = await user_client.get_messages(chat_id=chat_id_from_url, message_ids=message_ids) 291 | 292 | user_data = user_activity_collection.find_one({"user_id": user_id}) 293 | thumbnail_path = user_data.get("thumbnail_path") if user_data else None 294 | 295 | for chat_message in messages: 296 | if not chat_message: 297 | continue 298 | 299 | LOGGER.info(f"Downloading media from message ID {chat_message.id} for user {user_id}") 300 | 301 | if chat_message.document or chat_message.video or chat_message.audio: 302 | file_size = ( 303 | chat_message.document.file_size if chat_message.document else 304 | chat_message.video.file_size if chat_message.video else 305 | chat_message.audio.file_size 306 | ) 307 | if not await fileSizeLimit(file_size, message, "download", True): 308 | continue 309 | 310 | parsed_caption = await get_parsed_msg(chat_message.caption or "", chat_message.caption_entities) 311 | parsed_text = await get_parsed_msg(chat_message.text or "", chat_message.entities) 312 | 313 | if chat_message.media_group_id: 314 | if not await processMediaGroup(chat_message, bot, message): 315 | await bot.send_message( 316 | chat_id=chat_id, 317 | text="**❌ Could not extract any valid media from the media group.**", 318 | parse_mode=ParseMode.MARKDOWN 319 | ) 320 | continue 321 | 322 | elif chat_message.media: 323 | start_time = time() 324 | progress_message = await bot.send_message( 325 | chat_id=chat_id, 326 | text="**📥 Downloading Progress...**", 327 | parse_mode=ParseMode.MARKDOWN 328 | ) 329 | 330 | media_path = await chat_message.download( 331 | progress=Leaves.progress_for_pyrogram, 332 | progress_args=progressArgs("📥 Downloading Progress", progress_message, start_time) 333 | ) 334 | 335 | LOGGER.info(f"Downloaded media: {media_path} for user {user_id}") 336 | 337 | media_type = ( 338 | "photo" if chat_message.photo else 339 | "video" if chat_message.video else 340 | "audio" if chat_message.audio else 341 | "document" 342 | ) 343 | await send_media( 344 | bot, 345 | message, 346 | media_path, 347 | media_type, 348 | parsed_caption, 349 | progress_message, 350 | start_time, 351 | thumbnail_path=thumbnail_path 352 | ) 353 | 354 | if os.path.exists(media_path): 355 | os.remove(media_path) 356 | await progress_message.delete() 357 | 358 | elif chat_message.text or chat_message.caption: 359 | await bot.send_message( 360 | chat_id=chat_id, 361 | text=parsed_text or parsed_caption, 362 | parse_mode=ParseMode.MARKDOWN 363 | ) 364 | 365 | await asyncio.sleep(0.5) # Prevent rate-limiting 366 | 367 | completion_msg = await bot.send_message( 368 | chat_id=chat_id, 369 | text="**✅ Batch process completed successfully.**", 370 | parse_mode=ParseMode.MARKDOWN 371 | ) 372 | try: 373 | await bot.pin_chat_message(chat_id, completion_msg.id, both_sides=True) 374 | except Exception as e: 375 | LOGGER.warning(f"Failed to pin completion message for user {user_id}: {e}") 376 | 377 | LOGGER.info(f"Private batch download completed for user {user_id}: {count} messages from {post_url}") 378 | 379 | except (PeerIdInvalid, BadRequest): 380 | await bot.send_message( 381 | chat_id=chat_id, 382 | text="**❌ Make sure logged in user client is part of the channel.**", 383 | parse_mode=ParseMode.MARKDOWN 384 | ) 385 | LOGGER.error(f"User {user_id} not part of chat for URL: {post_url}") 386 | except Exception as e: 387 | await bot.send_message( 388 | chat_id=chat_id, 389 | text=f"**❌ Error processing batch download: {str(e)}**", 390 | parse_mode=ParseMode.MARKDOWN 391 | ) 392 | LOGGER.error(f"Failed to process batch download for user {user_id}: {e}") 393 | finally: 394 | if user: 395 | try: 396 | await user.stop() 397 | except Exception as e: 398 | LOGGER.error(f"Error stopping user client for user {user_id}: {e}") 399 | 400 | app.add_handler(app.on_message, group=1) 401 | app.add_handler(app.on_callback_query, group=2) -------------------------------------------------------------------------------- /plugins/login.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | import os 4 | import uuid 5 | from pyrogram import Client, filters 6 | from pyrogram.enums import ParseMode 7 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 8 | from pyrogram.errors import ( 9 | ApiIdInvalid, 10 | PhoneNumberInvalid, 11 | PhoneCodeInvalid, 12 | PhoneCodeExpired, 13 | SessionPasswordNeeded, 14 | PasswordHashInvalid, 15 | MessageNotModified 16 | ) 17 | from asyncio.exceptions import TimeoutError 18 | import asyncio 19 | from datetime import datetime 20 | from config import COMMAND_PREFIX 21 | from utils.logging_setup import LOGGER 22 | from core import prem_plan1, prem_plan2, prem_plan3, user_sessions 23 | 24 | # Constants for timeouts 25 | TIMEOUT_OTP = 600 # 10 minutes 26 | TIMEOUT_2FA = 300 # 5 minutes 27 | 28 | session_data = {} 29 | 30 | def setup_login_handler(app: Client): 31 | async def get_plan_limits(user_id: int) -> tuple[bool, int]: 32 | current_time = datetime.utcnow() 33 | if prem_plan3.find_one({"user_id": user_id, "expiry_date": {"$gt": current_time}}): 34 | return True, 10 # Plan3: 10 accounts 35 | elif prem_plan2.find_one({"user_id": user_id, "expiry_date": {"$gt": current_time}}): 36 | return True, 5 # Plan2: 5 accounts 37 | elif prem_plan1.find_one({"user_id": user_id, "expiry_date": {"$gt": current_time}}): 38 | return True, 1 # Plan1: 1 account 39 | return False, 0 # Free user: 0 accounts 40 | 41 | @app.on_message(filters.command("login", prefixes=COMMAND_PREFIX) & filters.private) 42 | async def login_command(client: Client, message: Message): 43 | user_id = message.from_user.id 44 | LOGGER.info(f"/login command received from user {user_id}") 45 | 46 | is_premium, max_accounts = await get_plan_limits(user_id) 47 | if not is_premium: 48 | await message.reply_text( 49 | "**❌ Only Premium Users Can Use /login Use /plans For Purchase Premium**", 50 | parse_mode=ParseMode.MARKDOWN 51 | ) 52 | LOGGER.warning(f"Non-premium user {user_id} attempted /login") 53 | return 54 | 55 | # Check current number of sessions 56 | user_session = user_sessions.find_one({"user_id": user_id}) or {"sessions": []} 57 | current_sessions = user_session.get("sessions", []) 58 | if len(current_sessions) >= max_accounts: 59 | await message.reply_text( 60 | f"**❌ You Have Reached Limit Of {max_accounts} Accounts {'s' if max_accounts > 1 else ''}! Use /logout To Clear Data.**", 61 | parse_mode=ParseMode.MARKDOWN 62 | ) 63 | LOGGER.warning(f"User {user_id} exceeded account limit ({max_accounts})") 64 | return 65 | 66 | session_data[message.chat.id] = {"type": "Pyrogram", "user_id": user_id} 67 | await client.send_message( 68 | chat_id=message.chat.id, 69 | text=( 70 | "**💥 Welcome to the Login SetUp!**\n" 71 | "**━━━━━━━━━━━━━━━━━**\n" 72 | "**This is a Login Module. We store your session securely in our database to keep you logged in until you use /logout.**\n\n" 73 | "**Note: Do not share your OTP with anyone.**" 74 | ), 75 | reply_markup=InlineKeyboardMarkup([[ 76 | InlineKeyboardButton("Start", callback_data="session_start_pyrogram"), 77 | InlineKeyboardButton("Close", callback_data="session_close") 78 | ]]), 79 | parse_mode=ParseMode.MARKDOWN 80 | ) 81 | 82 | @app.on_message(filters.command("logout", prefixes=COMMAND_PREFIX) & filters.private) 83 | async def logout_command(client: Client, message: Message): 84 | user_id = message.from_user.id 85 | LOGGER.info(f"/logout command received from user {user_id}") 86 | 87 | is_premium, _ = await get_plan_limits(user_id) 88 | if not is_premium: 89 | await message.reply_text( 90 | "**❌ Only Premium Users Can Use /login Use /plans For Purchase Premium**", 91 | parse_mode=ParseMode.MARKDOWN 92 | ) 93 | LOGGER.warning(f"Non-premium user {user_id} attempted /logout") 94 | return 95 | 96 | user_session = user_sessions.find_one({"user_id": user_id}) 97 | if not user_session or not user_session.get("sessions"): 98 | await message.reply_text( 99 | "**❌ You're Not Logged In!**", 100 | parse_mode=ParseMode.MARKDOWN 101 | ) 102 | LOGGER.warning(f"User {user_id} not logged in for /logout") 103 | return 104 | 105 | sessions = user_session.get("sessions", []) 106 | if len(sessions) == 1: 107 | # Single account: logout directly 108 | user_sessions.delete_one({"user_id": user_id}) 109 | session_file = f"temp_session_{user_id}_{sessions[0]['session_id']}.session" 110 | if os.path.exists(session_file): 111 | os.remove(session_file) 112 | await message.reply_text( 113 | f"**✅ Successfully Logout From Account '{sessions[0]['account_name']}'!**", 114 | parse_mode=ParseMode.MARKDOWN 115 | ) 116 | LOGGER.info(f"User {user_id} logged out of account {sessions[0]['account_name']}") 117 | else: 118 | # Multiple accounts: show selection 119 | buttons = [] 120 | for i in range(0, len(sessions), 2): 121 | row = [] 122 | for session in sessions[i:i+2]: 123 | row.append(InlineKeyboardButton( 124 | session["account_name"], 125 | callback_data=f"logout_select_{session['session_id']}" 126 | )) 127 | buttons.append(row) 128 | buttons.append([InlineKeyboardButton("Cancel", callback_data="session_close")]) 129 | await message.reply_text( 130 | "**📤 Select Account To Logout**", 131 | reply_markup=InlineKeyboardMarkup(buttons), 132 | parse_mode=ParseMode.MARKDOWN 133 | ) 134 | 135 | @app.on_callback_query(filters.regex(r"^(session_(start|restart|close)|logout_select_)")) 136 | async def callback_query_handler(client, callback_query): 137 | data = callback_query.data 138 | chat_id = callback_query.message.chat.id 139 | user_id = callback_query.from_user.id 140 | 141 | if data == "session_close": 142 | await callback_query.message.edit_text( 143 | "**❌ Cancelled. You Can Restart By Using /login**", 144 | parse_mode=ParseMode.MARKDOWN 145 | ) 146 | if chat_id in session_data: 147 | if session_data[chat_id].get("client_obj"): 148 | try: 149 | await session_data[chat_id]["client_obj"].disconnect() 150 | except Exception as e: 151 | LOGGER.error(f"Error disconnecting client for user {user_id}: {e}") 152 | del session_data[chat_id] 153 | return 154 | 155 | if data.startswith("logout_select_"): 156 | session_id = data.split("_", 2)[2] 157 | user_session = user_sessions.find_one({"user_id": user_id}) 158 | if user_session: 159 | sessions = user_session.get("sessions", []) 160 | for session in sessions: 161 | if session["session_id"] == session_id: 162 | sessions.remove(session) 163 | user_sessions.update_one( 164 | {"user_id": user_id}, 165 | {"$set": {"sessions": sessions}} 166 | ) 167 | session_file = f"temp_session_{user_id}_{session_id}.session" 168 | if os.path.exists(session_file): 169 | os.remove(session_file) 170 | await callback_query.message.edit_text( 171 | f"**✅ Successfully Logout From Account '{session['account_name']}'!**", 172 | parse_mode=ParseMode.MARKDOWN 173 | ) 174 | LOGGER.info(f"User {user_id} logged out of account {session['account_name']}") 175 | break 176 | return 177 | 178 | if data.startswith("session_start_"): 179 | session_type = "pyrogram" 180 | try: 181 | await callback_query.message.edit_text( 182 | "**Send Your API_ID**", 183 | reply_markup=InlineKeyboardMarkup([[ 184 | InlineKeyboardButton("Restart", callback_data=f"session_restart_{session_type}"), 185 | InlineKeyboardButton("Close", callback_data="session_close") 186 | ]]), 187 | parse_mode=ParseMode.MARKDOWN 188 | ) 189 | session_data[chat_id] = {"type": "Pyrogram", "user_id": user_id, "stage": "api_id"} 190 | except MessageNotModified: 191 | LOGGER.debug(f"Message not modified for session_start by user {user_id}") 192 | session_data[chat_id] = {"type": "Pyrogram", "user_id": user_id, "stage": "api_id"} 193 | 194 | elif data.startswith("session_restart_"): 195 | session_type = "pyrogram" 196 | # Reset session data 197 | if chat_id in session_data and session_data[chat_id].get("client_obj"): 198 | try: 199 | await session_data[chat_id]["client_obj"].disconnect() 200 | except Exception as e: 201 | LOGGER.error(f"Error disconnecting client during restart for user {user_id}: {e}") 202 | session_data[chat_id] = {"type": "Pyrogram", "user_id": user_id, "stage": "api_id"} 203 | try: 204 | await callback_query.message.edit_text( 205 | "**Send Your API_ID (Restarted)**", 206 | reply_markup=InlineKeyboardMarkup([[ 207 | InlineKeyboardButton("Restart", callback_data=f"session_restart_{session_type}"), 208 | InlineKeyboardButton("Close", callback_data="session_close") 209 | ]]), 210 | parse_mode=ParseMode.MARKDOWN 211 | ) 212 | except MessageNotModified: 213 | LOGGER.debug(f"Message not modified for session_restart by user {user_id}") 214 | 215 | @app.on_message(filters.text & filters.create(lambda _, __, message: message.chat.id in session_data)) 216 | async def text_handler(client, message: Message): 217 | chat_id = message.chat.id 218 | if chat_id not in session_data: 219 | return 220 | 221 | session = session_data[chat_id] 222 | stage = session.get("stage") 223 | 224 | if stage == "api_id": 225 | try: 226 | api_id = int(message.text) 227 | session["api_id"] = api_id 228 | await client.send_message( 229 | chat_id=message.chat.id, 230 | text="**Send Your API_HASH**", 231 | reply_markup=InlineKeyboardMarkup([[ 232 | InlineKeyboardButton("Restart", callback_data=f"session_restart_pyrogram"), 233 | InlineKeyboardButton("Close", callback_data="session_close") 234 | ]]), 235 | parse_mode=ParseMode.MARKDOWN 236 | ) 237 | session["stage"] = "api_hash" 238 | except ValueError: 239 | await message.reply_text( 240 | "**❌ Invalid API_ID Please Provide A Valid Integer**", 241 | parse_mode=ParseMode.MARKDOWN 242 | ) 243 | LOGGER.error(f"Invalid API ID provided by user {message.from_user.id}") 244 | 245 | elif stage == "api_hash": 246 | session["api_hash"] = message.text 247 | await client.send_message( 248 | chat_id=message.chat.id, 249 | text="**Send Your Phone Number\n[Example: +880xxxxxxxxxx]**", 250 | reply_markup=InlineKeyboardMarkup([[ 251 | InlineKeyboardButton("Restart", callback_data=f"session_restart_pyrogram"), 252 | InlineKeyboardButton("Close", callback_data="session_close") 253 | ]]), 254 | parse_mode=ParseMode.MARKDOWN 255 | ) 256 | session["stage"] = "phone_number" 257 | 258 | elif stage == "phone_number": 259 | session["phone_number"] = message.text 260 | otp_message = await client.send_message( 261 | chat_id=message.chat.id, 262 | text="**💥 Sending OTP Check PM...**", 263 | parse_mode=ParseMode.MARKDOWN 264 | ) 265 | await send_otp(client, message, otp_message) 266 | 267 | elif stage == "otp": 268 | otp = ''.join([char for char in message.text if char.isdigit()]) 269 | session["otp"] = otp 270 | otp_message = await client.send_message( 271 | chat_id=message.chat.id, 272 | text="**💥 Validating OTP...**", 273 | parse_mode=ParseMode.MARKDOWN 274 | ) 275 | await validate_otp(client, message, otp_message) 276 | 277 | elif stage == "2fa": 278 | session["password"] = message.text 279 | await validate_2fa(client, message) 280 | 281 | async def send_otp(client, message, otp_message): 282 | session = session_data[message.chat.id] 283 | api_id = session["api_id"] 284 | api_hash = session["api_hash"] 285 | phone_number = session["phone_number"] 286 | user_id = session["user_id"] 287 | session_id = str(uuid.uuid4()) 288 | 289 | # Use file-based storage 290 | session_name = f"temp_session_{user_id}_{session_id}" 291 | client_obj = Client(session_name, api_id, api_hash) 292 | await client_obj.connect() 293 | 294 | try: 295 | code = await client_obj.send_code(phone_number) 296 | session["client_obj"] = client_obj 297 | session["code"] = code 298 | session["stage"] = "otp" 299 | session["session_id"] = session_id 300 | 301 | asyncio.create_task(handle_otp_timeout(client, message)) 302 | 303 | await client.send_message( 304 | chat_id=message.chat.id, 305 | text="**✅ Send The OTP As Text. Please Send A Text Message Embedding The OTP Like: 'AB5 CD0 EF3 GH7 IJ6'**", 306 | reply_markup=InlineKeyboardMarkup([[ 307 | InlineKeyboardButton("Restart", callback_data=f"session_restart_pyrogram"), 308 | InlineKeyboardButton("Close", callback_data="session_close") 309 | ]]), 310 | parse_mode=ParseMode.MARKDOWN 311 | ) 312 | await otp_message.delete() 313 | except ApiIdInvalid: 314 | await client.send_message( 315 | chat_id=message.chat.id, 316 | text="**❌ Invalid API_ID & API_HASH Combination**", 317 | reply_markup=InlineKeyboardMarkup([[ 318 | InlineKeyboardButton("Restart", callback_data=f"session_restart_pyrogram"), 319 | InlineKeyboardButton("Close", callback_data="session_close") 320 | ]]), 321 | parse_mode=ParseMode.MARKDOWN 322 | ) 323 | await otp_message.delete() 324 | LOGGER.error(f"Invalid API_ID/API_HASH for user {message.from_user.id}") 325 | except PhoneNumberInvalid: 326 | await client.send_message( 327 | chat_id=message.chat.id, 328 | text="**❌ Sorry The Phone Number Invalid**", 329 | reply_markup=InlineKeyboardMarkup([[ 330 | InlineKeyboardButton("Restart", callback_data=f"session_restart_pyrogram"), 331 | InlineKeyboardButton("Close", callback_data="session_close") 332 | ]]), 333 | parse_mode=ParseMode.MARKDOWN 334 | ) 335 | await otp_message.delete() 336 | LOGGER.error(f"Invalid phone number for user {message.from_user.id}") 337 | 338 | async def handle_otp_timeout(client, message): 339 | await asyncio.sleep(TIMEOUT_OTP) 340 | if message.chat.id in session_data and session_data[message.chat.id].get("stage") == "otp": 341 | await client.send_message( 342 | chat_id=message.chat.id, 343 | text="**❌Your OTP Has Expired**", 344 | parse_mode=ParseMode.MARKDOWN 345 | ) 346 | if session_data[message.chat.id].get("client_obj"): 347 | try: 348 | await session_data[message.chat.id]["client_obj"].disconnect() 349 | except Exception as e: 350 | LOGGER.error(f"Error disconnecting client during OTP timeout for user {message.from_user.id}: {e}") 351 | del session_data[message.chat.id] 352 | LOGGER.info(f"OTP timed out for user {message.from_user.id}") 353 | 354 | async def validate_otp(client, message, otp_message): 355 | session = session_data[message.chat.id] 356 | client_obj = session["client_obj"] 357 | phone_number = session["phone_number"] 358 | otp = session["otp"] 359 | code = session["code"] 360 | 361 | try: 362 | await client_obj.sign_in(phone_number, code.phone_code_hash, otp) 363 | await generate_session(client, message) 364 | await otp_message.delete() 365 | except PhoneCodeInvalid: 366 | await client.send_message( 367 | chat_id=message.chat.id, 368 | text="**❌ Sorry Bro Wrong OTP**", 369 | reply_markup=InlineKeyboardMarkup([[ 370 | InlineKeyboardButton("Restart", callback_data=f"session_restart_pyrogram"), 371 | InlineKeyboardButton("Close", callback_data="session_close") 372 | ]]), 373 | parse_mode=ParseMode.MARKDOWN 374 | ) 375 | await otp_message.delete() 376 | LOGGER.error(f"Invalid OTP provided by user {message.from_user.id}") 377 | except PhoneCodeExpired: 378 | await client.send_message( 379 | chat_id=message.chat.id, 380 | text="**❌ Your OTP Has Expired**", 381 | reply_markup=InlineKeyboardMarkup([[ 382 | InlineKeyboardButton("Restart", callback_data=f"session_restart_pyrogram"), 383 | InlineKeyboardButton("Close", callback_data="session_close") 384 | ]]), 385 | parse_mode=ParseMode.MARKDOWN 386 | ) 387 | await otp_message.delete() 388 | LOGGER.error(f"Expired OTP for user {message.from_user.id}") 389 | except SessionPasswordNeeded: 390 | session["stage"] = "2fa" 391 | asyncio.create_task(handle_2fa_timeout(client, message)) 392 | await client.send_message( 393 | chat_id=message.chat.id, 394 | text="**❌ 2FA Is Required To Login Kindly Send Your 2 FA**", 395 | reply_markup=InlineKeyboardMarkup([[ 396 | InlineKeyboardButton("Restart", callback_data=f"session_restart_pyrogram"), 397 | InlineKeyboardButton("Close", callback_data="session_close") 398 | ]]), 399 | parse_mode=ParseMode.MARKDOWN 400 | ) 401 | await otp_message.delete() 402 | LOGGER.info(f"2FA required for user {message.from_user.id}") 403 | 404 | async def handle_2fa_timeout(client, message): 405 | await asyncio.sleep(TIMEOUT_2FA) 406 | if message.chat.id in session_data and session_data[message.chat.id].get("stage") == "2fa": 407 | await client.send_message( 408 | chat_id=message.chat.id, 409 | text="**❌ Your 2 FA Input Is Expired**", 410 | parse_mode=ParseMode.MARKDOWN 411 | ) 412 | if session_data[message.chat.id].get("client_obj"): 413 | try: 414 | await session_data[message.chat.id]["client_obj"].disconnect() 415 | except Exception as e: 416 | LOGGER.error(f"Error disconnecting client during 2FA timeout for user {message.from_user.id}: {e}") 417 | del session_data[message.chat.id] 418 | LOGGER.info(f"2FA timed out for user {message.from_user.id}") 419 | 420 | async def validate_2fa(client, message): 421 | session = session_data[message.chat.id] 422 | client_obj = session["client_obj"] 423 | password = session["password"] 424 | 425 | try: 426 | await client_obj.check_password(password=password) 427 | await generate_session(client, message) 428 | except PasswordHashInvalid: 429 | await client.send_message( 430 | chat_id=message.chat.id, 431 | text="**❌ Invalid 2 FA Password Provided**", 432 | reply_markup=InlineKeyboardMarkup([[ 433 | InlineKeyboardButton("Restart", callback_data=f"session_restart_pyrogram"), 434 | InlineKeyboardButton("Close", callback_data="session_close") 435 | ]]), 436 | parse_mode=ParseMode.MARKDOWN 437 | ) 438 | LOGGER.error(f"Invalid 2FA password provided by user {message.from_user.id}") 439 | 440 | async def generate_session(client, message): 441 | session = session_data[message.chat.id] 442 | client_obj = session["client_obj"] 443 | user_id = session["user_id"] 444 | session_id = session["session_id"] 445 | 446 | # Fetch account name 447 | user_info = await client_obj.get_me() 448 | account_name = f"{user_info.first_name} {user_info.last_name or ''}".strip() 449 | 450 | string_session = await client_obj.export_session_string() 451 | 452 | # Store session in user_sessions 453 | user_sessions.update_one( 454 | {"user_id": user_id}, 455 | { 456 | "$push": { 457 | "sessions": { 458 | "session_id": session_id, 459 | "session_string": string_session, 460 | "account_name": account_name 461 | } 462 | } 463 | }, 464 | upsert=True 465 | ) 466 | 467 | # Wait to allow background updates to complete 468 | await asyncio.sleep(2) 469 | await client_obj.disconnect() 470 | 471 | # Clean up temporary session file 472 | session_file = f"temp_session_{user_id}_{session_id}.session" 473 | if os.path.exists(session_file): 474 | os.remove(session_file) 475 | 476 | await client.send_message( 477 | chat_id=message.chat.id, 478 | text=f"**✅ Successfully Logout From As '{account_name}'! You Can Now User /pdl To Download Private Media**", 479 | parse_mode=ParseMode.MARKDOWN 480 | ) 481 | LOGGER.info(f"Session string generated and saved for user {user_id} as {account_name}") 482 | del session_data[message.chat.id] 483 | 484 | app.add_handler(app.on_message, group=1) 485 | app.add_handler(app.on_callback_query, group=2) -------------------------------------------------------------------------------- /plugins/plan.py: -------------------------------------------------------------------------------- 1 | # Copyright @ISmartDevs 2 | # Channel t.me/TheSmartDev 3 | import uuid 4 | import hashlib 5 | import time 6 | from datetime import datetime, timedelta 7 | from pyrogram import Client, filters 8 | from pyrogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton 9 | from pyrogram.raw.functions.messages import SendMedia, SetBotPrecheckoutResults 10 | from pyrogram.raw.types import ( 11 | InputMediaInvoice, 12 | Invoice, 13 | DataJSON, 14 | LabeledPrice, 15 | UpdateBotPrecheckoutQuery, 16 | UpdateNewMessage, 17 | MessageService, 18 | MessageActionPaymentSentMe, 19 | PeerUser, 20 | PeerChat, 21 | PeerChannel, 22 | ReplyInlineMarkup, 23 | KeyboardButtonRow, 24 | KeyboardButtonBuy 25 | ) 26 | from pyrogram.handlers import MessageHandler, CallbackQueryHandler, RawUpdateHandler 27 | from pyrogram.enums import ParseMode 28 | from pyrogram.errors import UserIdInvalid, UsernameInvalid, PeerIdInvalid 29 | from config import COMMAND_PREFIX, DEVELOPER_USER_ID 30 | from utils import LOGGER 31 | from core import prem_plan1, prem_plan2, prem_plan3, daily_limit 32 | 33 | # Plan configurations 34 | PLANS = { 35 | "plan1": {"stars": 150, "name": "Plan Premium 1", "accounts": 1, "max_downloads": 1000, "private_support": True, "inbox_support": False}, 36 | "plan2": {"stars": 500, "name": "Plan Premium 2", "accounts": 5, "max_downloads": 2000, "private_support": True, "inbox_support": True}, 37 | "plan3": {"stars": 1000, "name": "Plan Premium 3", "accounts": 10, "max_downloads": "unlimited", "private_support": True, "inbox_support": True} 38 | } 39 | 40 | # Shared Strings and Emojis 41 | PLAN_OPTIONS_TEXT = """ 42 | **💎 Choose your Premium Plan 💎** 43 | 44 | **Plan Premium 1 - 150 🌟** 45 | • 1 Account Login 46 | • Validity 1 Month 47 | • Mass Content Downloader (Max 1000) 48 | • Private Chat/Channel Supported ✅ 49 | • Private Inbox/Bot Supported ❌ 50 | 51 | **Plan Premium 2 - 500 🌟** 52 | • 5 Account Login 53 | • Validity 1 Month 54 | • Mass Content Downloader (Max 2000) 55 | • Private Chat/Channel Supported ✅ 56 | • Private Inbox/Bot Supported ✅ 57 | 58 | **Plan Premium 3 - 1000 🌟** 59 | • 10 Account Login 60 | • Validity 1 Month 61 | • Mass Content Unlimited 62 | • Private Chat/Channel Supported ✅ 63 | • Private Inbox/Bot Supported ✅ 64 | 65 | **👉 Choose a plan to unlock these features!** 66 | """ 67 | 68 | PAYMENT_SUCCESS_TEXT = """ 69 | **✅ Plan Purchase Successful!** 70 | 71 | **🎉 Thanks {0} for purchasing {1} with {2} Stars!** 72 | Your premium features are now active! Enjoy 🚀 73 | 74 | **🧾 Transaction ID: {3}** 75 | """ 76 | 77 | ADMIN_NOTIFICATION_TEXT = """ 78 | **🌟 New Plan Purchase!** 79 | **✨ From: {0} 💫** 80 | **⁉️ User ID: {1}** 81 | **🌐 Username: {2}** 82 | **💥 Plan: {3}** 83 | **🌟 Amount: {4} Stars** 84 | **📝 Transaction ID: {5}** 85 | """ 86 | 87 | INVOICE_CREATION_TEXT = "**Generating invoice for {0} Stars... Please wait ⏳**" 88 | INVOICE_CONFIRMATION_TEXT = "**✅ Invoice for {0} Stars has been generated! Proceed to pay via the button below.**" 89 | DUPLICATE_INVOICE_TEXT = "**🚫 Wait! Another purchase is already in progress!**" 90 | INVOICE_FAILED_TEXT = "**❌ Invoice creation failed! Try again.**" 91 | PAYMENT_FAILED_TEXT = "**❌ Payment declined! Contact support.**" 92 | 93 | # Store active invoices to prevent duplicates 94 | active_invoices = {} 95 | 96 | def setup_plan_handler(app: Client): 97 | # Generate Plan Selection Buttons 98 | def get_plan_buttons(): 99 | return InlineKeyboardMarkup([ 100 | [InlineKeyboardButton("Buy Plan 1", callback_data="buy_plan1"), 101 | InlineKeyboardButton("Buy Plan 2", callback_data="buy_plan2")], 102 | [InlineKeyboardButton("Buy Plan 3", callback_data="buy_plan3")] 103 | ]) 104 | 105 | # Generate and Send Invoice 106 | async def generate_invoice(client: Client, chat_id: int, user_id: int, plan_key: str): 107 | if active_invoices.get(user_id): 108 | await client.send_message(chat_id, DUPLICATE_INVOICE_TEXT, parse_mode=ParseMode.MARKDOWN) 109 | return 110 | 111 | plan = PLANS[plan_key] 112 | amount = plan["stars"] 113 | plan_name = plan["name"] 114 | 115 | # Send loading message 116 | back_button = InlineKeyboardMarkup([[InlineKeyboardButton("🔙 Back", callback_data="show_plan_options")]]) 117 | loading_message = await client.send_message( 118 | chat_id, 119 | INVOICE_CREATION_TEXT.format(amount), 120 | parse_mode=ParseMode.MARKDOWN 121 | ) 122 | 123 | try: 124 | active_invoices[user_id] = True 125 | 126 | # Generate unique payload 127 | timestamp = int(time.time()) 128 | unique_id = str(uuid.uuid4())[:8] 129 | invoice_payload = f"plan_{plan_key}_{user_id}_{amount}_{timestamp}_{unique_id}" 130 | random_id = int(hashlib.sha256(invoice_payload.encode()).hexdigest(), 16) % (2**63) 131 | 132 | title = f"Purchase {plan_name}" 133 | description = f"Unlock premium features with {plan_name} for {amount} Stars. Enjoy {plan['accounts']} account(s), {plan['max_downloads']} downloads, and private chat support!" 134 | currency = "XTR" 135 | 136 | # Create Invoice object 137 | invoice = Invoice( 138 | currency=currency, 139 | prices=[LabeledPrice(label=f"⭐ {amount} Stars", amount=amount)], 140 | max_tip_amount=0, 141 | suggested_tip_amounts=[], 142 | recurring=False, 143 | test=False, 144 | name_requested=False, 145 | phone_requested=False, 146 | email_requested=False, 147 | shipping_address_requested=False, 148 | flexible=False 149 | ) 150 | 151 | # Create InputMediaInvoice 152 | media = InputMediaInvoice( 153 | title=title, 154 | description=description, 155 | invoice=invoice, 156 | payload=invoice_payload.encode(), 157 | provider="STARS", 158 | provider_data=DataJSON(data="{}") 159 | ) 160 | 161 | # Create ReplyInlineMarkup 162 | markup = ReplyInlineMarkup( 163 | rows=[ 164 | KeyboardButtonRow( 165 | buttons=[ 166 | KeyboardButtonBuy(text=f"Buy {plan_name}") 167 | ] 168 | ) 169 | ] 170 | ) 171 | 172 | # Resolve peer 173 | peer = await client.resolve_peer(chat_id) 174 | 175 | # Send the invoice 176 | await client.invoke( 177 | SendMedia( 178 | peer=peer, 179 | media=media, 180 | message="", 181 | random_id=random_id, 182 | reply_markup=markup 183 | ) 184 | ) 185 | 186 | # Edit loading message to confirmation 187 | await client.edit_message_text( 188 | chat_id, 189 | loading_message.id, 190 | INVOICE_CONFIRMATION_TEXT.format(amount), 191 | parse_mode=ParseMode.MARKDOWN, 192 | reply_markup=back_button 193 | ) 194 | LOGGER.info(f"✅ Invoice sent for {plan_name} ({amount} stars) to user {user_id}") 195 | except Exception as e: 196 | LOGGER.error(f"❌ Failed to generate invoice for user {user_id}: {str(e)}") 197 | await client.edit_message_text( 198 | chat_id, 199 | loading_message.id, 200 | INVOICE_FAILED_TEXT, 201 | parse_mode=ParseMode.MARKDOWN, 202 | reply_markup=back_button 203 | ) 204 | finally: 205 | active_invoices.pop(user_id, None) 206 | 207 | # Handle /plans Command 208 | async def plans_command(client: Client, message: Message): 209 | user_id = message.from_user.id 210 | LOGGER.info(f"Plans command received: user: {user_id}, chat: {message.chat.id}") 211 | reply_markup = get_plan_buttons() 212 | await client.send_message( 213 | chat_id=message.chat.id, 214 | text=PLAN_OPTIONS_TEXT, 215 | parse_mode=ParseMode.MARKDOWN, 216 | reply_markup=reply_markup 217 | ) 218 | 219 | # Handle /add Command 220 | async def add_premium_command(client: Client, message: Message): 221 | if message.from_user.id != DEVELOPER_USER_ID: 222 | await message.reply_text( 223 | "**❌ Only Admins can use this command!**", 224 | parse_mode=ParseMode.MARKDOWN 225 | ) 226 | LOGGER.warning(f"Unauthorized /add attempt by user {message.from_user.id}") 227 | return 228 | 229 | if len(message.command) != 3 or message.command[2] not in ["1", "2", "3"]: 230 | await message.reply_text( 231 | "**❌ Invalid format! Usage: /add {username/userid} {1, 2, or 3}**", 232 | parse_mode=ParseMode.MARKDOWN 233 | ) 234 | LOGGER.warning(f"Invalid /add format by admin {message.from_user.id}") 235 | return 236 | 237 | identifier = message.command[1] 238 | plan_number = message.command[2] 239 | plan_key = f"plan{plan_number}" 240 | target_user_id = None 241 | 242 | try: 243 | # Try resolving as user ID 244 | try: 245 | target_user_id = int(identifier) 246 | except ValueError: 247 | # Try resolving as username 248 | if identifier.startswith("@"): 249 | identifier = identifier[1:] 250 | user = await client.get_users(identifier) 251 | target_user_id = user.id 252 | 253 | # Map plan number to collection 254 | plan_collection = { 255 | "plan1": prem_plan1, 256 | "plan2": prem_plan2, 257 | "plan3": prem_plan3 258 | }[plan_key] 259 | plan = PLANS[plan_key] 260 | expiry_date = datetime.utcnow() + timedelta(days=30) 261 | 262 | # Remove user from other plans 263 | for other_plan_key, other_collection in [ 264 | ("plan1", prem_plan1), 265 | ("plan2", prem_plan2), 266 | ("plan3", prem_plan3) 267 | ]: 268 | if other_plan_key != plan_key: 269 | other_collection.delete_one({"user_id": target_user_id}) 270 | 271 | # Update user in the selected plan 272 | plan_collection.update_one( 273 | {"user_id": target_user_id}, 274 | { 275 | "$set": { 276 | "user_id": target_user_id, 277 | "plan": plan_key, 278 | "plan_name": plan["name"], 279 | "accounts": plan["accounts"], 280 | "max_downloads": plan["max_downloads"], 281 | "private_support": plan["private_support"], 282 | "inbox_support": plan["inbox_support"], 283 | "expiry_date": expiry_date 284 | } 285 | }, 286 | upsert=True 287 | ) 288 | 289 | await message.reply_text( 290 | f"**✅ Promoted user {identifier} (ID: {target_user_id}) to {plan['name']}!**", 291 | parse_mode=ParseMode.MARKDOWN 292 | ) 293 | LOGGER.info(f"Promoted user {target_user_id} to {plan['name']} by admin {message.from_user.id}") 294 | 295 | except (UserIdInvalid, UsernameInvalid, PeerIdInvalid): 296 | await message.reply_text( 297 | f"**❌ Invalid username or user ID: {identifier}!**", 298 | parse_mode=ParseMode.MARKDOWN 299 | ) 300 | LOGGER.error(f"Invalid user {identifier} in /add by admin {message.from_user.id}") 301 | except Exception as e: 302 | await message.reply_text( 303 | f"**❌ Error promoting user: {str(e)}**", 304 | parse_mode=ParseMode.MARKDOWN 305 | ) 306 | LOGGER.error(f"Error in /add for user {identifier} by admin {message.from_user.id}: {str(e)}") 307 | 308 | # Handle /rm Command 309 | async def remove_premium_command(client: Client, message: Message): 310 | if message.from_user.id != DEVELOPER_USER_ID: 311 | await message.reply_text( 312 | "**❌ Only Admins can use this command!**", 313 | parse_mode=ParseMode.MARKDOWN 314 | ) 315 | LOGGER.warning(f"Unauthorized /rm attempt by user {message.from_user.id}") 316 | return 317 | 318 | if len(message.command) != 2: 319 | await message.reply_text( 320 | "**❌ Invalid format! Usage: /rm {username/userid}**", 321 | parse_mode=ParseMode.MARKDOWN 322 | ) 323 | LOGGER.warning(f"Invalid /rm format by admin {message.from_user.id}") 324 | return 325 | 326 | identifier = message.command[1] 327 | target_user_id = None 328 | 329 | try: 330 | # Try resolving as user ID 331 | try: 332 | target_user_id = int(identifier) 333 | except ValueError: 334 | # Try resolving as username 335 | if identifier.startswith("@"): 336 | identifier = identifier[1:] 337 | user = await client.get_users(identifier) 338 | target_user_id = user.id 339 | 340 | # Remove user from all premium plans 341 | removed = False 342 | for plan_collection in [prem_plan1, prem_plan2, prem_plan3]: 343 | result = plan_collection.delete_one({"user_id": target_user_id}) 344 | if result.deleted_count > 0: 345 | removed = True 346 | 347 | if removed: 348 | await message.reply_text( 349 | f"**✅ Removed user {identifier} (ID: {target_user_id}) from premium plans!**", 350 | parse_mode=ParseMode.MARKDOWN 351 | ) 352 | LOGGER.info(f"Removed user {target_user_id} from premium plans by admin {message.from_user.id}") 353 | else: 354 | await message.reply_text( 355 | f"**❌ User {identifier} (ID: {target_user_id}) is not in any premium plan!**", 356 | parse_mode=ParseMode.MARKDOWN 357 | ) 358 | LOGGER.info(f"User {target_user_id} not in premium plans for /rm by admin {message.from_user.id}") 359 | 360 | except (UserIdInvalid, UsernameInvalid, PeerIdInvalid): 361 | await message.reply_text( 362 | f"**❌ Invalid username or user ID: {identifier}!**", 363 | parse_mode=ParseMode.MARKDOWN 364 | ) 365 | LOGGER.error(f"Invalid user {identifier} in /rm by admin {message.from_user.id}") 366 | except Exception as e: 367 | await message.reply_text( 368 | f"**❌ Error removing user: {str(e)}**", 369 | parse_mode=ParseMode.MARKDOWN 370 | ) 371 | LOGGER.error(f"Error in /rm for user {identifier} by admin {message.from_user.id}: {str(e)}") 372 | 373 | # Handle Callback Queries for Plan Selection 374 | async def handle_plan_callback(client: Client, callback_query: CallbackQuery): 375 | data = callback_query.data 376 | chat_id = callback_query.message.chat.id 377 | user_id = callback_query.from_user.id 378 | 379 | LOGGER.info(f"Callback query received: data={data}, user: {user_id}, chat: {chat_id}") 380 | plan_mapping = { 381 | "buy_plan1": "plan1", 382 | "buy_plan2": "plan2", 383 | "buy_plan3": "plan3" 384 | } 385 | if data in plan_mapping: 386 | plan_key = plan_mapping[data] 387 | await generate_invoice(client, chat_id, user_id, plan_key) 388 | await callback_query.answer(f"✅ Invoice Generated for {PLANS[plan_key]['name']}!") 389 | elif data == "show_plan_options": 390 | reply_markup = get_plan_buttons() 391 | await client.edit_message_text( 392 | chat_id, 393 | callback_query.message.id, 394 | PLAN_OPTIONS_TEXT, 395 | parse_mode=ParseMode.MARKDOWN, 396 | reply_markup=reply_markup 397 | ) 398 | await callback_query.answer() 399 | 400 | # Raw Update Handler for Payment Processing 401 | async def raw_update_handler(client: Client, update, users, chats): 402 | if isinstance(update, UpdateBotPrecheckoutQuery): 403 | try: 404 | await client.invoke( 405 | SetBotPrecheckoutResults( 406 | query_id=update.query_id, 407 | success=True 408 | ) 409 | ) 410 | LOGGER.info(f"✅ Pre-checkout query {update.query_id} OK for user {update.user_id}") 411 | except Exception as e: 412 | LOGGER.error(f"❌ Pre-checkout query {update.query_id} failed: {str(e)}") 413 | await client.invoke( 414 | SetBotPrecheckoutResults( 415 | query_id=update.query_id, 416 | success=False, 417 | error="Failed to process pre-checkout." 418 | ) 419 | ) 420 | elif isinstance(update, UpdateNewMessage) and isinstance(update.message, MessageService) and isinstance(update.message.action, MessageActionPaymentSentMe): 421 | payment = update.message.action 422 | try: 423 | # Extract user_id and chat_id 424 | user_id = update.message.from_id.user_id if update.message.from_id and hasattr(update.message.from_id, 'user_id') else None 425 | if not user_id and users: 426 | possible_user_ids = [uid for uid in users if uid > 0] 427 | user_id = possible_user_ids[0] if possible_user_ids else None 428 | 429 | if isinstance(update.message.peer_id, PeerUser): 430 | chat_id = update.message.peer_id.user_id 431 | elif isinstance(update.message.peer_id, PeerChat): 432 | chat_id = update.message.peer_id.chat_id 433 | elif isinstance(update.message.peer_id, PeerChannel): 434 | chat_id = update.message.peer_id.channel_id 435 | else: 436 | chat_id = None 437 | 438 | if not user_id or not chat_id: 439 | raise ValueError(f"Invalid chat_id ({chat_id}) or user_id ({user_id})") 440 | 441 | # Get user details 442 | user = users.get(user_id) 443 | full_name = f"{user.first_name} {getattr(user, 'last_name', '')}".strip() or "Unknown" if user else "Unknown" 444 | username = f"@{user.username}" if user and user.username else "@N/A" 445 | 446 | # Determine plan from payload 447 | payload = payment.payload.decode() 448 | plan_key = payload.split("_")[1] 449 | plan = PLANS.get(plan_key) 450 | if not plan: 451 | raise ValueError(f"Invalid plan key in payload: {payload}") 452 | 453 | # Update user plan in database 454 | plan_collection = { 455 | "plan1": prem_plan1, 456 | "plan2": prem_plan2, 457 | "plan3": prem_plan3 458 | }[plan_key] 459 | expiry_date = datetime.utcnow() + timedelta(days=30) 460 | 461 | # Remove user from other plans 462 | for other_plan_key, other_collection in [ 463 | ("plan1", prem_plan1), 464 | ("plan2", prem_plan2), 465 | ("plan3", prem_plan3) 466 | ]: 467 | if other_plan_key != plan_key: 468 | other_collection.delete_one({"user_id": user_id}) 469 | 470 | plan_collection.update_one( 471 | {"user_id": user_id}, 472 | { 473 | "$set": { 474 | "user_id": user_id, 475 | "plan": plan_key, 476 | "plan_name": plan["name"], 477 | "accounts": plan["accounts"], 478 | "max_downloads": plan["max_downloads"], 479 | "private_support": plan["private_support"], 480 | "inbox_support": plan["inbox_support"], 481 | "expiry_date": expiry_date 482 | } 483 | }, 484 | upsert=True 485 | ) 486 | 487 | # Send success message to user 488 | await client.send_message( 489 | chat_id=chat_id, 490 | text=PAYMENT_SUCCESS_TEXT.format(full_name, plan["name"], payment.total_amount, payment.charge.id), 491 | parse_mode=ParseMode.MARKDOWN 492 | ) 493 | 494 | # Notify admins 495 | admin_text = ADMIN_NOTIFICATION_TEXT.format(full_name, user_id, username, plan["name"], payment.total_amount, payment.charge.id) 496 | for admin_id in [DEVELOPER_USER_ID] if isinstance(DEVELOPER_USER_ID, int) else DEVELOPER_USER_ID: 497 | try: 498 | await client.send_message( 499 | chat_id=admin_id, 500 | text=admin_text, 501 | parse_mode=ParseMode.MARKDOWN 502 | ) 503 | except Exception as e: 504 | LOGGER.error(f"❌ Failed to notify admin {admin_id}: {str(e)}") 505 | 506 | LOGGER.info(f"✅ Payment processed for user {user_id}: {plan['name']} ({payment.total_amount} Stars)") 507 | except Exception as e: 508 | LOGGER.error(f"❌ Payment processing failed for user {user_id if user_id else 'unknown'}: {str(e)}") 509 | if 'chat_id' in locals() and chat_id: 510 | await client.send_message( 511 | chat_id=chat_id, 512 | text=PAYMENT_FAILED_TEXT, 513 | parse_mode=ParseMode.MARKDOWN, 514 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("📞 Support", url=f"tg://user?id={DEVELOPER_USER_ID}")]]) 515 | ) 516 | 517 | # Register Handlers 518 | app.add_handler( 519 | MessageHandler( 520 | plans_command, 521 | filters = (filters.command(["plans", "buy"], prefixes=COMMAND_PREFIX) & (filters.private | filters.group)) 522 | ), 523 | group=1 524 | ) 525 | app.add_handler( 526 | MessageHandler( 527 | add_premium_command, 528 | filters=filters.command("add", prefixes=COMMAND_PREFIX) & filters.private 529 | ), 530 | group=1 531 | ) 532 | app.add_handler( 533 | MessageHandler( 534 | remove_premium_command, 535 | filters=filters.command("rm", prefixes=COMMAND_PREFIX) & filters.private 536 | ), 537 | group=1 538 | ) 539 | app.add_handler( 540 | CallbackQueryHandler( 541 | handle_plan_callback, 542 | filters=filters.regex(r'^(buy_plan[1-3]|show_plan_options)$') 543 | ), 544 | group=2 545 | ) 546 | app.add_handler( 547 | RawUpdateHandler(raw_update_handler), 548 | group=3 549 | ) --------------------------------------------------------------------------------