├── 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 | [](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 | )
--------------------------------------------------------------------------------