├── handlers ├── AlphaBotz ├── user │ ├── credits │ ├── __init__.py │ ├── about.py │ ├── repo.py │ ├── help.py │ └── start.py ├── utils │ ├── source │ ├── __init__.py │ └── message_delete.py ├── shortner │ ├── __init__.py │ └── modiji.py ├── admin │ ├── __init__.py │ ├── auto_delete.py │ ├── message_delete.py │ ├── stats.py │ ├── manage_admin.py │ ├── broadcast.py │ ├── upload.py │ ├── batch.py │ └── qupload.py ├── __init__.py └── callback_handler.py ├── utils ├── alphabotz ├── __init__.py ├── admin_check.py ├── progress.py └── button_manager.py ├── .python-version ├── Procfile ├── assets ├── batch_mode.png ├── extraction_modes.png ├── quality_upload_banner.png └── quality_upload_feature.png ├── requirements.txt ├── config.example.py ├── web.py ├── __init__.py ├── helper_func.py ├── License ├── .env ├── main.py ├── app.json ├── .gitignore ├── config.py ├── Readme.md └── database.py /handlers/AlphaBotz: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /utils/alphabotz: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.11.6 2 | -------------------------------------------------------------------------------- /handlers/user/credits: -------------------------------------------------------------------------------- 1 | # created by @TheAlphaBotz 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python3 main.py 2 | web: python3 main.py 3 | 4 | -------------------------------------------------------------------------------- /handlers/utils/source: -------------------------------------------------------------------------------- 1 | # source code available at @alphabotzchat 2 | -------------------------------------------------------------------------------- /assets/batch_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utkarshdubey2008/Alphashare/HEAD/assets/batch_mode.png -------------------------------------------------------------------------------- /handlers/shortner/__init__.py: -------------------------------------------------------------------------------- 1 | from .modiji import short_url_command 2 | 3 | __all__ = ['short_url_command'] 4 | -------------------------------------------------------------------------------- /assets/extraction_modes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utkarshdubey2008/Alphashare/HEAD/assets/extraction_modes.png -------------------------------------------------------------------------------- /assets/quality_upload_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utkarshdubey2008/Alphashare/HEAD/assets/quality_upload_banner.png -------------------------------------------------------------------------------- /assets/quality_upload_feature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/utkarshdubey2008/Alphashare/HEAD/assets/quality_upload_feature.png -------------------------------------------------------------------------------- /handlers/admin/__init__.py: -------------------------------------------------------------------------------- 1 | from .message_delete import schedule_message_deletion 2 | 3 | __all__ = [ 4 | 'schedule_message_deletion' 5 | ] 6 | -------------------------------------------------------------------------------- /handlers/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .message_delete import schedule_message_deletion 2 | 3 | __all__ = [ 4 | 'schedule_message_deletion' 5 | ] 6 | -------------------------------------------------------------------------------- /handlers/user/__init__.py: -------------------------------------------------------------------------------- 1 | from .start import start_command 2 | from .help import help_command 3 | from .about import about_command 4 | 5 | __all__ = [ 6 | 'start_command', 7 | 'help_command', 8 | 'about_command' 9 | ] 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyrofork 2 | tgcrypto==1.2.5 3 | motor==3.3.1 4 | dnspython==2.4.2 5 | humanize==4.9.0 6 | python-dotenv==1.0.0 7 | aiofiles==23.2.1 8 | pytz==2023.3 9 | pymongo==4.5.0 10 | aiohttp 11 | requests==2.31.0 12 | rich==13.7.0 13 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .button_manager import ButtonManager 2 | from .progress import progress_callback, humanbytes, TimeFormatter 3 | from .admin_check import is_admin 4 | 5 | __all__ = [ 6 | 'ButtonManager', 7 | 'progress_callback', 8 | 'humanbytes', 9 | 'TimeFormatter', 10 | 'is_admin' 11 | ] 12 | -------------------------------------------------------------------------------- /handlers/user/about.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | from pyrogram.types import Message 3 | from utils import ButtonManager 4 | import config 5 | 6 | button_manager = ButtonManager() 7 | 8 | @Client.on_message(filters.command("about")) 9 | async def about_command(client: Client, message: Message): 10 | about_text = config.Messages.ABOUT_TEXT.format( 11 | bot_name=config.BOT_NAME, 12 | version=config.BOT_VERSION 13 | ) 14 | await message.reply_text(about_text, reply_markup=button_manager.about_button()) 15 | -------------------------------------------------------------------------------- /utils/admin_check.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | from pyrogram.types import Message, CallbackQuery 3 | import config 4 | 5 | def is_admin(update: Union[Message, CallbackQuery]) -> bool: 6 | """ 7 | Check if the user is an admin. 8 | Works with both Message and CallbackQuery objects. 9 | 10 | Args: 11 | update: The Message or CallbackQuery object to check 12 | 13 | Returns: 14 | bool: True if user is admin, False otherwise 15 | """ 16 | user_id = ( 17 | update.from_user.id if isinstance(update, Message) 18 | else update.message.from_user.id 19 | ) 20 | return user_id in config.ADMIN_IDS 21 | -------------------------------------------------------------------------------- /handlers/__init__.py: -------------------------------------------------------------------------------- 1 | from .admin.auto_delete import auto_delete_command 2 | from .admin.broadcast import broadcast_command 3 | from .admin.stats import stats_command 4 | from .admin.upload import upload_command 5 | from .shortner import short_url_command 6 | from .user.start import start_command 7 | from .user.help import help_command 8 | from .user.about import about_command 9 | from .admin.manage_admin import add_admin_cmd, remove_admin_cmd, list_admins_cmd 10 | 11 | __all__ = [ 12 | 'auto_delete_command', 13 | 'broadcast_command', 14 | 'stats_command', 15 | 'upload_command', 16 | 'short_url_command', 17 | 'start_command', 18 | 'help_command', 19 | 'about_command', 20 | 'add_admin_cmd', 21 | 'remove_admin_cmd', 22 | 'list_admins_cmd' 23 | ] 24 | -------------------------------------------------------------------------------- /config.example.py: -------------------------------------------------------------------------------- 1 | #this file is for koyeb 2 | import os 3 | from dotenv import load_dotenv 4 | 5 | load_dotenv() 6 | 7 | BOT_TOKEN = os.getenv("BOT_TOKEN") 8 | API_ID = int(os.getenv("API_ID", 0)) 9 | API_HASH = os.getenv("API_HASH") 10 | 11 | OWNER_ID = int(os.getenv("OWNER_ID")) 12 | 13 | MONGO_URI = os.getenv("MONGO_URI") 14 | DATABASE_NAME = os.getenv("DATABASE_NAME") 15 | 16 | DB_CHANNEL_ID = int(os.getenv("DB_CHANNEL_ID", 0)) 17 | FORCE_SUB_CHANNEL = int(os.getenv("FORCE_SUB_CHANNEL", 0)) 18 | 19 | BOT_USERNAME = os.getenv("BOT_USERNAME") 20 | BOT_NAME = os.getenv("BOT_NAME") 21 | 22 | CHANNEL_LINK = os.getenv("CHANNEL_LINK") 23 | DEVELOPER_LINK = os.getenv("DEVELOPER_LINK") 24 | SUPPORT_LINK = os.getenv("SUPPORT_LINK") 25 | 26 | ADMIN_IDS = [int(admin_id.strip()) for admin_id in os.getenv("ADMIN_IDS", "").split() if admin_id.strip().isdigit()] 27 | -------------------------------------------------------------------------------- /handlers/admin/auto_delete.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | from pyrogram.types import Message 3 | from handlers.admin.manage_admin import get_all_admin_ids 4 | 5 | @Client.on_message(filters.command("auto_del")) 6 | async def auto_delete_command(client: Client, message: Message): 7 | user_id = message.from_user.id 8 | 9 | admins = await get_all_admin_ids() 10 | 11 | if user_id not in admins: 12 | return await message.reply_text("__You are not authorized to use this command!__") 13 | 14 | await message.reply_text( 15 | "**⚙️ Auto Delete Time Configuration Moved**\n\n" 16 | "The auto-delete feature has been shifted to the `config.py` file.\n" 17 | "Please open your `config.py` and set the value for `AUTO_DELETE_TIME` there.\n\n" 18 | "**Example:**\n" 19 | "`AUTO_DELETE_TIME = 30 # in minutes`\n\n" 20 | "Restart the bot after updating the configuration to apply changes." 21 | ) 22 | -------------------------------------------------------------------------------- /handlers/user/repo.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 3 | 4 | @Client.on_message(filters.command("repo")) 5 | async def repo_command(client: Client, message: Message): 6 | text = ( 7 | "**🚀 AlphaShare - Open Source File Sharing Bot**\n\n" 8 | "This is the most popular Telegram file sharing bot with multiple features.\n" 9 | "You can deploy it completely **for free** and customize it as you like!" 10 | ) 11 | 12 | buttons = InlineKeyboardMarkup( 13 | [ 14 | [InlineKeyboardButton("📂 Source", url="https://github.com/utkarshdubey2008/alphashare")], 15 | [ 16 | InlineKeyboardButton("📢 Updates", url="https://t.me/thealphabotz"), 17 | InlineKeyboardButton("💬 Support", url="https://t.me/alphabotzchat") 18 | ] 19 | ] 20 | ) 21 | 22 | await message.reply_text(text, reply_markup=buttons) 23 | -------------------------------------------------------------------------------- /handlers/utils/message_delete.py: -------------------------------------------------------------------------------- 1 | # -- coding: utf-8 -- 2 | 3 | import asyncio 4 | import logging 5 | from pyrogram import Client, enums 6 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton 7 | import config 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | async def schedule_message_deletion(client: Client, chat_id: int, msg_ids: list, mins: int, link: str = None): 12 | await asyncio.sleep(mins * 60) 13 | try: 14 | await client.delete_messages(chat_id, msg_ids) 15 | 16 | kb = None 17 | if link: 18 | 19 | start_link = f"https://t.me/{config.BOT_NAME}?start={link}" 20 | kb = InlineKeyboardMarkup( 21 | [[InlineKeyboardButton("🔄 Get Again", url=start_link)]] 22 | ) 23 | 24 | await client.send_message( 25 | chat_id, 26 | "
🗑️ This file was auto-deleted after the set time to save space.\n" 27 | "Use the button below to get it again.
", 28 | reply_markup=kb, 29 | parse_mode=enums.ParseMode.HTML 30 | ) 31 | except Exception as e: 32 | logger.error(f"Error in deletion: {e}") 33 | -------------------------------------------------------------------------------- /handlers/user/help.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | from pyrogram.types import Message 3 | from utils import ButtonManager 4 | 5 | button_manager = ButtonManager() 6 | 7 | @Client.on_message(filters.command("help")) 8 | async def help_command(client: Client, message: Message): 9 | help_text = ( 10 | "**📚 Bot Commands & Usage**\n\n" 11 | "Here are the available commands:\n\n" 12 | "👥 **User Commands:**\n" 13 | "• /start - Start the bot\n" 14 | "• /help - Show this help message\n" 15 | "• /about - About the bot\n\n" 16 | "👮‍♂️ **Admin Commands:**\n" 17 | "• /upload - Upload a file (reply to file)\n" 18 | "• /auto_del - Set auto-delete time\n" 19 | "• /stats - View bot statistics\n" 20 | "• /bcast - Broadcast message to users\n" 21 | "• /bcast_time - Broadcast time on or off to send broadcast in time.\n\n" 22 | "💡 **Auto-Delete Feature:**\n" 23 | "Files are automatically deleted after the set time.\n" 24 | "Use /auto_del to change the deletion time. • /short - to shorten any url in modiji,usage :- /short example.com" 25 | ) 26 | await message.reply_text(help_text, reply_markup=button_manager.help_button()) 27 | -------------------------------------------------------------------------------- /web.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from aiohttp import web, ClientSession, ClientTimeout 3 | 4 | async def start_webserver(): 5 | routes = web.RouteTableDef() 6 | 7 | @routes.get("/", allow_head=True) 8 | async def root_route_handler(request): 9 | res = { 10 | "status": "running", 11 | } 12 | return web.json_response(res) 13 | 14 | async def web_server(): 15 | web_app = web.Application(client_max_size=30000000) 16 | web_app.add_routes(routes) 17 | return web_app 18 | 19 | app = web.AppRunner(await web_server()) 20 | await app.setup() 21 | await web.TCPSite(app, "0.0.0.0", 8080).start() 22 | print("Web server started") 23 | 24 | async def ping_server(url, sleep_time): 25 | while True: 26 | await asyncio.sleep(sleep_time) 27 | try: 28 | async with ClientSession( 29 | timeout= ClientTimeout(total=10) 30 | ) as session: 31 | async with session.get(url) as resp: 32 | print("Pinged server with response: {}".format(resp.status)) 33 | except TimeoutError: 34 | print(f"Couldn't connect to the site {url}..!") 35 | except Exception as e: 36 | print(e) 37 | -------------------------------------------------------------------------------- /handlers/admin/message_delete.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client 2 | from database import Database 3 | import asyncio 4 | 5 | db = Database() 6 | 7 | async def schedule_message_deletion(client: Client, file_uuid: str, chat_id: int, message_ids: list, delete_time: int): 8 | await asyncio.sleep(delete_time * 60) 9 | try: 10 | await client.delete_messages(chat_id, message_ids) 11 | await client.send_message( 12 | chat_id=chat_id, 13 | text=( 14 | "⚠️ **File Removed Due to Copyright Protection**\n\n" 15 | "The file you received has been automatically deleted as part of our copyright protection policy.\n\n" 16 | "🔹 If you need the file again, you can request it using the original link (if still available).\n" 17 | "🔹 To avoid losing important files, save them to your personal storage in advance.\n" 18 | "🔹 We enforce these measures to ensure a safe and legal file-sharing environment for everyone.\n\n" 19 | "Thank you for your understanding!" 20 | ) 21 | ) 22 | for msg_id in message_ids: 23 | await db.remove_file_message(file_uuid, chat_id, msg_id) 24 | except Exception as e: 25 | print(f"Error in auto-delete: {str(e)}") 26 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | from .admin.auto_delete import auto_delete_command 2 | from .admin.broadcast import broadcast_command 3 | from .admin.stats import stats_command 4 | from .admin.upload import upload_command 5 | from .shortner import short_url_command 6 | from .user.start import start_command 7 | from .user.help import help_command 8 | from .user.about import about_command 9 | from .admin.manage_admin import add_admin_cmd, remove_admin_cmd, list_admins_cmd 10 | from .admin.batch import ( 11 | batch_command, 12 | handle_batch_file, 13 | done_command, 14 | handle_batch_start, 15 | cancel_command 16 | ) 17 | from .admin.qupload import ( 18 | qupload_command, 19 | qupload_done_command, 20 | qcancel_command, 21 | qmode_command, 22 | qmode_callback 23 | ) 24 | 25 | __all__ = [ 26 | 'auto_delete_command', 27 | 'broadcast_command', 28 | 'stats_command', 29 | 'upload_command', 30 | 'short_url_command', 31 | 'start_command', 32 | 'help_command', 33 | 'about_command', 34 | 'add_admin_cmd', 35 | 'remove_admin_cmd', 36 | 'list_admins_cmd', 37 | 'batch_command', 38 | 'handle_batch_file', 39 | 'done_command', 40 | 'handle_batch_start', 41 | 'cancel_command', 42 | 'qupload_command', 43 | 'qupload_done_command', 44 | 'qcancel_command', 45 | 'qmode_command', 46 | 'qmode_callback' 47 | ] 48 | -------------------------------------------------------------------------------- /helper_func.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 @thealphabotz - All Rights Reserved. 2 | #this file is to sync codex botz files 3 | 4 | import base64 5 | import re 6 | from typing import Union 7 | 8 | async def encode(string: str) -> str: 9 | string_bytes = string.encode("ascii") 10 | base64_bytes = base64.b64encode(string_bytes) 11 | return base64_bytes.decode("ascii") 12 | 13 | async def decode(base64_string: str) -> str: 14 | try: 15 | base64_bytes = base64_string.encode("ascii") 16 | string_bytes = base64.b64decode(base64_bytes) 17 | return string_bytes.decode("ascii") 18 | except: 19 | return "" 20 | 21 | async def get_message_id(client, message: Union[str, int]) -> Union[int, bool]: 22 | if message.forward_from_chat: 23 | if message.forward_from_chat.id == client.db_channel.id: 24 | return message.forward_from_message_id 25 | return False 26 | if message.text: 27 | pattern = "https://t.me/(?:c/)?([^/]+)/([0-9]+)" 28 | matches = re.match(pattern, message.text.strip()) 29 | if not matches: 30 | return False 31 | channel_id = matches.group(1) 32 | msg_id = int(matches.group(2)) 33 | if channel_id.isdigit(): 34 | if f"-100{channel_id}" == str(client.db_channel.id): 35 | return msg_id 36 | else: 37 | if channel_id == client.db_channel.username: 38 | return msg_id 39 | return False 40 | 41 | # @thealphabotz | Join @thealphabotz on Telegram 42 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 utkarshdubey2008 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. 22 | 23 | ADDITIONAL CONDITIONS: 24 | 25 | 1. The Software is free to use for personal and commercial purposes. 26 | 2. Selling the Software, in whole or in part, is strictly prohibited. 27 | 3. Attribution must be given to the original author (utkarshdubey2008) in any distribution or derivative works. 28 | 29 | For any inquiries or support, contact the developer: 30 | - GitHub: @utkarshdubey2008 31 | telegram - @thealphabotz , @alphabotzchat 32 | -------------------------------------------------------------------------------- /handlers/admin/stats.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | from pyrogram.types import Message 3 | from database import Database 4 | from utils import humanbytes 5 | import config 6 | import logging 7 | from handlers.admin.manage_admin import get_all_admin_ids 8 | 9 | logger = logging.getLogger(__name__) 10 | db = Database() 11 | 12 | @Client.on_message(filters.command("stats")) 13 | async def stats_command(client: Client, message: Message): 14 | from_user_id = message.from_user.id 15 | 16 | admins = await get_all_admin_ids() 17 | 18 | if from_user_id not in admins: 19 | return await message.reply_text("__You are not authorized to use this command!__") 20 | 21 | try: 22 | stats = await db.get_stats() 23 | 24 | stats_text = "**📊 Bot Statistics**\n\n" 25 | stats_text += f"📁 **Files:** `{stats.get('total_files', 0)}`\n" 26 | stats_text += f"👥 **Users:** `{stats.get('total_users', 0)}`\n" 27 | stats_text += f"📥 **Downloads:** `{stats.get('total_downloads', 0)}`\n" 28 | 29 | if stats.get('total_size'): 30 | stats_text += f"💾 **Size:** `{humanbytes(stats['total_size'])}`\n" 31 | 32 | if 'active_autodelete_files' in stats: 33 | stats_text += f"🕒 **Auto-Delete Files:** `{stats['active_autodelete_files']}`\n" 34 | 35 | if getattr(config, 'DEFAULT_AUTO_DELETE', None): 36 | stats_text += f"\n⏱ **Auto-Delete Time:** `{config.DEFAULT_AUTO_DELETE}` minutes" 37 | 38 | await message.reply_text(stats_text) 39 | 40 | except Exception as e: 41 | logger.error(f"Stats command error: {e}") 42 | await message.reply_text("❌ Error fetching statistics. Please try again later.") 43 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Bot Configuration 2 | BOT_TOKEN=7941536726:Axxxxxxxxxxxxxxxx 3 | API_ID=25xxxxxxx 4 | API_HASH=e032d6e5c05a5dxxxxxxxx 5 | 6 | OWNER_ID=7355202884 7 | 8 | # Database Configuration 9 | MONGO_URI=mongodb+srv://AlphaShare:xxxxjdaShare@alphashare.il6rjzn4.mongodb.net/?retryWrites=true&w=majority&appName=AlphaShare 10 | DATABASE_NAME=file_sharebot 11 | 12 | # Channel Configuration 13 | DB_CHANNEL_ID=-10028832577 14 | FORCE_SUB_CHANNEL=-102198832577 # First force subscribe channel 15 | FORCE_SUB_CHANNEL_2=0 # Second force subscribe channel (defaults to 0) 16 | FORCE_SUB_CHANNEL_3=0 # Third force subscribe channel (defaults to 0) 17 | FORCE_SUB_CHANNEL_4=0 # Fourth force subscribe channel (defaults to 0) 18 | 19 | # Links 20 | CHANNEL_LINK=https://t.me/+KeEYOBeS1 # Primary channel link 21 | CHANNEL_LINK_2= # Secondary channel link (optional) 22 | CHANNEL_LINK_3= # Third channel link (optional) 23 | CHANNEL_LINK_4= # Fourth channel link (optional) 24 | DEVELOPER_LINK=https://t.me/utkarsh 25 | SUPPORT_LINK=https://t.me/animejunctions 26 | 27 | # Bot Information 28 | BOT_USERNAME=alphamusicplaybot 29 | BOT_NAME=Alpha File Share Bot 30 | 31 | # Modiji API Key 32 | MODIJI_API_KEY=828727 # Replace this if you use any API requiring a key 33 | 34 | # Web Server Settings 35 | WEB_SERVER=False # Set True if deploying on Koyeb/Render 36 | PING_URL=https://your-ping-url.com # Replace with your actual uptime URL 37 | PING_TIME=300 # Ping interval in seconds 38 | 39 | # Admin IDs 40 | ADMIN_IDS=7758708579 2009509228 # Add more admin IDs with space separation 41 | 42 | # Privacy Mode Configuration 43 | PRIVACY_MODE=off # Set "on" for enabling privacy mode 44 | 45 | # Auto Delete Configuration (in minutes) 46 | AUTO_DELETE_TIME=30 47 | 48 | # Start Photo 49 | START_PHOTO= # Provide URL for the bot's start photo 50 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #AlphaShare bot join @Thealphabotz 2 | from pyrogram import Client, idle 3 | from web import start_webserver, ping_server 4 | from database import Database 5 | import config 6 | import asyncio 7 | import os 8 | import time 9 | 10 | 11 | class FileShareBot(Client): 12 | def __init__(self): 13 | super().__init__( 14 | name="FileShareBot", 15 | api_id=config.API_ID, 16 | api_hash=config.API_HASH, 17 | bot_token=config.BOT_TOKEN, 18 | plugins=dict(root="handlers") 19 | ) 20 | self.db = Database() 21 | print("Bot Initialized!") 22 | 23 | async def start(self): 24 | await super().start() 25 | me = await self.get_me() 26 | print(f"Bot Started as {me.first_name}") 27 | print(f"Username: @{me.username}") 28 | print("----------------") 29 | 30 | 31 | 32 | async def stop(self): 33 | await super().stop() 34 | print("Bot Stopped. Bye!") 35 | 36 | async def main(): 37 | bot = FileShareBot() 38 | 39 | try: 40 | print("Starting Bot...") 41 | await bot.start() 42 | print("Bot is Running!") 43 | if config.WEB_SERVER: 44 | asyncio.create_task(start_webserver()) 45 | asyncio.create_task(ping_server(config.PING_URL, config.PING_TIME)) 46 | 47 | await idle() 48 | except Exception as e: 49 | print(f"ERROR: {str(e)}") 50 | finally: 51 | await bot.stop() 52 | print("Bot Stopped!") 53 | 54 | 55 | if __name__ == "__main__": 56 | try: 57 | 58 | if os.name == 'nt': 59 | asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) 60 | loop = asyncio.get_event_loop() 61 | loop.run_until_complete(main()) 62 | except KeyboardInterrupt: 63 | print("Bot Stopped by User!") 64 | except Exception as e: 65 | print(f"CRITICAL ERROR: {str(e)}") 66 | -------------------------------------------------------------------------------- /utils/progress.py: -------------------------------------------------------------------------------- 1 | import math 2 | import time 3 | from typing import Union 4 | from pyrogram.types import Message 5 | 6 | async def progress_callback( 7 | current: int, 8 | total: int, 9 | message: Message, 10 | start_time: float, 11 | status: str = "Uploading", 12 | file_name: str = "" 13 | ) -> None: 14 | now = time.time() 15 | diff = now - start_time 16 | if diff < 1: 17 | return 18 | 19 | speed = current / diff 20 | percentage = current * 100 / total 21 | time_to_complete = round((total - current) / speed) if speed != 0 else 0 22 | time_to_complete = TimeFormatter(time_to_complete) 23 | 24 | progress = "[{0}{1}] \n".format( 25 | ''.join(["●" for i in range(math.floor(percentage / 5))]), 26 | ''.join(["○" for i in range(20 - math.floor(percentage / 5))]) 27 | ) 28 | 29 | current_message = ( 30 | f"{status}\n" 31 | f"{progress}\n" 32 | f"File Name: {file_name}\n" 33 | f"Progress: {current * 100 / total:.1f}%\n" 34 | f"Speed: {humanbytes(speed)}/s\n" 35 | f"ETA: {time_to_complete}\n" 36 | ) 37 | 38 | try: 39 | await message.edit_text(current_message) 40 | except: 41 | pass 42 | 43 | def humanbytes(size: Union[int, float]) -> str: 44 | if not size: 45 | return "0B" 46 | power = 2**10 47 | n = 0 48 | power_labels = {0: '', 1: 'K', 2: 'M', 3: 'G', 4: 'T'} 49 | while size >= power and n < 4: 50 | size /= power 51 | n += 1 52 | return f"{size:.2f} {power_labels[n]}B" 53 | 54 | def TimeFormatter(seconds: int) -> str: 55 | minutes, seconds = divmod(seconds, 60) 56 | hours, minutes = divmod(minutes, 60) 57 | days, hours = divmod(hours, 24) 58 | 59 | tmp = ( 60 | (f"{days}d, " if days else "") + 61 | (f"{hours}h, " if hours else "") + 62 | (f"{minutes}m, " if minutes else "") + 63 | (f"{seconds}s" if seconds else "") 64 | ) 65 | return tmp 66 | -------------------------------------------------------------------------------- /handlers/shortner/modiji.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | import requests 3 | import time 4 | from rich.console import Console 5 | from rich.panel import Panel 6 | import config # Added import for config 7 | 8 | console = Console() 9 | 10 | # Check if MODIJI_API_KEY exists in config 11 | if not hasattr(config, 'MODIJI_API_KEY'): 12 | raise Exception("Please add MODIJI_API_KEY to your config.py") 13 | 14 | # API endpoint 15 | MODIJI_API_URL = "https://api.modijiurl.com/api" 16 | 17 | @Client.on_message(filters.command("short") & filters.user(config.ADMIN_IDS)) 18 | async def short_url_command(client, message): 19 | """ 20 | Command: /short {url} 21 | Description: Shortens a URL using ModijiURL API 22 | """ 23 | try: 24 | # Extract URL from command 25 | command = message.text.split() 26 | if len(command) != 2: 27 | await message.reply_text( 28 | "❌ **Invalid command format!**\n\n" 29 | "**Usage:** `/short url`\n" 30 | "**Example:** `/short https://example.com`", 31 | quote=True 32 | ) 33 | return 34 | 35 | url = command[1] 36 | 37 | 38 | status_msg = await message.reply_text( 39 | "🔄 **Processing your URL...**", 40 | quote=True 41 | ) 42 | 43 | # API request parameters 44 | params = { 45 | 'api': config.MODIJI_API_KEY, 46 | 'url': url, 47 | 'format': 'json' 48 | } 49 | 50 | 51 | time.sleep(2) 52 | 53 | 54 | response = requests.get(MODIJI_API_URL, params=params) 55 | response.raise_for_status() 56 | 57 | data = response.json() 58 | 59 | if data.get('status') == 'success': 60 | shortened_url = data.get('shortenedUrl') 61 | 62 | await status_msg.edit_text( 63 | f"✅ **URL Shortened Successfully!**\n\n" 64 | f"**Original URL:**\n`{url}`\n\n" 65 | f"**Shortened URL:**\n`{shortened_url}`\n\n" 66 | f"Powered by @Thealphabotz" 67 | ) 68 | else: 69 | await status_msg.edit_text( 70 | "❌ **Failed to shorten URL!**\n\n" 71 | "Please check your URL and try again." 72 | ) 73 | 74 | except IndexError: 75 | await message.reply_text( 76 | "❌ **Please provide a URL to shorten!**\n\n" 77 | "**Usage:** `/short url`", 78 | quote=True 79 | ) 80 | except requests.RequestException as e: 81 | await status_msg.edit_text( 82 | f"❌ **API Error:**\n`{str(e)}`\n\n" 83 | "Please try again later." 84 | ) 85 | except Exception as e: 86 | await status_msg.edit_text( 87 | f"❌ **An unexpected error occurred:**\n`{str(e)}`" 88 | ) 89 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AlphaShare", 3 | "description": "A secure file sharing bot using Telegram.", 4 | "repository": "https://github.com/utkarshdubey2008/Alphashare", 5 | "env": { 6 | "BOT_TOKEN": { 7 | "description": "The Telegram bot token from @BotFather", 8 | "required": true 9 | }, 10 | "API_ID": { 11 | "description": "Your Telegram API ID from my.telegram.org", 12 | "required": true 13 | }, 14 | "API_HASH": { 15 | "description": "Your Telegram API Hash from my.telegram.org", 16 | "required": true 17 | }, 18 | "MONGO_URI": { 19 | "description": "MongoDB connection string", 20 | "required": true 21 | }, 22 | "DATABASE_NAME": { 23 | "description": "The name of the database for storing bot data", 24 | "value": "file_share_bot", 25 | "required": true 26 | }, 27 | "DB_CHANNEL_ID": { 28 | "description": "Channel ID where files will be stored", 29 | "required": true 30 | }, 31 | "FORCE_SUB_CHANNEL": { 32 | "description": "First mandatory subscription channel", 33 | "required": true 34 | }, 35 | "FORCE_SUB_CHANNEL_2": { 36 | "description": "Second mandatory subscription channel (set 0 if not used)", 37 | "value": "0", 38 | "required": false 39 | }, 40 | "CHANNEL_LINK": { 41 | "description": "Link to the first mandatory subscription channel", 42 | "required": true 43 | }, 44 | "CHANNEL_LINK_2": { 45 | "description": "Link to the second mandatory subscription channel (leave empty if not used)", 46 | "value": "", 47 | "required": false 48 | }, 49 | "DEVELOPER_LINK": { 50 | "description": "Link to the developer's Telegram profile or bot support", 51 | "required": true 52 | }, 53 | "SUPPORT_LINK": { 54 | "description": "Link to the support group or chat for the bot", 55 | "required": true 56 | }, 57 | "BOT_USERNAME": { 58 | "description": "The username of your bot (without @)", 59 | "required": true 60 | }, 61 | "BOT_NAME": { 62 | "description": "The display name of your bot", 63 | "value": "Alpha File Share Bot", 64 | "required": true 65 | }, 66 | "MODIJI_API_KEY": { 67 | "description": "API key for external services (if applicable)", 68 | "required": false 69 | }, 70 | "WEB_SERVER": { 71 | "description": "Set to True for hosting on platforms like Render/Koyeb; False if using local hosting", 72 | "value": "True", 73 | "required": true 74 | }, 75 | "PING_URL": { 76 | "description": "The health check or ping URL for keeping the bot active", 77 | "required": false 78 | }, 79 | "PING_TIME": { 80 | "description": "Interval (in seconds) to ping the bot to prevent sleeping", 81 | "value": "300", 82 | "required": false 83 | }, 84 | "ADMIN_IDS": { 85 | "description": "List of admin Telegram user IDs who have control over the bot", 86 | "required": true 87 | }, 88 | "PRIVACY_MODE": { 89 | "description": "Set to 'on' for restricting users to copy or forward the content", 90 | "value": "off", 91 | "required": false 92 | }, 93 | "AUTO_DELETE_TIME": { 94 | "description": "Time (in minutes) after which a file will be automatically deleted after being accessed", 95 | "value": "30", 96 | "required": true 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /handlers/admin/manage_admin.py: -------------------------------------------------------------------------------- 1 | from database import Database 2 | from pyrogram import Client, filters 3 | from pyrogram.types import Message 4 | from config import OWNER_ID 5 | 6 | import logging 7 | 8 | db = Database() 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | 14 | async def add_admin_to_db(user_id: int) -> bool: 15 | try: 16 | existing = await db.admin_collection.find_one({"user_id": user_id}) 17 | if existing: 18 | return False 19 | 20 | await db.admin_collection.insert_one({"user_id": user_id}) 21 | return True 22 | 23 | except Exception as e: 24 | logger.error(f"Error adding admin: {str(e)}") 25 | return False 26 | 27 | 28 | async def remove_admin_from_db(user_id: int) -> bool: 29 | try: 30 | result = await db.admin_collection.delete_one({"user_id": user_id}) 31 | return result.deleted_count > 0 32 | except Exception as e: 33 | logger.error(f"Error removing admin: {str(e)}") 34 | return False 35 | 36 | async def get_all_admin_ids() -> list: 37 | try: 38 | cursor = db.admin_collection.find({}) 39 | admins = await cursor.to_list(length=None) 40 | admin_ids = [admin['user_id'] for admin in admins] 41 | 42 | admin_ids.append(OWNER_ID) 43 | return list(set(admin_ids)) 44 | 45 | except Exception as e: 46 | logger.error(f"Error fetching admin list: {str(e)}") 47 | return [OWNER_ID] 48 | 49 | 50 | 51 | 52 | @Client.on_message(filters.command("addadmin") & filters.private) 53 | async def add_admin_cmd(client: Client, message: Message): 54 | from_user_id = message.from_user.id 55 | 56 | if from_user_id != OWNER_ID: 57 | await message.reply_text("__Only the owner can add new admins!__") 58 | return 59 | 60 | if len(message.command) != 2: 61 | return await message.reply_text("**Usage:** `/addadmin user_id`") 62 | 63 | try: 64 | parts = message.text.split() 65 | if len(parts) != 2 or not parts[1].isdigit(): 66 | await message.reply_text("**Usage:** `/addadmin user_id`") 67 | return 68 | 69 | user_id = int(parts[1]) 70 | success = await add_admin_to_db(user_id) 71 | 72 | if success: 73 | await message.reply_text(f"✅ User `{user_id}` has been added as an admin.") 74 | else: 75 | await message.reply_text(f"⚠️ User `{user_id}` is already an admin.") 76 | 77 | except Exception as e: 78 | await message.reply_text(f"🔥 Error: `{str(e)}`") 79 | 80 | 81 | 82 | @Client.on_message(filters.command("rmadmin") & filters.private) 83 | async def remove_admin_cmd(client, message: Message): 84 | try: 85 | user_id = message.from_user.id 86 | if user_id != OWNER_ID: 87 | return await message.reply_text("__Only my Owner can remove admins.__") 88 | 89 | if len(message.command) != 2: 90 | return await message.reply_text("**Usage:** `/rmadmin user_id`") 91 | 92 | target_id = int(message.command[1]) 93 | 94 | removed = await remove_admin_from_db(target_id) 95 | if removed: 96 | await message.reply_text(f"✅ User `{target_id}` has been removed from admin list.") 97 | else: 98 | await message.reply_text(f"⚠️ User `{target_id}` is not in the admin list.") 99 | 100 | except Exception as e: 101 | await message.reply_text(f"❌ Error: `{str(e)}`") 102 | 103 | 104 | 105 | 106 | @Client.on_message(filters.command("adminlist") & filters.private) 107 | async def list_admins_cmd(client, message: Message): 108 | try: 109 | user_id = message.from_user.id 110 | if user_id != OWNER_ID: 111 | return await message.reply_text("__Only Owner can view the admin list!__") 112 | 113 | admin_ids = await get_all_admin_ids() 114 | 115 | if not admin_ids: 116 | await message.reply_text("__No admins found in the list.__") 117 | return 118 | 119 | text = "**🔐 List of Admins:**\n" 120 | text += "\n".join([f"- `{uid}`" for uid in admin_ids]) 121 | await message.reply_text(text) 122 | 123 | except Exception as e: 124 | await message.reply_text(f"❌ Error: `{str(e)}`") -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | #uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 116 | .pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | # Ruff stuff: 171 | .ruff_cache/ 172 | 173 | # PyPI configuration file 174 | .pypirc 175 | -------------------------------------------------------------------------------- /handlers/admin/broadcast.py: -------------------------------------------------------------------------------- 1 | # © @TheAlphaBotz [2021-2025] 2 | import asyncio, re 3 | from pyrogram import Client, filters 4 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message 5 | from pyrogram.errors import FloodWait, UserIsBlocked, ChatWriteForbidden 6 | from database import Database 7 | from handlers.admin.manage_admin import get_all_admin_ids 8 | 9 | db = Database() 10 | 11 | broadcast_settings = {"bcast_time": False} 12 | 13 | async def load_broadcast_settings(): 14 | broadcast_settings["bcast_time"] = await db.get_setting("bcast_time", False) 15 | 16 | async def save_broadcast_setting(key, value): 17 | broadcast_settings[key] = value 18 | await db.set_setting(key, value) 19 | 20 | @Client.on_message(filters.command("bcast_time") & filters.private) 21 | async def bcast_time(client, message): 22 | from_user_id = message.from_user.id 23 | 24 | admins = await get_all_admin_ids() 25 | 26 | if from_user_id not in admins: 27 | return await message.reply_text("__You are not authorized to use this command!__") 28 | 29 | cmd = message.text.strip().split(maxsplit=1) 30 | if len(cmd) != 2 or cmd[1].lower() not in ["on", "off"]: 31 | return await message.reply("Usage: `/bcast_time on` or `/bcast_time off`") 32 | status = cmd[1].lower() == "on" 33 | await save_broadcast_setting("bcast_time", status) 34 | return await message.reply(f"✅ Timed broadcast is now **{'enabled' if status else 'disabled'}**") 35 | 36 | def chunk_buttons(buttons, row_size=4): 37 | return [buttons[i:i + row_size] for i in range(0, len(buttons), row_size)] 38 | 39 | def parse_buttons(text): 40 | if not text: 41 | return "", None 42 | pattern = r'\{([^\}]+)\}' 43 | matches = re.findall(pattern, text) 44 | buttons = [InlineKeyboardButton(text=btn.strip(), url=url.strip()) for btn, url in matches] 45 | cleaned = re.sub(pattern, '', text).strip() 46 | markup = InlineKeyboardMarkup(chunk_buttons(buttons)) if buttons else None 47 | return cleaned, markup 48 | 49 | async def send_broadcast_message(client, user_id, message_to_forward=None, text=None, buttons=None): 50 | """Helper function to send broadcast message with error handling""" 51 | try: 52 | if message_to_forward: 53 | msg = await message_to_forward.forward( 54 | chat_id=user_id, 55 | disable_notification=True 56 | ) 57 | else: 58 | msg = await client.send_message( 59 | chat_id=user_id, 60 | text=text, 61 | reply_markup=buttons, 62 | disable_notification=True 63 | ) 64 | return True 65 | except FloodWait as e: 66 | await asyncio.sleep(e.value) 67 | return await send_broadcast_message(client, user_id, message_to_forward, text, buttons) 68 | except (UserIsBlocked, ChatWriteForbidden): 69 | return False 70 | except Exception as e: 71 | print(f"Error broadcasting to {user_id}: {str(e)}") 72 | return False 73 | 74 | @Client.on_message(filters.command("bcast") & filters.private) 75 | async def broadcast_command(client, message: Message): 76 | from_user_id = message.from_user.id 77 | admins = await get_all_admin_ids() 78 | 79 | if from_user_id not in admins: 80 | return await message.reply_text("__You are not authorized to use this command!__") 81 | 82 | 83 | delay_hours = 0 84 | should_pin = False 85 | message_to_forward = None 86 | text = None 87 | buttons = None 88 | 89 | 90 | if message.reply_to_message: 91 | message_to_forward = message.reply_to_message 92 | else: 93 | if len(message.command) < 2: 94 | return await message.reply( 95 | "**Usage:**\n" 96 | "1. Reply to any message with `/bcast` to forward it\n" 97 | "2. `/bcast Hello [interval] [pin]` to send text message\n" 98 | "**Example:** `/bcast Hello 4h pin` = send every 4 hours and pin\n" 99 | "[Generate buttons](https://alphasharebtngen.netlify.app)" 100 | ) 101 | content = message.text.split(None, 1)[1] 102 | words = content.strip().split() 103 | 104 | 105 | if "pin" in words: 106 | should_pin = True 107 | words.remove("pin") 108 | match = re.search(r"(\d+)([hm])$", words[-1]) if words else None 109 | if match and broadcast_settings["bcast_time"]: 110 | value, unit = int(match.group(1)), match.group(2) 111 | delay_hours = value if unit == "h" else value / 60 112 | words = words[:-1] 113 | content = " ".join(words) 114 | text, buttons = parse_buttons(content) 115 | 116 | async def broadcast(): 117 | users = await db.get_all_users() 118 | total_users = len(users) 119 | sent, failed = 0, 0 120 | start_time = asyncio.get_event_loop().time() 121 | 122 | status_msg = await message.reply( 123 | "📣 **Broadcast Started**\n\n" 124 | f"Total Users: {total_users}\n" 125 | "Progress: 0%" 126 | ) 127 | 128 | update_interval = max(1, total_users // 20) 129 | last_update_time = start_time 130 | 131 | for i, user in enumerate(users, 1): 132 | success = await send_broadcast_message( 133 | client, 134 | user["user_id"], 135 | message_to_forward, 136 | text, 137 | buttons 138 | ) 139 | 140 | if success: 141 | sent += 1 142 | if should_pin and message_to_forward: 143 | try: 144 | await client.pin_chat_message( 145 | user["user_id"], 146 | message_to_forward.id, 147 | disable_notification=True 148 | ) 149 | except: 150 | pass 151 | else: 152 | failed += 1 153 | 154 | 155 | current_time = asyncio.get_event_loop().time() 156 | if i % update_interval == 0 or i == total_users: 157 | if current_time - last_update_time >= 2: 158 | progress = (i / total_users) * 100 159 | elapsed_time = current_time - start_time 160 | speed = i / elapsed_time if elapsed_time > 0 else 0 161 | 162 | await status_msg.edit( 163 | "📣 **Broadcasting...**\n\n" 164 | f"**Total Users:** {total_users}\n" 165 | f"**Progress:** {progress:.1f}%\n" 166 | f"**Success:** {sent}\n" 167 | f"**Failed:** {failed}\n" 168 | f"**Speed:** {speed:.1f} messages/sec" 169 | ) 170 | last_update_time = current_time 171 | 172 | 173 | if i % 100 == 0: 174 | await asyncio.sleep(0.5) 175 | 176 | time_taken = asyncio.get_event_loop().time() - start_time 177 | await status_msg.edit( 178 | "✅ **Broadcast Completed!**\n\n" 179 | f"**Total Users:** {total_users}\n" 180 | f"**Successfully Sent:** {sent}\n" 181 | f"**Failed:** {failed}\n" 182 | f"**Time Taken:** {time_taken:.1f} seconds\n" 183 | f"**Average Speed:** {sent/time_taken:.1f} messages/sec" 184 | ) 185 | 186 | if delay_hours > 0: 187 | while True: 188 | await broadcast() 189 | await asyncio.sleep(delay_hours * 3600) 190 | else: 191 | await broadcast() 192 | -------------------------------------------------------------------------------- /handlers/admin/upload.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # © @TheAlphaBotz [2021-2025] 3 | 4 | from pyrogram import Client, filters 5 | from pyrogram.types import Message 6 | from database import Database 7 | from utils import ButtonManager, humanbytes 8 | import config 9 | import uuid 10 | from handlers.admin.manage_admin import get_all_admin_ids 11 | 12 | db = Database() 13 | button_manager = ButtonManager() 14 | 15 | # Emoji constants using UTF-8 unicode escape sequences 16 | class Emoji: 17 | ERROR = "\u274C" # ❌ 18 | SUCCESS = "\u2705" # ✅ 19 | LOADING = "\U0001F504" # 🔄 20 | HOURGLASS = "\u231B" # ⏳ 21 | INBOX = "\U0001F4E5" # 📥 22 | ARROW = "\u279C" # ➜ 23 | BULLET = "\u2022" # • 24 | 25 | async def process_single_file(client: Client, file_message: Message, status_msg: Message = None): 26 | try: 27 | forwarded_msg = await file_message.copy(config.DB_CHANNEL_ID) 28 | 29 | file_data = { 30 | "file_id": None, 31 | "file_name": "Unknown", 32 | "file_size": 0, 33 | "file_type": None, 34 | "uuid": str(uuid.uuid4()), 35 | "uploader_id": file_message.from_user.id, 36 | "message_id": forwarded_msg.id, 37 | "auto_delete": True, 38 | "auto_delete_time": getattr(config, 'DEFAULT_AUTO_DELETE', 30) 39 | } 40 | 41 | if file_message.document: 42 | file_data.update({ 43 | "file_id": file_message.document.file_id, 44 | "file_name": file_message.document.file_name or "document", 45 | "file_size": file_message.document.file_size, 46 | "file_type": "document" 47 | }) 48 | elif file_message.video: 49 | file_data.update({ 50 | "file_id": file_message.video.file_id, 51 | "file_name": file_message.video.file_name or "video.mp4", 52 | "file_size": file_message.video.file_size, 53 | "file_type": "video" 54 | }) 55 | elif file_message.audio: 56 | file_data.update({ 57 | "file_id": file_message.audio.file_id, 58 | "file_name": file_message.audio.file_name or "audio", 59 | "file_size": file_message.audio.file_size, 60 | "file_type": "audio" 61 | }) 62 | elif file_message.photo: 63 | file_data.update({ 64 | "file_id": file_message.photo.file_id, 65 | "file_name": f"photo_{file_data['uuid']}.jpg", 66 | "file_size": file_message.photo.file_size, 67 | "file_type": "photo" 68 | }) 69 | elif file_message.voice: 70 | file_data.update({ 71 | "file_id": file_message.voice.file_id, 72 | "file_name": f"voice_{file_data['uuid']}.ogg", 73 | "file_size": file_message.voice.file_size, 74 | "file_type": "voice" 75 | }) 76 | elif file_message.video_note: 77 | file_data.update({ 78 | "file_id": file_message.video_note.file_id, 79 | "file_name": f"video_note_{file_data['uuid']}.mp4", 80 | "file_size": file_message.video_note.file_size, 81 | "file_type": "video_note" 82 | }) 83 | elif file_message.animation: 84 | file_data.update({ 85 | "file_id": file_message.animation.file_id, 86 | "file_name": file_message.animation.file_name or f"animation_{file_data['uuid']}.gif", 87 | "file_size": file_message.animation.file_size, 88 | "file_type": "animation" 89 | }) 90 | else: 91 | if status_msg: 92 | await status_msg.edit_text(f"{Emoji.ERROR} **Unsupported file type!**") 93 | return None 94 | 95 | if not file_data["file_id"]: 96 | if status_msg: 97 | await status_msg.edit_text(f"{Emoji.ERROR} **Could not process file!**") 98 | return None 99 | 100 | if file_data["file_size"] and file_data["file_size"] > config.MAX_FILE_SIZE: 101 | if status_msg: 102 | await status_msg.edit_text(f"{Emoji.ERROR} **File too large!**\nMaximum size: {humanbytes(config.MAX_FILE_SIZE)}") 103 | return None 104 | 105 | file_uuid = await db.add_file(file_data) 106 | share_link = f"https://t.me/{config.BOT_USERNAME}?start={file_uuid}" 107 | chat_share_link = f"https://t.me/share/url?url={share_link}" 108 | 109 | return { 110 | "file_name": file_data['file_name'], 111 | "file_size": file_data['file_size'], 112 | "file_type": file_data['file_type'], 113 | "share_link": share_link, 114 | "chat_share_link": chat_share_link, 115 | "uuid": file_uuid 116 | } 117 | 118 | except Exception as e: 119 | if status_msg: 120 | error_text = ( 121 | f"{Emoji.ERROR} **Upload Failed**\n\n" 122 | f"Error: {str(e)}\n\n" 123 | "Please try again or contact support if the issue persists." 124 | ) 125 | await status_msg.edit_text(error_text) 126 | return None 127 | 128 | async def process_upload(client: Client, message: Message, replied_msg=None): 129 | from_user_id = message.from_user.id 130 | admins = await get_all_admin_ids() 131 | 132 | if from_user_id not in admins: 133 | return await message.reply_text("__You are not authorized to upload files!__") 134 | 135 | messages_to_process = [] 136 | if replied_msg: 137 | messages_to_process.append(replied_msg) 138 | elif message.media_group_id: 139 | async for m in client.get_media_group(message.chat.id, message.id): 140 | messages_to_process.append(m) 141 | else: 142 | messages_to_process.append(message) 143 | 144 | if not messages_to_process: 145 | await message.reply_text(f"{Emoji.ERROR} No valid files found!") 146 | return 147 | 148 | status_msg = await message.reply_text( 149 | f"{Emoji.LOADING} **Processing Upload**\n\n{Emoji.HOURGLASS} Processing {len(messages_to_process)} file(s)..." 150 | ) 151 | 152 | results = [] 153 | for msg in messages_to_process: 154 | result = await process_single_file(client, msg) 155 | if result: 156 | results.append(result) 157 | 158 | if not results: 159 | await status_msg.edit_text(f"{Emoji.ERROR} **No files could be processed!**") 160 | return 161 | 162 | success_text = f"{Emoji.SUCCESS} **Successfully uploaded {len(results)} file(s)**\n\n" 163 | 164 | for idx, result in enumerate(results, 1): 165 | success_text += ( 166 | f"**File {idx}:**\n" 167 | f"{Emoji.LOADING} **Name:** `{result['file_name']}`\n" 168 | f"{Emoji.HOURGLASS} **Size:** {humanbytes(result['file_size'])}\n" 169 | f"{Emoji.INBOX} **Type:** {result['file_type']}\n" 170 | f"{Emoji.ARROW} **Link:** `{result['share_link']}`\n\n" 171 | ) 172 | 173 | success_text += f"{Emoji.HOURGLASS} **Auto-Delete:** {getattr(config, 'DEFAULT_AUTO_DELETE', 30)} minutes\n" 174 | success_text += f"{Emoji.ARROW} Use `Check .env file of your Repo to change auto-delete time`" 175 | 176 | if len(results) == 1: 177 | await status_msg.edit_text( 178 | success_text, 179 | reply_markup=button_manager.file_button(results[0]['chat_share_link'], results[0]['uuid']) 180 | ) 181 | else: 182 | await status_msg.edit_text(success_text) 183 | 184 | @Client.on_message(filters.command("upload") & filters.reply) 185 | async def upload_command(client: Client, message: Message): 186 | await process_upload(client, message, message.reply_to_message) 187 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | from typing import List, Dict 2 | import os 3 | from dotenv import load_dotenv 4 | 5 | 6 | load_dotenv() 7 | 8 | # Bot Configuration 9 | BOT_TOKEN = os.getenv("BOT_TOKEN") 10 | API_ID = int(os.getenv("API_ID")) 11 | API_HASH = os.getenv("API_HASH") 12 | 13 | OWNER_ID = int(os.getenv("OWNER_ID", 7355202884)) 14 | 15 | # Database Configuration 16 | MONGO_URI = os.getenv("MONGO_URI") 17 | DATABASE_NAME = os.getenv("DATABASE_NAME") 18 | 19 | # Channel Configuration 20 | DB_CHANNEL_ID = int(os.getenv("DB_CHANNEL_ID")) 21 | FORCE_SUB_CHANNEL = int(os.getenv("FORCE_SUB_CHANNEL")) # First force sub channel 22 | FORCE_SUB_CHANNEL_2 = int(os.getenv("FORCE_SUB_CHANNEL_2", 0)) # Second force sub channel, defaults to 0 if not set 23 | FORCE_SUB_CHANNEL_3 = int(os.getenv("FORCE_SUB_CHANNEL_3", 0)) 24 | FORCE_SUB_CHANNEL_4 = int(os.getenv("FORCE_SUB_CHANNEL_4", 0)) 25 | 26 | # Add a second channel link 27 | CHANNEL_LINK = os.getenv("CHANNEL_LINK") # First channel link 28 | CHANNEL_LINK_2 = os.getenv("CHANNEL_LINK_2", "") # Second channel link 29 | CHANNEL_LINK_3 = os.getenv("CHANNEL_LINK_3", "") 30 | CHANNEL_LINK_4 = os.getenv("CHANNEL_LINK_4", "") 31 | 32 | #start photo 33 | START_PHOTO = os.getenv("START_PHOTO", "") #start photo for bot 34 | 35 | # Bot Information 36 | BOT_USERNAME = os.getenv("BOT_USERNAME") 37 | BOT_NAME = os.getenv("BOT_NAME") 38 | BOT_VERSION = "2.0" 39 | 40 | # Privacy Mode Configuration and codexbotz delete time 41 | PRIVACY_MODE = os.getenv("PRIVACY_MODE", "off").lower() == "on" 42 | AUTO_DELETE_TIME = int(os.getenv("AUTO_DELETE_TIME", 3)) 43 | 44 | # Your Modiji Url Api Key Here 45 | MODIJI_API_KEY = os.getenv("MODIJI_API_KEY") 46 | if not MODIJI_API_KEY: 47 | print("⚠️ Warning: MODIJI_API_KEY not set in environment variables") 48 | 49 | # Links 50 | CHANNEL_LINK = os.getenv("CHANNEL_LINK") 51 | DEVELOPER_LINK = os.getenv("DEVELOPER_LINK") 52 | SUPPORT_LINK = os.getenv("SUPPORT_LINK") 53 | 54 | # For Koyeb/render 55 | WEB_SERVER = bool(os.getenv("WEB_SERVER", True)) # make it True if deploying on koyeb/render else False 56 | PING_URL = os.getenv("PING_URL") # add your koyeb/render's public url 57 | PING_TIME = int(os.getenv("PING_TIME")) # Add time_out in seconds 58 | 59 | # Admin IDs - Convert space-separated string to list of integers 60 | ADMIN_IDS: List[int] = [ 61 | int(admin_id.strip()) 62 | for admin_id in os.getenv("ADMIN_IDS", "").split() 63 | if admin_id.strip().isdigit() 64 | ] 65 | 66 | # File size limit (4GB in bytes) 67 | MAX_FILE_SIZE = 4000 * 1024 * 1024 68 | 69 | # Supported file types and extensions 70 | SUPPORTED_TYPES = [ 71 | "document", 72 | "video", 73 | "audio", 74 | "photo", 75 | "voice", 76 | "video_note", 77 | "animation" 78 | ] 79 | 80 | SUPPORTED_EXTENSIONS = [ 81 | # Documents 82 | "pdf", "txt", "doc", "docx", "xls", "xlsx", "ppt", "pptx", 83 | # Programming Files 84 | "py", "js", "html", "css", "json", "xml", "yaml", "yml", 85 | # Archives 86 | "zip", "rar", "7z", "tar", "gz", "bz2", 87 | # Media Files 88 | "mp4", "mp3", "m4a", "wav", "avi", "mkv", "flv", "mov", 89 | "webm", "3gp", "m4v", "ogg", "opus", 90 | # Images 91 | "jpg", "jpeg", "png", "gif", "webp", "bmp", "ico", 92 | # Applications 93 | "apk", "exe", "msi", "deb", "rpm", 94 | # Other 95 | "txt", "text", "log", "csv", "md", "srt", "sub" 96 | ] 97 | 98 | SUPPORTED_MIME_TYPES = [ 99 | "application/pdf", 100 | "application/msword", 101 | "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 102 | "application/zip", 103 | "application/x-rar-compressed", 104 | "application/x-7z-compressed", 105 | "video/mp4", 106 | "audio/mpeg", 107 | "audio/mp4", 108 | "image/jpeg", 109 | "image/png", 110 | "image/gif", 111 | "application/vnd.android.package-archive", 112 | "application/x-executable", 113 | ] 114 | 115 | class Messages: 116 | START_TEXT = """ 117 | 🎉 **Welcome to {bot_name}!** 🎉 118 | 119 | Hello {user_mention}! I'm your secure file sharing assistant! 120 | 121 | 📢 Join @Thealphabotz for updates! 122 | 👨‍💻 Contact @adarsh2626 for support 123 | 124 | Use /help to see available commands! 125 | """ 126 | 127 | HELP_TEXT = """ 128 | 📚 **Available Commands** 129 | 130 | 👤 **User Commands:** 131 | • `/start` - Start the bot 132 | • `/help` - Show this menu 133 | • `/about` - Bot details 134 | • `/short [url]` - Shorten a link (e.g., `/short example.com`) 135 | /repo 136 | 137 | 👑 **Admin Commands:** 138 | • `/upload` - Upload a file (reply to a file) 139 | • `/stats` - View bot statistics 140 | • `/bcast` - Send a message to all users 141 | • `/auto_del` - Set auto-delete timer 142 | 143 | 144 | 🗑 **Auto-Delete System:** 145 | • Files auto-delete after a set time. 146 | • Modify timer using From Repo. 147 | 148 | 🔗 **Batch System:** 149 | • `/batch` - Group multiple files into one link. 150 | • Forward files & reply with `/batch`. 151 | 152 | 153 | 🛠 **Open Source:** 154 | 🔗 [GitHub](https://github.com/utkarshdubey2008/alphashare) 155 | 156 | ⚠️ **Need Help?** Contact [AlphaBotz](https://t.me/alphabotzchat) 157 | """ 158 | 159 | ABOUT_TEXT = """ 160 | ℹ️ 𝙰𝚋𝚘𝚞𝚝 {bot_name} 161 | 162 | 𝚅𝚎𝚛𝚜𝚒𝚘𝚗: {version} 163 | 𝙳𝚎𝚟𝚎𝚕𝚘𝚙𝚎𝚛: @Alphabotzchat 164 | 𝙻𝚊𝚗𝚐𝚞𝚊𝚐𝚎: 𝙿𝚢𝚝𝚑𝚘𝚗 165 | 𝙵𝚛𝚊𝚖𝚎𝚠𝚘𝚛𝚔: 𝙿𝚢𝚛𝚘𝚐𝚛𝚊𝚖 166 | 167 | 📢 𝚄𝚙𝚍𝚊𝚝𝚎𝚜: @TheAlphaBotz 168 | 🛠 𝚂𝚞𝚙𝚙𝚘𝚛𝚝: @AlphaBotzChat 169 | 170 | use /repo to know more info 171 | """ 172 | 173 | FILE_TEXT = """ 174 | 📁 **File Details** 175 | 176 | **Name:** `{file_name}` 177 | **Size:** {file_size} 178 | **Type:** {file_type} 179 | **Downloads:** {downloads} 180 | **Uploaded:** {upload_time} 181 | **By:** {uploader} 182 | 183 | 🔗 **Share Link:** 184 | `{share_link}` 185 | """ 186 | 187 | FORCE_SUB_TEXT = """ 188 | ⚠️ **𝙰𝚌𝚌𝚎𝚜𝚜 𝚁𝚎𝚜𝚝𝚛𝚒𝚌𝚝𝚎𝚍!** 189 | 190 | 𝙷𝚎𝚢 𝚢𝚘𝚞 𝚌𝚊𝚗'𝚝 𝚊𝚌𝚌𝚎𝚜𝚜 𝚝𝚑𝚒𝚜 𝚏𝚒𝚕𝚎𝚜 𝚞𝚗𝚝𝚒𝚕𝚕 𝚊𝚗𝚍 𝚞𝚗𝚕𝚎𝚜𝚜 𝚢𝚘𝚞 𝚓𝚘𝚒𝚗 𝚝𝚑𝚎 𝚌𝚑𝚊𝚗𝚗𝚎𝚕𝚜 𝚋𝚎𝚕𝚘𝚠 👇 191 | 𝙲𝚕𝚒𝚌𝚔 𝚋𝚞𝚝𝚝𝚘𝚗 𝚋𝚎𝚕𝚘𝚠, 𝚝𝚑𝚎𝚗 𝚝𝚛𝚢 𝚊𝚐𝚊𝚒𝚗! 192 | """ 193 | 194 | class Buttons: 195 | def start_buttons() -> List[List[Dict[str, str]]]: 196 | return [ 197 | [ 198 | {"text": "Help 📚", "callback_data": "help"}, 199 | {"text": "About ℹ️", "callback_data": "about"} 200 | ], 201 | [ 202 | {"text": "Channel 📢", "url": CHANNEL_LINK}, 203 | {"text": "Developer 👨‍💻", "url": DEVELOPER_LINK} 204 | ] 205 | ] 206 | 207 | def help_buttons() -> List[List[Dict[str, str]]]: 208 | return [ 209 | [ 210 | {"text": "Home 🏠", "callback_data": "home"}, 211 | {"text": "About ℹ️", "callback_data": "about"} 212 | ], 213 | [ 214 | {"text": "Channel 📢", "url": CHANNEL_LINK} 215 | ] 216 | ] 217 | 218 | def about_buttons() -> List[List[Dict[str, str]]]: 219 | return [ 220 | [ 221 | {"text": "Home 🏠", "callback_data": "home"}, 222 | {"text": "Help 📚", "callback_data": "help"} 223 | ], 224 | [ 225 | {"text": "Channel 📢", "url": CHANNEL_LINK} 226 | ] 227 | ] 228 | 229 | def file_buttons(file_uuid: str) -> List[List[Dict[str, str]]]: 230 | return [ 231 | [ 232 | {"text": "Download 📥", "callback_data": f"download_{file_uuid}"}, 233 | {"text": "Share 🔗", "callback_data": f"share_{file_uuid}"} 234 | ], 235 | [ 236 | {"text": "Channel 📢", "url": CHANNEL_LINK} 237 | ] 238 | ] 239 | 240 | 241 | class Progress: 242 | PROGRESS_BAR = "█" 243 | EMPTY_PROGRESS_BAR = "░" 244 | PROGRESS_TEXT = """ 245 | **{0}** {1}% 246 | 247 | **⚡️ Speed:** {2}/s 248 | **💫 Done:** {3} 249 | **💭 Total:** {4} 250 | **⏰ Time Left:** {5} 251 | """ 252 | -------------------------------------------------------------------------------- /handlers/callback_handler.py: -------------------------------------------------------------------------------- 1 | # © @Thealphabotz [2021-2025] 2 | from pyrogram import Client, filters 3 | from pyrogram.types import CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton 4 | from database import Database 5 | from utils import ButtonManager, humanbytes 6 | import config 7 | import logging 8 | import asyncio 9 | from handlers.utils.message_delete import schedule_message_deletion 10 | 11 | logging.basicConfig(level=logging.INFO) 12 | logger = logging.getLogger(__name__) 13 | 14 | db = Database() 15 | button_manager = ButtonManager() 16 | 17 | @Client.on_callback_query() 18 | async def callback_handler(client: Client, callback: CallbackQuery): 19 | try: 20 | if callback.data == "home": 21 | await button_manager.show_start(client, callback) 22 | 23 | elif callback.data == "help": 24 | await button_manager.show_help(client, callback) 25 | 26 | elif callback.data == "about": 27 | await button_manager.show_about(client, callback) 28 | 29 | elif callback.data.startswith("download_"): 30 | if not await button_manager.check_force_sub(client, callback.from_user.id): 31 | await callback.answer( 32 | "Please join our channel to download files!", 33 | show_alert=True 34 | ) 35 | return 36 | 37 | file_uuid = callback.data.split("_")[1] 38 | file_data = await db.get_file(file_uuid) 39 | 40 | if not file_data: 41 | await callback.answer("File not found!", show_alert=True) 42 | return 43 | 44 | try: 45 | status_msg = await callback.message.reply_text( 46 | "🔄 Processing Download\n\n⏳ Please wait..." 47 | ) 48 | 49 | msg = await client.copy_message( 50 | chat_id=callback.message.chat.id, 51 | from_chat_id=config.DB_CHANNEL_ID, 52 | message_id=file_data["message_id"], 53 | protect_content=config.PRIVACY_MODE 54 | ) 55 | await db.increment_downloads(file_uuid) 56 | await db.update_file_message_id(file_uuid, msg.id, callback.message.chat.id) 57 | 58 | if file_data.get("auto_delete"): 59 | delete_time = file_data.get("auto_delete_time") 60 | if delete_time: 61 | info_msg = await msg.reply_text( 62 | f"⏳ File Auto-Delete Information\n\n" 63 | f"This file will be automatically deleted in {delete_time} minutes\n" 64 | f"• Delete Time: {delete_time} minutes\n" 65 | f"• Time Left: {delete_time} minutes\n" 66 | f"💡 Save this file to your saved messages before it's deleted!", 67 | protect_content=config.PRIVACY_MODE 68 | ) 69 | 70 | file_link = f"https://t.me/{config.BOT_USERNAME}?start={file_uuid}" 71 | asyncio.create_task(schedule_message_deletion( 72 | client, 73 | callback.message.chat.id, 74 | [msg.id, info_msg.id], 75 | delete_time, 76 | file_link 77 | )) 78 | 79 | await status_msg.delete() 80 | 81 | except Exception as e: 82 | logger.error(f"Download error: {str(e)}") 83 | await callback.answer(f"Error: {str(e)}", show_alert=True) 84 | 85 | elif callback.data.startswith("share_"): 86 | file_uuid = callback.data.split("_")[1] 87 | share_link = f"https://t.me/{config.BOT_USERNAME}?start={file_uuid}" 88 | await callback.answer( 89 | f"Share Link: {share_link}", 90 | show_alert=True 91 | ) 92 | 93 | elif callback.data.startswith("dlbatch_"): 94 | if not await button_manager.check_force_sub(client, callback.from_user.id): 95 | await callback.answer( 96 | "Please join our channel to download files!", 97 | show_alert=True 98 | ) 99 | return 100 | 101 | batch_uuid = callback.data.split("_")[1] 102 | batch_data = await db.get_batch(batch_uuid) 103 | 104 | if not batch_data: 105 | await callback.answer("Batch not found!", show_alert=True) 106 | return 107 | 108 | status_msg = await callback.message.reply_text( 109 | "🔄 Processing Batch Download\n\n⏳ Please wait..." 110 | ) 111 | 112 | success_count = 0 113 | failed_count = 0 114 | sent_msgs = [] 115 | delete_time = batch_data.get("auto_delete_time", getattr(config, "DEFAULT_AUTO_DELETE", 30)) 116 | 117 | for file_uuid in batch_data["files"]: 118 | file_data = await db.get_file(file_uuid) 119 | if file_data and "message_id" in file_data: 120 | try: 121 | msg = await client.copy_message( 122 | chat_id=callback.message.chat.id, 123 | from_chat_id=config.DB_CHANNEL_ID, 124 | message_id=file_data["message_id"], 125 | protect_content=config.PRIVACY_MODE 126 | ) 127 | sent_msgs.append(msg.id) 128 | success_count += 1 129 | except Exception as e: 130 | failed_count += 1 131 | logger.error(f"Batch download error: {str(e)}") 132 | continue 133 | 134 | if success_count > 0: 135 | await db.increment_batch_downloads(batch_uuid) 136 | 137 | if batch_data.get("auto_delete", True): 138 | info_msg = await status_msg.edit_text( 139 | f"✅ Batch Download Complete\n\n" 140 | f"📥 Successfully sent: {success_count} files\n" 141 | f"❌ Failed: {failed_count} files\n\n" 142 | f"⏳ Files will be deleted in {delete_time} minutes" 143 | ) 144 | batch_link = f"https://t.me/{config.BOT_USERNAME}?start=batch_{batch_uuid}" 145 | sent_msgs.append(info_msg.id) 146 | asyncio.create_task(schedule_message_deletion( 147 | client, 148 | callback.message.chat.id, 149 | sent_msgs, 150 | delete_time, 151 | batch_link 152 | )) 153 | else: 154 | await status_msg.edit_text( 155 | f"✅ Batch Download Complete\n\n" 156 | f"📥 Successfully sent: {success_count} files\n" 157 | f"❌ Failed: {failed_count} files" 158 | ) 159 | 160 | elif callback.data.startswith("share_batch_"): 161 | batch_uuid = callback.data.split("_")[2] 162 | share_link = f"https://t.me/{config.BOT_USERNAME}?start=batch_{batch_uuid}" 163 | await callback.answer( 164 | f"Share Link: {share_link}", 165 | show_alert=True 166 | ) 167 | 168 | try: 169 | if not callback.answered: 170 | await callback.answer() 171 | except: 172 | pass 173 | 174 | except Exception as e: 175 | logger.error(f"Callback error: {str(e)}") 176 | try: 177 | if not callback.answered: 178 | await callback.answer("❌ An error occurred", show_alert=True) 179 | except: 180 | pass 181 | -------------------------------------------------------------------------------- /utils/button_manager.py: -------------------------------------------------------------------------------- 1 | from typing import List, Union 2 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery 3 | import config 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | class ButtonManager: 9 | def __init__(self): 10 | self.force_sub_channel = config.FORCE_SUB_CHANNEL 11 | self.force_sub_channel_2 = config.FORCE_SUB_CHANNEL_2 12 | self.force_sub_channel_3 = config.FORCE_SUB_CHANNEL_3 13 | self.force_sub_channel_4 = config.FORCE_SUB_CHANNEL_4 14 | self.db_channel = config.DB_CHANNEL_ID 15 | 16 | async def check_force_sub(self, client, user_id: int) -> bool: 17 | try: 18 | if self.force_sub_channel != 0: 19 | member = await client.get_chat_member(self.force_sub_channel, user_id) 20 | if member.status in ["left", "kicked"]: 21 | return False 22 | 23 | if self.force_sub_channel_2 != 0: 24 | member = await client.get_chat_member(self.force_sub_channel_2, user_id) 25 | if member.status in ["left", "kicked"]: 26 | return False 27 | if self.force_sub_channel_3 != 0: 28 | member = await client.get_chat_member(self.force_sub_channel_3, user_id) 29 | if member.status in ["left", "kicked"]: 30 | return False 31 | if self.force_sub_channel_4 != 0: 32 | member = await client.get_chat_member(self.force_sub_channel_4, user_id) 33 | if member.status in ["left", "kicked"]: 34 | return False 35 | 36 | return True 37 | except Exception as e: 38 | logger.error(f"Force sub check error: {str(e)}") 39 | return False 40 | 41 | async def show_start(self, client, callback_query: CallbackQuery): 42 | try: 43 | await callback_query.message.reply_photo( 44 | photo=config.START_PHOTO, 45 | caption=config.Messages.START_TEXT.format( 46 | bot_name=config.BOT_NAME, 47 | user_mention=callback_query.from_user.mention 48 | ), 49 | reply_markup=self.start_button() 50 | ) 51 | except Exception as e: 52 | logger.error(f"Show start error: {str(e)}") 53 | 54 | async def show_help(self, client, callback_query: CallbackQuery): 55 | try: 56 | await callback_query.message.edit_text( 57 | config.Messages.HELP_TEXT, 58 | reply_markup=self.help_button() 59 | ) 60 | except Exception as e: 61 | logger.error(f"Show help error: {str(e)}") 62 | 63 | async def show_about(self, client, callback_query: CallbackQuery): 64 | try: 65 | await callback_query.message.edit_text( 66 | config.Messages.ABOUT_TEXT.format( 67 | bot_name=config.BOT_NAME, 68 | version=config.BOT_VERSION 69 | ), 70 | reply_markup=self.about_button() 71 | ) 72 | except Exception as e: 73 | logger.error(f"Show about error: {str(e)}") 74 | 75 | def force_sub_button(self) -> InlineKeyboardMarkup: 76 | buttons = [] 77 | 78 | if config.FORCE_SUB_CHANNEL != 0 and config.CHANNEL_LINK: 79 | buttons.append([ 80 | InlineKeyboardButton( 81 | "Join Channel 1 🔔", 82 | url=config.CHANNEL_LINK 83 | ) 84 | ]) 85 | 86 | if config.FORCE_SUB_CHANNEL_2 != 0 and config.CHANNEL_LINK_2: 87 | buttons.append([ 88 | InlineKeyboardButton( 89 | "Join Channel 2 🔔", 90 | url=config.CHANNEL_LINK_2 91 | ) 92 | ]) 93 | 94 | if config.FORCE_SUB_CHANNEL_3 != 0 and config.CHANNEL_LINK_3: 95 | buttons.append([ 96 | InlineKeyboardButton( 97 | "Join Channel 3 🔔", 98 | url=config.CHANNEL_LINK_3 99 | ) 100 | ]) 101 | 102 | if config.FORCE_SUB_CHANNEL_4 != 0 and config.CHANNEL_LINK_4: 103 | buttons.append([ 104 | InlineKeyboardButton( 105 | "Join Channel 4 🔔", 106 | url=config.CHANNEL_LINK_4 107 | ) 108 | ]) 109 | 110 | if config.BOT_USERNAME: 111 | buttons.append([ 112 | InlineKeyboardButton( 113 | "✅ Try Again", 114 | url=f"https://t.me/{config.BOT_USERNAME}?start=start" 115 | ) 116 | ]) 117 | 118 | return InlineKeyboardMarkup(buttons) 119 | 120 | def start_button(self) -> InlineKeyboardMarkup: 121 | buttons = [ 122 | [ 123 | InlineKeyboardButton("Help 📜", callback_data="help"), 124 | InlineKeyboardButton("About ℹ️", callback_data="about") 125 | ] 126 | ] 127 | 128 | buttons.append([ 129 | InlineKeyboardButton("Developer 👨‍💻", url=config.DEVELOPER_LINK) 130 | ]) 131 | 132 | return InlineKeyboardMarkup(buttons) 133 | 134 | def help_button(self) -> InlineKeyboardMarkup: 135 | buttons = [ 136 | [ 137 | InlineKeyboardButton("Home 🏠", callback_data="home"), 138 | InlineKeyboardButton("About ℹ️", callback_data="about") 139 | ] 140 | ] 141 | 142 | return InlineKeyboardMarkup(buttons) 143 | 144 | def about_button(self) -> InlineKeyboardMarkup: 145 | buttons = [ 146 | [ 147 | InlineKeyboardButton("Home 🏠", callback_data="home"), 148 | InlineKeyboardButton("Help 📜", callback_data="help") 149 | ] 150 | ] 151 | 152 | return InlineKeyboardMarkup(buttons) 153 | 154 | def file_button(self, chat_share_link, file_uuid: str) -> InlineKeyboardMarkup: 155 | buttons = [ 156 | [ 157 | InlineKeyboardButton("Download 📥", callback_data=f"download_{file_uuid}"), 158 | InlineKeyboardButton("Share Link 🔗", url=chat_share_link) 159 | ] 160 | ] 161 | 162 | return InlineKeyboardMarkup(buttons) 163 | 164 | def batch_button(self, batch_uuid: str) -> InlineKeyboardMarkup: 165 | buttons = [ 166 | [ 167 | InlineKeyboardButton("Download All 📥", callback_data=f"dlbatch_{batch_uuid}"), 168 | InlineKeyboardButton("Share Link 🔗", callback_data=f"share_batch_{batch_uuid}") 169 | ] 170 | ] 171 | 172 | return InlineKeyboardMarkup(buttons) 173 | 174 | 175 | def force_sub_button_new(self, file_uuid: str) -> InlineKeyboardMarkup: 176 | buttons = [] 177 | 178 | if config.FORCE_SUB_CHANNEL != 0 and config.CHANNEL_LINK: 179 | buttons.append([ 180 | InlineKeyboardButton( 181 | "Join Channel 1 🔔", 182 | url=config.CHANNEL_LINK 183 | ) 184 | ]) 185 | 186 | if config.FORCE_SUB_CHANNEL_2 != 0 and config.CHANNEL_LINK_2: 187 | buttons.append([ 188 | InlineKeyboardButton( 189 | "Join Channel 2 🔔", 190 | url=config.CHANNEL_LINK_2 191 | ) 192 | ]) 193 | 194 | if config.FORCE_SUB_CHANNEL_3 != 0 and config.CHANNEL_LINK_3: 195 | buttons.append([ 196 | InlineKeyboardButton( 197 | "Join Channel 3 🔔", 198 | url=config.CHANNEL_LINK_3 199 | ) 200 | ]) 201 | 202 | if config.FORCE_SUB_CHANNEL_4 != 0 and config.CHANNEL_LINK_4: 203 | buttons.append([ 204 | InlineKeyboardButton( 205 | "Join Channel 4 🔔", 206 | url=config.CHANNEL_LINK_4 207 | ) 208 | ]) 209 | 210 | if config.BOT_USERNAME: 211 | buttons.append([ 212 | InlineKeyboardButton( 213 | "✅ Try Again", 214 | url=f"https://t.me/{config.BOT_USERNAME}?start={file_uuid}" 215 | ) 216 | ]) 217 | 218 | return InlineKeyboardMarkup(buttons) 219 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # αlphα Fílє Shαrє Ɓot 4 | 5 | Version 2.1 6 | 7 | **A Revolutionary Telegram File Sharing Bot with Quality Upload Mode** 8 | 9 | Python Version 10 | GitHub Stars 11 | GitHub Forks 12 |
13 | GitHub Issues 14 | Last Commit 15 | Updates Channel 16 | 17 |
18 | 19 | --- 20 | 21 | ## 🎯 What's New in V2.1 22 | 23 |
24 | 25 | ### 🚀 MAJOR FEATURE: QUALITY UPLOAD MODE 26 | 27 | Quality Upload Feature 28 | 29 | *Automatically organize your media files by quality and generate grouped, shareable links* 30 | 31 |
32 | 33 | ### 📌 Quality Upload Mode Overview 34 | 35 | A revolutionary feature that **automatically organizes** your media files by quality (480p, 720p, 1080p, HDrip, WEBrip, etc.) and generates **grouped, shareable links**. 36 | 37 | #### 🔧 Commands 38 | 39 | | Command | Description | 40 | |---------|-------------| 41 | | `/qu` or `/qupload` | Start quality upload mode | 42 | | `/qmode` | Toggle between filename/caption extraction | 43 | | `/qdone` or `/qud` | Generate quality-grouped links | 44 | | `/qcancel` | Cancel quality upload mode | 45 | 46 | #### 📝 How It Works 47 | 48 | 1. **Activate** quality upload with `/qu` 49 | 2. **Send** your media files (videos/documents) 50 | 3. **Auto-detect** quality from filename or caption 51 | 4. **Generate** organized links with `/qdone` 52 | 53 | #### 💡 Example Output 54 | 55 | ``` 56 | 480p - https://t.me/bot?start=xxx | 720p - https://t.me/bot?start=xxx | 1080p - https://t.me/bot?start=xxx 57 | HDrip - https://t.me/bot?start=xxx 58 | WEBrip - https://t.me/bot?start=xxx 59 | ``` 60 | 61 | #### 🎨 Supported Quality Formats 62 | 63 | - **Resolutions**: `240p`, `360p`, `480p`, `720p`, `1080p`, `1440p`, `2160p`, `4K` 64 | - **Rip Types**: `BluRay`, `WEB-DL`, `WEBrip`, `HDrip`, `HDTV`, `DVDrip`, `TS`, `CAM` 65 | - **Flexible Pattern**: Handles `HDrip`, `HD-rip`, `HD_rip`, `HD.rip`, `hdrip` 66 | 67 | --- 68 | 69 | ## 🎯 Quality Extraction Modes 70 | 71 |
72 | Extraction Modes 73 |
74 | 75 | ### 📂 Filename Mode (Default) 76 | - Extracts quality from file name 77 | - **Example**: `Movie.Name.2024.1080p.WEBrip.mkv` → `1080p` & `WEBrip` 78 | 79 | ### 💬 Caption Mode 80 | - Extracts quality from file caption/description 81 | - Useful when quality is mentioned in caption 82 | - Toggle via `/qmode` command 83 | 84 | ### 🔀 Smart Fallback 85 | - If caption mode is active but no caption exists 86 | - Automatically falls back to filename extraction 87 | - Ensures quality is always detected 88 | 89 | --- 90 | 91 | ## 🚀 Batch Mode Enhancements 92 | 93 |
94 | Batch Mode 95 |
96 | 97 | ### 📦 Perfect Episode Sequencing 98 | - Files now send in **exact sorted order** 99 | - Episode numbers detected from multiple patterns 100 | - Supports: `E01`, `Episode 01`, `Ep01`, `S01E01`, `[E01]`, `-E01-` 101 | 102 | ### ⚡ FloodWait Resume System 103 | - Bot remembers **exact position** during FloodWait 104 | - Resumes from where it stopped 105 | - Real-time progress tracking 106 | - No files skipped or duplicated 107 | 108 | --- 109 | 110 | ## 🔧 Technical Improvements 111 | 112 | ### ✅ Smart Link Formatting 113 | - All links now in monospace format for easy copying 114 | - Resolution qualities grouped on one line 115 | - Rip qualities listed separately for clarity 116 | 117 | ### ✅ Retry Logic 118 | - 3 automatic retries on failed uploads 119 | - 2-second delay between retries 120 | - Graceful error handling 121 | 122 | ### ✅ Command Aliases 123 | - `/qdone` → `/qud` (quick done) 124 | - Both work identically for user convenience 125 | 126 | ### ✅ Admin-Only Access 127 | - All batch and quality upload features restricted to admins 128 | - Unauthorized users receive clear denial messages 129 | 130 | --- 131 | 132 | ## 📋 Complete Command Reference 133 | 134 | ### 🎬 Quality Upload Commands 135 | 136 | ``` 137 | /qu, /qupload → Start quality upload mode 138 | /qmode → Toggle extraction mode (filename/caption) 139 | /qdone, /qud → Generate quality links 140 | /qcancel → Cancel quality upload 141 | ``` 142 | 143 | ### 📦 Batch Upload Commands 144 | 145 | ``` 146 | /batch → Start batch mode 147 | /done → Generate batch link (episode sorted) 148 | /cancel → Cancel batch mode 149 | ``` 150 | 151 | ### 👑 Admin Commands 152 | 153 | ``` 154 | /addadmin → Add a new admin (Owner only) 155 | /rmadmin → Remove admin privileges (Owner only) 156 | /adminlist → View current admin list (Owner only) 157 | ``` 158 | 159 | ### 🛠️ Utility Commands 160 | 161 | ``` 162 | /start → Start the bot 163 | /help → Get help information 164 | /stats → View bot statistics 165 | /short → Shorten a URL 166 | ``` 167 | 168 | --- 169 | 170 | ## ✨ Core Features 171 | 172 | 173 | 174 | 189 | 204 | 205 |
175 | 176 | ### 🎯 Quality Upload Mode 177 | - Auto-organize by quality 178 | - Grouped shareable links 179 | - Smart quality detection 180 | - Multiple extraction modes 181 | 182 | ### 📦 Advanced Batch Mode 183 | - Perfect episode sorting 184 | - FloodWait resume system 185 | - Real-time progress tracking 186 | - No duplicates or skips 187 | 188 | 190 | 191 | ### 🔐 Security & Admin 192 | - Multi-admin system 193 | - MongoDB admin management 194 | - Admin-only uploads 195 | - File access control 196 | 197 | ### 📊 Analytics & Tracking 198 | - Real-time download stats 199 | - Storage analytics 200 | - User tracking 201 | - Performance metrics 202 | 203 |
206 | 207 | ### 🌟 Additional Features 208 | 209 | - ✅ **Universal File Support** — All Telegram-supported file types 210 | - ✅ **UUID-based Links** — Unique sharing with download tracking 211 | - ✅ **Professional UI** — Clean interface with progress bars 212 | - ✅ **Auto-Delete** — Configurable auto-deletion for copyright 213 | - ✅ **URL Shortening** — Built-in URL shortener 214 | - ✅ **Privacy Mode** — Prevent file forwarding/copying 215 | - ✅ **24/7 Uptime** — Koyeb keep-alive mechanism 216 | - ✅ **Force Subscribe** — Multiple force sub channels support 217 | - ✅ **Broadcast System** — Message broadcasting with inline buttons 218 | 219 | --- 220 | 221 | ## 🛠️ Installation & Deployment 222 | 223 | ### 🚀 Quick Deploy 224 | 225 |
226 | 227 | [![Deploy on Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/utkarshdubey2008/AlphaShare) 228 | [![Deploy on Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://youtu.be/2EKt3nVcY6E?si=NKMlRw3qx6eaWjNU) 229 | 230 |
231 | 232 | ### 💻 Manual Installation 233 | 234 | ```bash 235 | # Clone the repository 236 | git clone https://github.com/utkarshdubey2008/AlphaShare.git 237 | cd AlphaShare 238 | 239 | # Create virtual environment 240 | python -m venv venv 241 | 242 | # Activate virtual environment 243 | source venv/bin/activate # Linux/Mac 244 | .\venv\Scripts\activate # Windows 245 | 246 | # Install dependencies 247 | pip install -r requirements.txt 248 | 249 | # Run the bot 250 | python main.py 251 | ``` 252 | 253 | ### 🔄 How to Update to V2.1 254 | 255 | > [!IMPORTANT] 256 | > **Sync Your Fork** to get the latest V2.1 features! 257 | 258 | 1. Go to your forked repository on GitHub 259 | 2. Click on **"Sync fork"** button 260 | 3. Click **"Update branch"** to sync with the latest changes 261 | 4. Redeploy your bot to apply the updates 262 | 263 | --- 264 | 265 | ## 🎓 Usage Examples 266 | 267 | ### Example 1: Quality Upload Mode 268 | 269 | ``` 270 | User: /qu 271 | Bot: ✅ Quality Upload Mode activated! 272 | 273 | User: [Sends Movie.2024.1080p.WEBrip.mkv] 274 | Bot: ✅ Detected: 1080p, WEBrip 275 | 276 | User: [Sends Movie.2024.720p.HDrip.mkv] 277 | Bot: ✅ Detected: 720p, HDrip 278 | 279 | User: /qdone 280 | Bot: 281 | 📊 Quality Upload Complete! 282 | 283 | 1080p - https://t.me/bot?start=xxx 284 | 720p - https://t.me/bot?start=xxx 285 | WEBrip - https://t.me/bot?start=xxx 286 | HDrip - https://t.me/bot?start=xxx 287 | ``` 288 | 289 | ### Example 2: Batch Mode with Episodes 290 | 291 | ``` 292 | User: /batch 293 | Bot: ✅ Batch Mode activated! 294 | 295 | User: [Sends Series.S01E03.mkv] 296 | User: [Sends Series.S01E01.mkv] 297 | User: [Sends Series.S01E02.mkv] 298 | 299 | User: /done 300 | Bot: 301 | 📦 Batch Upload Complete! 302 | Files sorted: E01, E02, E03 303 | 304 | Batch Link: https://t.me/bot?start=batch_xxx 305 | ``` 306 | 307 | --- 308 | 309 | ## 📜 License 310 | 311 | This project is licensed under the **[MIT LICENSE](https://github.com/utkarshdubey2008/Alphashare/blob/main/License)**. 312 | 313 | --- 314 | 315 | ## 🙏 Credits & Acknowledgments 316 | 317 |
318 | 319 | ### 👨‍💻 Developer 320 | 321 | **[Utkarsh Dubey](https://github.com/utkarshdubey2008)** 322 | *Main Developer of Alpha Share Bot* 323 | 324 | ### 🛠️ Technologies 325 | 326 | - **[Pyrogram](https://github.com/pyrogram/pyrogram)** — Telegram MTProto API Framework 327 | - **[MongoDB](https://www.mongodb.com/)** — Database for admin and file management 328 | - **[Koyeb](https://www.koyeb.com/)** — Seamless 24/7 hosting 329 | 330 | ### 🌟 Special Thanks 331 | 332 | - Contributors, testers, and community members 333 | - Alpha Bots Community for continuous support 334 | - All users who provided valuable feedback 335 | 336 |
337 | 338 | --- 339 | 340 |
341 | 342 | ### 💬 Join Our Community 343 | 344 | 345 | Telegram Channel 346 | 347 | 348 | **Stay updated with the latest features and announcements!** 349 | 350 | --- 351 | 352 | Made with ❤️ by [Utkarsh Dubey](https://github.com/utkarshdubey2008) 353 | 354 | **⭐ Star this repo if you find it useful!** 355 | 356 |
357 | -------------------------------------------------------------------------------- /database.py: -------------------------------------------------------------------------------- 1 | from motor.motor_asyncio import AsyncIOMotorClient 2 | from datetime import datetime, timedelta 3 | import config 4 | import pytz 5 | from typing import List, Dict, Optional, Union 6 | import logging 7 | import uuid 8 | 9 | logging.basicConfig(level=logging.INFO) 10 | logger = logging.getLogger(__name__) 11 | 12 | class Database: 13 | def __init__(self): 14 | try: 15 | self.client = AsyncIOMotorClient(config.MONGO_URI) 16 | self.db = self.client[config.DATABASE_NAME] 17 | self.files_collection = self.db.files 18 | self.users_collection = self.db.users 19 | self.batch_collection = self.db.batches 20 | self.settings = self.db.settings 21 | self.messages_collection = self.db.messages 22 | self.admin_collection = self.db.admins 23 | 24 | logger.info("Database connection established successfully") 25 | except Exception as e: 26 | logger.error(f"Database connection failed: {str(e)}") 27 | raise 28 | 29 | async def add_user(self, user_id: int, username: str = None, first_name: str = None) -> bool: 30 | try: 31 | if not await self.users_collection.find_one({"user_id": user_id}): 32 | user_data = { 33 | "user_id": user_id, 34 | "username": username, 35 | "first_name": first_name, 36 | "join_date": datetime.now(pytz.UTC), 37 | "total_files": 0, 38 | "total_downloads": 0 39 | } 40 | await self.users_collection.insert_one(user_data) 41 | logger.info(f"New user added: {user_id}") 42 | return True 43 | return False 44 | except Exception as e: 45 | logger.error(f"Error adding user: {str(e)}") 46 | return False 47 | 48 | async def get_all_users(self) -> List[Dict]: 49 | try: 50 | return await self.users_collection.find({}).to_list(None) 51 | except Exception as e: 52 | logger.error(f"Error getting all users: {str(e)}") 53 | return [] 54 | 55 | async def add_file(self, file_data: dict) -> str: 56 | try: 57 | file_data.update({ 58 | "uuid": str(uuid.uuid4()), 59 | "upload_time": datetime.now(pytz.UTC), 60 | "downloads": 0, 61 | "last_accessed": datetime.now(pytz.UTC), 62 | "active_copies": [] 63 | }) 64 | await self.files_collection.insert_one(file_data) 65 | await self.users_collection.update_one( 66 | {"user_id": file_data["uploader_id"]}, 67 | {"$inc": {"total_files": 1}} 68 | ) 69 | logger.info(f"New file added: {file_data['uuid']}") 70 | return file_data["uuid"] 71 | except Exception as e: 72 | logger.error(f"Error adding file: {str(e)}") 73 | raise 74 | 75 | async def update_file_message_id(self, file_uuid: str, message_id: int, chat_id: int) -> bool: 76 | try: 77 | message_data = { 78 | "file_uuid": file_uuid, 79 | "message_id": message_id, 80 | "chat_id": chat_id, 81 | "created_at": datetime.now(pytz.UTC) 82 | } 83 | await self.messages_collection.insert_one(message_data) 84 | 85 | await self.files_collection.update_one( 86 | {"uuid": file_uuid}, 87 | { 88 | "$push": {"active_copies": {"chat_id": chat_id, "message_id": message_id}}, 89 | "$set": {"last_accessed": datetime.now(pytz.UTC)} 90 | } 91 | ) 92 | logger.info(f"Added message copy for file {file_uuid}: {message_id} in chat {chat_id}") 93 | return True 94 | except Exception as e: 95 | logger.error(f"Error updating file message ID: {str(e)}") 96 | return False 97 | 98 | async def get_file(self, file_uuid: str) -> Optional[dict]: 99 | try: 100 | file_data = await self.files_collection.find_one({"uuid": file_uuid}) 101 | if file_data: 102 | await self.files_collection.update_one( 103 | {"uuid": file_uuid}, 104 | {"$set": {"last_accessed": datetime.now(pytz.UTC)}} 105 | ) 106 | return file_data 107 | except Exception as e: 108 | logger.error(f"Error getting file: {str(e)}") 109 | return None 110 | 111 | async def delete_file(self, file_uuid: str) -> bool: 112 | try: 113 | file_data = await self.get_file(file_uuid) 114 | if file_data: 115 | result = await self.files_collection.delete_one({"uuid": file_uuid}) 116 | if result.deleted_count > 0: 117 | await self.users_collection.update_one( 118 | {"user_id": file_data["uploader_id"]}, 119 | {"$inc": {"total_files": -1}} 120 | ) 121 | await self.messages_collection.delete_many({"file_uuid": file_uuid}) 122 | logger.info(f"File deleted: {file_uuid}") 123 | return True 124 | return False 125 | except Exception as e: 126 | logger.error(f"Error deleting file: {str(e)}") 127 | return False 128 | 129 | async def increment_downloads(self, file_uuid: str) -> int: 130 | try: 131 | result = await self.files_collection.find_one_and_update( 132 | {"uuid": file_uuid}, 133 | { 134 | "$inc": {"downloads": 1}, 135 | "$set": {"last_accessed": datetime.now(pytz.UTC)} 136 | }, 137 | return_document=True 138 | ) 139 | if result: 140 | await self.users_collection.update_one( 141 | {"user_id": result["uploader_id"]}, 142 | {"$inc": {"total_downloads": 1}} 143 | ) 144 | return result["downloads"] 145 | return 0 146 | except Exception as e: 147 | logger.error(f"Error incrementing downloads: {str(e)}") 148 | return 0 149 | 150 | async def add_batch(self, batch_data: dict) -> str: 151 | try: 152 | for file_uuid in batch_data["files"]: 153 | file_data = await self.get_file(file_uuid) 154 | if not file_data: 155 | raise ValueError(f"File {file_uuid} not found") 156 | 157 | batch_data.update({ 158 | "uuid": str(uuid.uuid4()), 159 | "created_at": datetime.now(pytz.UTC), 160 | "downloads": 0, 161 | "last_accessed": datetime.now(pytz.UTC), 162 | "active_copies": [] 163 | }) 164 | await self.batch_collection.insert_one(batch_data) 165 | logger.info(f"New batch created: {batch_data['uuid']}") 166 | return batch_data["uuid"] 167 | except Exception as e: 168 | logger.error(f"Error creating batch: {str(e)}") 169 | raise 170 | 171 | async def get_batch(self, batch_uuid: str) -> Optional[dict]: 172 | try: 173 | batch_data = await self.batch_collection.find_one({"uuid": batch_uuid}) 174 | if batch_data: 175 | valid_files = [] 176 | for file_uuid in batch_data["files"]: 177 | file_data = await self.get_file(file_uuid) 178 | if file_data: 179 | valid_files.append(file_uuid) 180 | 181 | if valid_files: 182 | await self.batch_collection.update_one( 183 | {"uuid": batch_uuid}, 184 | {"$set": { 185 | "last_accessed": datetime.now(pytz.UTC), 186 | "files": valid_files 187 | }} 188 | ) 189 | batch_data["files"] = valid_files 190 | return batch_data 191 | return None 192 | except Exception as e: 193 | logger.error(f"Error getting batch: {str(e)}") 194 | return None 195 | 196 | async def delete_batch(self, batch_uuid: str) -> bool: 197 | try: 198 | result = await self.batch_collection.delete_one({"uuid": batch_uuid}) 199 | success = result.deleted_count > 0 200 | if success: 201 | logger.info(f"Batch deleted: {batch_uuid}") 202 | return success 203 | except Exception as e: 204 | logger.error(f"Error deleting batch: {str(e)}") 205 | return False 206 | 207 | async def increment_batch_downloads(self, batch_uuid: str) -> int: 208 | try: 209 | result = await self.batch_collection.find_one_and_update( 210 | {"uuid": batch_uuid}, 211 | { 212 | "$inc": {"downloads": 1}, 213 | "$set": {"last_accessed": datetime.now(pytz.UTC)} 214 | }, 215 | return_document=True 216 | ) 217 | return result["downloads"] if result else 0 218 | except Exception as e: 219 | logger.error(f"Error incrementing batch downloads: {str(e)}") 220 | return 0 221 | 222 | async def get_setting(self, key: str, default=None): 223 | try: 224 | doc = await self.settings.find_one({"key": key}) 225 | return doc["value"] if doc else default 226 | except Exception as e: 227 | logger.error(f"Error getting setting '{key}': {str(e)}") 228 | return default 229 | 230 | async def set_setting(self, key: str, value): 231 | try: 232 | await self.settings.update_one( 233 | {"key": key}, 234 | {"$set": {"value": value}}, 235 | upsert=True 236 | ) 237 | logger.info(f"Setting '{key}' updated to: {value}") 238 | except Exception as e: 239 | logger.error(f"Error setting '{key}': {str(e)}") 240 | 241 | async def get_stats(self) -> dict: 242 | try: 243 | total_users = await self.users_collection.count_documents({}) 244 | total_files = await self.files_collection.count_documents({}) 245 | total_batches = await self.batch_collection.count_documents({}) 246 | 247 | downloads_agg = await self.files_collection.aggregate([ 248 | {"$group": {"_id": None, "total": {"$sum": "$downloads"}}} 249 | ]).to_list(1) 250 | total_downloads = downloads_agg[0]["total"] if downloads_agg else 0 251 | 252 | batch_downloads_agg = await self.batch_collection.aggregate([ 253 | {"$group": {"_id": None, "total": {"$sum": "$downloads"}}} 254 | ]).to_list(1) 255 | total_batch_downloads = batch_downloads_agg[0]["total"] if batch_downloads_agg else 0 256 | 257 | active_files = await self.files_collection.count_documents({"active_copies": {"$ne": []}}) 258 | 259 | return { 260 | "total_users": total_users, 261 | "total_files": total_files, 262 | "total_batches": total_batches, 263 | "total_downloads": total_downloads, 264 | "total_batch_downloads": total_batch_downloads, 265 | "active_files": active_files 266 | } 267 | except Exception as e: 268 | logger.error(f"Error getting stats: {str(e)}") 269 | return { 270 | "total_users": 0, 271 | "total_files": 0, 272 | "total_batches": 0, 273 | "total_downloads": 0, 274 | "total_batch_downloads": 0, 275 | "active_files": 0 276 | } 277 | -------------------------------------------------------------------------------- /handlers/admin/batch.py: -------------------------------------------------------------------------------- 1 | # © @TheAlphaBotz [2021-2025] 2 | from pyrogram import Client, filters 3 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 4 | from pyrogram.errors import FloodWait 5 | from database import Database 6 | from utils import ButtonManager, humanbytes 7 | import config 8 | import uuid 9 | from datetime import datetime 10 | import pytz 11 | from handlers.admin.manage_admin import get_all_admin_ids 12 | import asyncio 13 | import re 14 | 15 | db = Database() 16 | button_manager = ButtonManager() 17 | batch_users = {} 18 | 19 | def extract_episode_number(filename): 20 | if not filename: 21 | return float('inf') 22 | 23 | patterns = [ 24 | r'[Ee](\d+)', 25 | r'[Ee]pisode\s*(\d+)', 26 | r'[Ee]p\s*(\d+)', 27 | r'S\d+[Ee](\d+)', 28 | r'\[E(\d+)\]', 29 | r'-E(\d+)-', 30 | ] 31 | 32 | for pattern in patterns: 33 | match = re.search(pattern, filename) 34 | if match: 35 | return int(match.group(1)) 36 | 37 | return float('inf') 38 | 39 | @Client.on_message(filters.command("batch") & filters.private) 40 | async def batch_command(client: Client, message: Message): 41 | from_user_id = message.from_user.id 42 | 43 | admins = await get_all_admin_ids() 44 | 45 | if from_user_id not in admins: 46 | return await message.reply_text("__Yᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀᴜᴛʜᴏʀɪᴢᴇᴅ ᴛᴏ ᴜꜱᴇ ʙᴀᴛᴄʜ ᴍᴏᴅᴇ!__") 47 | 48 | user_id = message.from_user.id 49 | batch_users[user_id] = { 50 | "files": [], 51 | "status_msg": None, 52 | "processing_queue": [], 53 | "is_processing": False, 54 | "processed_count": 0 55 | } 56 | 57 | await message.reply_text( 58 | "📦 **Bᴀᴛᴄʜ Mᴏᴅᴇ Aᴄᴛɪᴠᴀᴛᴇᴅ!**\n\n" 59 | "• Sᴇɴᴅ ᴍᴜʟᴛɪᴘʟᴇ ꜰɪʟᴇꜱ ᴏɴᴇ ʙʏ ᴏɴᴇ\n" 60 | "• Eᴀᴄʜ ꜰɪʟᴇ ᴡɪʟʟ ʙᴇ ᴘʀᴏᴄᴇꜱꜱᴇᴅ ᴀᴜᴛᴏᴍᴀᴛɪᴄᴀʟʟʏ\n" 61 | "• Uꜱᴇ /done ᴡʜᴇɴ ꜰɪɴɪꜱʜᴇᴅ ᴛᴏ ɢᴇᴛ ʙᴀᴛᴄʜ ʟɪɴᴋ\n" 62 | "• Uꜱᴇ /cancel ᴛᴏ ᴄᴀɴᴄᴇʟ ʙᴀᴛᴄʜ ᴍᴏᴅᴇ" 63 | ) 64 | 65 | async def process_file_sequentially(client: Client, user_id: int): 66 | if batch_users[user_id]["is_processing"]: 67 | return 68 | 69 | batch_users[user_id]["is_processing"] = True 70 | 71 | while batch_users[user_id]["processing_queue"]: 72 | message_data = batch_users[user_id]["processing_queue"].pop(0) 73 | message = message_data["message"] 74 | 75 | try: 76 | max_retries = 3 77 | retry_count = 0 78 | copied_msg = None 79 | 80 | while retry_count < max_retries: 81 | try: 82 | copied_msg = await message.copy(config.DB_CHANNEL_ID) 83 | break 84 | except FloodWait as e: 85 | retry_count += 1 86 | wait_time = e.value 87 | await message.reply_text(f"⏳ Fʟᴏᴏᴅᴡᴀɪᴛ {wait_time} ꜱᴇᴄᴏɴᴅꜱ. Rᴇꜱᴜᴍɪɴɢ...") 88 | await asyncio.sleep(wait_time) 89 | except Exception as e: 90 | if retry_count >= max_retries - 1: 91 | raise 92 | await asyncio.sleep(2) 93 | 94 | if not copied_msg: 95 | await message.reply_text("❌ Fᴀɪʟᴇᴅ ᴛᴏ ᴘʀᴏᴄᴇꜱꜱ ꜰɪʟᴇ ᴀꜰᴛᴇʀ ʀᴇᴛʀɪᴇꜱ") 96 | continue 97 | 98 | file_data = { 99 | "file_id": None, 100 | "file_name": "Unknown", 101 | "file_size": 0, 102 | "file_type": None, 103 | "uuid": str(uuid.uuid4()), 104 | "uploader_id": user_id, 105 | "message_id": copied_msg.id, 106 | "auto_delete": True, 107 | "auto_delete_time": getattr(config, 'DEFAULT_AUTO_DELETE', 30), 108 | "sequence_number": len(batch_users[user_id]["files"]) + 1 109 | } 110 | 111 | if message.document: 112 | file_data.update({ 113 | "file_id": message.document.file_id, 114 | "file_name": message.document.file_name or "document", 115 | "file_size": message.document.file_size, 116 | "file_type": "document" 117 | }) 118 | elif message.video: 119 | file_data.update({ 120 | "file_id": message.video.file_id, 121 | "file_name": message.video.file_name or "video.mp4", 122 | "file_size": message.video.file_size, 123 | "file_type": "video" 124 | }) 125 | elif message.audio: 126 | file_data.update({ 127 | "file_id": message.audio.file_id, 128 | "file_name": message.audio.file_name or "audio", 129 | "file_size": message.audio.file_size, 130 | "file_type": "audio" 131 | }) 132 | elif message.photo: 133 | file_data.update({ 134 | "file_id": message.photo.file_id, 135 | "file_name": f"photo_{file_data['uuid']}.jpg", 136 | "file_size": message.photo.file_size, 137 | "file_type": "photo" 138 | }) 139 | else: 140 | await message.reply_text("❌ Uɴꜱᴜᴘᴘᴏʀᴛᴇᴅ ꜰɪʟᴇ ᴛʏᴘᴇ!") 141 | continue 142 | 143 | if not file_data["file_id"]: 144 | await message.reply_text("❌ Cᴏᴜʟᴅ ɴᴏᴛ ᴘʀᴏᴄᴇꜱꜱ ꜰɪʟᴇ!") 145 | continue 146 | 147 | if file_data["file_size"] > config.MAX_FILE_SIZE: 148 | await message.reply_text(f"❌ Fɪʟᴇ ᴛᴏᴏ ʟᴀʀɢᴇ!\nMᴀxɪᴍᴜᴍ ꜱɪᴢᴇ: {humanbytes(config.MAX_FILE_SIZE)}") 149 | continue 150 | 151 | file_uuid = await db.add_file(file_data) 152 | batch_users[user_id]["files"].append(file_uuid) 153 | batch_users[user_id]["processed_count"] += 1 154 | 155 | await message.reply_text(f"✅ Fɪʟᴇ Aᴅᴅᴇᴅ Iɴ Qᴜᴇᴜᴇ ({batch_users[user_id]['processed_count']})") 156 | 157 | except FloodWait as e: 158 | batch_users[user_id]["processing_queue"].insert(0, message_data) 159 | wait_time = e.value 160 | await message.reply_text(f"⏳ Fʟᴏᴏᴅᴡᴀɪᴛ {wait_time} ꜱᴇᴄᴏɴᴅꜱ. Rᴇꜱᴜᴍɪɴɢ...") 161 | await asyncio.sleep(wait_time) 162 | continue 163 | 164 | except Exception as e: 165 | await message.reply_text( 166 | f"❌ Pʀᴏᴄᴇꜱꜱɪɴɢ Fᴀɪʟᴇᴅ\n\n" 167 | f"Eʀʀᴏʀ: {str(e)}" 168 | ) 169 | 170 | await asyncio.sleep(0.5) 171 | 172 | batch_users[user_id]["is_processing"] = False 173 | 174 | @Client.on_message(~filters.command(["batch", "done", "cancel", "qu", "qupload", "qdone", "qud", "qcancel", "qmode"]) & filters.private) 175 | async def handle_batch_file(client: Client, message: Message): 176 | user_id = message.from_user.id 177 | 178 | admins = await get_all_admin_ids() 179 | 180 | if user_id not in admins: 181 | return 182 | 183 | try: 184 | from handlers.admin.qupload import is_qupload_active, handle_qupload_file_internal 185 | 186 | if is_qupload_active(user_id): 187 | await handle_qupload_file_internal(client, message, user_id) 188 | return 189 | except ImportError: 190 | pass 191 | 192 | if user_id not in batch_users: 193 | return 194 | 195 | batch_users[user_id]["processing_queue"].append({ 196 | "message": message 197 | }) 198 | 199 | asyncio.create_task(process_file_sequentially(client, user_id)) 200 | 201 | @Client.on_message(filters.command("done") & filters.private) 202 | async def done_command(client: Client, message: Message): 203 | user_id = message.from_user.id 204 | 205 | admins = await get_all_admin_ids() 206 | 207 | if user_id not in admins: 208 | return 209 | 210 | try: 211 | from handlers.admin.qupload import is_qupload_active 212 | if is_qupload_active(user_id): 213 | return 214 | except ImportError: 215 | pass 216 | 217 | if user_id not in batch_users: 218 | await message.reply_text("⚠️ Bᴀᴛᴄʜ ᴍᴏᴅᴇ ɪꜱ ɴᴏᴛ ᴀᴄᴛɪᴠᴇ! Uꜱᴇ /batch ᴛᴏ ꜱᴛᴀʀᴛ.") 219 | return 220 | 221 | status_msg = await message.reply_text("⏳ Wᴀɪᴛɪɴɢ ꜰᴏʀ ᴀʟʟ ꜰɪʟᴇꜱ ᴛᴏ ᴘʀᴏᴄᴇꜱꜱ...") 222 | 223 | while batch_users[user_id]["processing_queue"] or batch_users[user_id]["is_processing"]: 224 | await asyncio.sleep(1) 225 | 226 | if not batch_users[user_id]["files"]: 227 | await status_msg.edit_text("❌ Nᴏ ꜰɪʟᴇꜱ ɪɴ ʙᴀᴛᴄʜ! Sᴇɴᴅ ꜱᴏᴍᴇ ꜰɪʟᴇꜱ ꜰɪʀꜱᴛ.") 228 | return 229 | 230 | try: 231 | await status_msg.edit_text("🔄 Cʀᴇᴀᴛɪɴɢ Bᴀᴛᴄʜ Lɪɴᴋ & Sᴏʀᴛɪɴɢ Eᴘɪꜱᴏᴅᴇꜱ...") 232 | 233 | file_data_list = [] 234 | for file_uuid in batch_users[user_id]["files"]: 235 | file_data = await db.files_collection.find_one({"uuid": file_uuid}) 236 | if file_data: 237 | file_data_list.append(file_data) 238 | 239 | sorted_files = sorted(file_data_list, key=lambda x: extract_episode_number(x.get("file_name", ""))) 240 | 241 | sorted_file_uuids = [file_data["uuid"] for file_data in sorted_files] 242 | 243 | batch_uuid = str(uuid.uuid4()) 244 | batch_data = { 245 | "uuid": batch_uuid, 246 | "files": sorted_file_uuids, 247 | "uploader_id": user_id, 248 | "created_at": datetime.now(pytz.UTC), 249 | "file_count": len(sorted_file_uuids), 250 | "auto_delete": True, 251 | "auto_delete_time": getattr(config, 'DEFAULT_AUTO_DELETE', 30) 252 | } 253 | 254 | await db.batch_collection.insert_one(batch_data) 255 | batch_link = f"https://t.me/{config.BOT_USERNAME}?start=batch_{batch_uuid}" 256 | 257 | await status_msg.edit_text( 258 | f"✅ **Bᴀᴛᴄʜ Cʀᴇᴀᴛᴇᴅ Sᴜᴄᴄᴇꜱꜱꜰᴜʟʟʏ**\n\n" 259 | f"📁 **Tᴏᴛᴀʟ Fɪʟᴇꜱ:** {len(sorted_file_uuids)}\n" 260 | f"⏱ **Aᴜᴛᴏ-Dᴇʟᴇᴛᴇ:** {batch_data['auto_delete_time']} ᴍɪɴᴜᴛᴇꜱ\n" 261 | f"🔗 **Bᴀᴛᴄʜ Lɪɴᴋ:** `{batch_link}`" 262 | ) 263 | 264 | del batch_users[user_id] 265 | 266 | except Exception as e: 267 | await status_msg.edit_text( 268 | f"❌ **Bᴀᴛᴄʜ Cʀᴇᴀᴛɪᴏɴ Fᴀɪʟᴇᴅ**\n\n" 269 | f"Eʀʀᴏʀ: {str(e)}" 270 | ) 271 | if user_id in batch_users: 272 | del batch_users[user_id] 273 | 274 | @Client.on_message(filters.command("start") & filters.regex(r"^/start batch_")) 275 | async def handle_batch_start(client: Client, message: Message): 276 | try: 277 | batch_uuid = message.text.split("_")[1] 278 | batch_data = await db.batch_collection.find_one({"uuid": batch_uuid}) 279 | 280 | if not batch_data: 281 | await message.reply_text("❌ Bᴀᴛᴄʜ ɴᴏᴛ ꜰᴏᴜɴᴅ ᴏʀ ᴇxᴘɪʀᴇᴅ!") 282 | return 283 | 284 | status_msg = await message.reply_text("🔄 Sᴇɴᴅɪɴɢ ꜰɪʟᴇꜱ...") 285 | 286 | sent_count = 0 287 | for file_uuid in batch_data["files"]: 288 | file_data = await db.files_collection.find_one({"uuid": file_uuid}) 289 | if file_data: 290 | try: 291 | max_retries = 3 292 | retry_count = 0 293 | 294 | while retry_count < max_retries: 295 | try: 296 | await client.copy_message( 297 | chat_id=message.chat.id, 298 | from_chat_id=config.DB_CHANNEL_ID, 299 | message_id=file_data["message_id"] 300 | ) 301 | sent_count += 1 302 | break 303 | except FloodWait as e: 304 | wait_time = e.value 305 | await status_msg.edit_text( 306 | f"⏳ Fʟᴏᴏᴅᴡᴀɪᴛ {wait_time} ꜱᴇᴄᴏɴᴅꜱ. Rᴇꜱᴜᴍɪɴɢ...\n\n" 307 | f"Sᴇɴᴛ: {sent_count}/{len(batch_data['files'])}" 308 | ) 309 | await asyncio.sleep(wait_time) 310 | retry_count += 1 311 | except Exception as e: 312 | if retry_count >= max_retries - 1: 313 | raise 314 | await asyncio.sleep(2) 315 | retry_count += 1 316 | 317 | await asyncio.sleep(0.3) 318 | 319 | except Exception as e: 320 | await message.reply_text(f"❌ Eʀʀᴏʀ ꜱᴇɴᴅɪɴɢ: {file_data['file_name']}") 321 | 322 | await status_msg.edit_text(f"✅ Aʟʟ ꜰɪʟᴇꜱ ꜱᴇɴᴛ ꜱᴜᴄᴄᴇꜱꜱꜰᴜʟʟʏ! ({sent_count}/{len(batch_data['files'])})") 323 | 324 | except Exception as e: 325 | await message.reply_text( 326 | f"❌ **Dᴏᴡɴʟᴏᴀᴅ Fᴀɪʟᴇᴅ**\n\n" 327 | f"Eʀʀᴏʀ: {str(e)}" 328 | ) 329 | 330 | @Client.on_message(filters.command("cancel") & filters.private) 331 | async def cancel_command(client: Client, message: Message): 332 | user_id = message.from_user.id 333 | 334 | admins = await get_all_admin_ids() 335 | 336 | if user_id not in admins: 337 | return 338 | 339 | if user_id in batch_users: 340 | del batch_users[user_id] 341 | await message.reply_text("❌ Bᴀᴛᴄʜ ᴍᴏᴅᴇ ᴄᴀɴᴄᴇʟʟᴇᴅ!") 342 | else: 343 | await message.reply_text("⚠️ Bᴀᴛᴄʜ ᴍᴏᴅᴇ ɪꜱ ɴᴏᴛ ᴀᴄᴛɪᴠᴇ!") 344 | -------------------------------------------------------------------------------- /handlers/admin/qupload.py: -------------------------------------------------------------------------------- 1 | # © @TheAlphaBotz [2021-2025] 2 | from pyrogram import Client, filters 3 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery 4 | from pyrogram.errors import FloodWait 5 | from database import Database 6 | from utils import humanbytes 7 | import config 8 | import uuid 9 | from datetime import datetime 10 | import pytz 11 | from handlers.admin.manage_admin import get_all_admin_ids 12 | import asyncio 13 | import re 14 | 15 | db = Database() 16 | qupload_users = {} 17 | qupload_modes = {} 18 | 19 | def extract_quality(text, source="filename"): 20 | if not text: 21 | return "Unknown" 22 | 23 | text_lower = text.lower() 24 | 25 | quality_patterns = [ 26 | (r'\b(2160p|4k)\b', '2160p'), 27 | (r'\b1440p\b', '1440p'), 28 | (r'\b1080p\b', '1080p'), 29 | (r'\b720p\b', '720p'), 30 | (r'\b480p\b', '480p'), 31 | (r'\b360p\b', '360p'), 32 | (r'\b240p\b', '240p'), 33 | (r'\b(hd[\s\-_.]?rip|hdrip)\b', 'HDrip'), 34 | (r'\b(web[\s\-_.]?rip|webrip)\b', 'WEBrip'), 35 | (r'\b(web[\s\-_.]?dl|webdl|web-dl)\b', 'WEB-DL'), 36 | (r'\b(blu[\s\-_.]?ray|bluray|blu-ray)\b', 'BluRay'), 37 | (r'\b(dvd[\s\-_.]?rip|dvdrip)\b', 'DVDrip'), 38 | (r'\b(hdtv)\b', 'HDTV'), 39 | (r'\b(cam)\b', 'CAM'), 40 | (r'\b(ts|telesync)\b', 'TS'), 41 | ] 42 | 43 | for pattern, quality_name in quality_patterns: 44 | if re.search(pattern, text_lower): 45 | return quality_name 46 | 47 | return "Unknown" 48 | 49 | @Client.on_message(filters.command("qmode") & filters.private) 50 | async def qmode_command(client: Client, message: Message): 51 | from_user_id = message.from_user.id 52 | 53 | admins = await get_all_admin_ids() 54 | 55 | if from_user_id not in admins: 56 | return await message.reply_text("__Yᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀᴜᴛʜᴏʀɪᴢᴇᴅ ᴛᴏ ᴜꜱᴇ Qᴜᴀʟɪᴛʏ Mᴏᴅᴇ ꜱᴇᴛᴛɪɴɢꜱ!__") 57 | 58 | current_mode = qupload_modes.get(from_user_id, "filename") 59 | 60 | keyboard = InlineKeyboardMarkup([ 61 | [ 62 | InlineKeyboardButton( 63 | "✅ Fɪʟᴇɴᴀᴍᴇ" if current_mode == "filename" else "Fɪʟᴇɴᴀᴍᴇ", 64 | callback_data="qmode_filename" 65 | ), 66 | InlineKeyboardButton( 67 | "✅ Cᴀᴘᴛɪᴏɴ" if current_mode == "caption" else "Cᴀᴘᴛɪᴏɴ", 68 | callback_data="qmode_caption" 69 | ) 70 | ] 71 | ]) 72 | 73 | await message.reply_text( 74 | "🎬 **Qᴜᴀʟɪᴛʏ Exᴛʀᴀᴄᴛɪᴏɴ Mᴏᴅᴇ**\n\n" 75 | f"**Cᴜʀʀᴇɴᴛ Mᴏᴅᴇ:** `{current_mode.title()}`\n\n" 76 | "**Fɪʟᴇɴᴀᴍᴇ:** Exᴛʀᴀᴄᴛ ǫᴜᴀʟɪᴛʏ ꜰʀᴏᴍ ꜰɪʟᴇ ɴᴀᴍᴇ\n" 77 | "**Cᴀᴘᴛɪᴏɴ:** Exᴛʀᴀᴄᴛ ǫᴜᴀʟɪᴛʏ ꜰʀᴏᴍ ꜰɪʟᴇ ᴄᴀᴘᴛɪᴏɴ\n\n" 78 | "Sᴇʟᴇᴄᴛ ʏᴏᴜʀ ᴘʀᴇꜰᴇʀʀᴇᴅ ᴍᴏᴅᴇ:", 79 | reply_markup=keyboard 80 | ) 81 | 82 | @Client.on_callback_query(filters.regex(r"^qmode_")) 83 | async def qmode_callback(client: Client, callback_query: CallbackQuery): 84 | user_id = callback_query.from_user.id 85 | 86 | admins = await get_all_admin_ids() 87 | if user_id not in admins: 88 | await callback_query.answer("Yᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀᴜᴛʜᴏʀɪᴢᴇᴅ!", show_alert=True) 89 | return 90 | 91 | mode = callback_query.data.split("_")[1] 92 | qupload_modes[user_id] = mode 93 | 94 | keyboard = InlineKeyboardMarkup([ 95 | [ 96 | InlineKeyboardButton( 97 | "✅ Fɪʟᴇɴᴀᴍᴇ" if mode == "filename" else "Fɪʟᴇɴᴀᴍᴇ", 98 | callback_data="qmode_filename" 99 | ), 100 | InlineKeyboardButton( 101 | "✅ Cᴀᴘᴛɪᴏɴ" if mode == "caption" else "Cᴀᴘᴛɪᴏɴ", 102 | callback_data="qmode_caption" 103 | ) 104 | ] 105 | ]) 106 | 107 | await callback_query.edit_message_text( 108 | "🎬 **Qᴜᴀʟɪᴛʏ Exᴛʀᴀᴄᴛɪᴏɴ Mᴏᴅᴇ**\n\n" 109 | f"**Cᴜʀʀᴇɴᴛ Mᴏᴅᴇ:** `{mode.title()}`\n\n" 110 | "**Fɪʟᴇɴᴀᴍᴇ:** Exᴛʀᴀᴄᴛ ǫᴜᴀʟɪᴛʏ ꜰʀᴏᴍ ꜰɪʟᴇ ɴᴀᴍᴇ\n" 111 | "**Cᴀᴘᴛɪᴏɴ:** Exᴛʀᴀᴄᴛ ǫᴜᴀʟɪᴛʏ ꜰʀᴏᴍ ꜰɪʟᴇ ᴄᴀᴘᴛɪᴏɴ\n\n" 112 | "Sᴇʟᴇᴄᴛ ʏᴏᴜʀ ᴘʀᴇꜰᴇʀʀᴇᴅ ᴍᴏᴅᴇ:", 113 | reply_markup=keyboard 114 | ) 115 | 116 | await callback_query.answer(f"✅ Mᴏᴅᴇ ꜱᴇᴛ ᴛᴏ: {mode.title()}") 117 | 118 | @Client.on_message(filters.command(["qu", "qupload"]) & filters.private) 119 | async def qupload_command(client: Client, message: Message): 120 | from_user_id = message.from_user.id 121 | 122 | admins = await get_all_admin_ids() 123 | 124 | if from_user_id not in admins: 125 | return await message.reply_text("__Yᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀᴜᴛʜᴏʀɪᴢᴇᴅ ᴛᴏ ᴜꜱᴇ Qᴜᴀʟɪᴛʏ Uᴘʟᴏᴀᴅ ᴍᴏᴅᴇ!__") 126 | 127 | user_id = message.from_user.id 128 | qupload_users[user_id] = { 129 | "files": {}, 130 | "processing_queue": [], 131 | "is_processing": False, 132 | "processed_count": 0 133 | } 134 | 135 | mode = qupload_modes.get(user_id, "filename") 136 | 137 | await message.reply_text( 138 | "🎬 **Qᴜᴀʟɪᴛʏ Uᴘʟᴏᴀᴅ Mᴏᴅᴇ Aᴄᴛɪᴠᴀᴛᴇᴅ!**\n\n" 139 | f"📌 **Exᴛʀᴀᴄᴛɪᴏɴ Mᴏᴅᴇ:** `{mode.title()}`\n" 140 | "• Sᴇɴᴅ ꜰɪʟᴇꜱ ᴡɪᴛʜ ǫᴜᴀʟɪᴛʏ ɪɴ ɴᴀᴍᴇ/ᴄᴀᴘᴛɪᴏɴ\n" 141 | "• Fɪʟᴇꜱ ᴡɪʟʟ ʙᴇ ɢʀᴏᴜᴘᴇᴅ ʙʏ ǫᴜᴀʟɪᴛʏ\n" 142 | "• Uꜱᴇ /qdone ᴏʀ /qud ᴛᴏ ɢᴇᴛ ʟɪɴᴋꜱ\n" 143 | "• Uꜱᴇ /qcancel ᴛᴏ ᴄᴀɴᴄᴇʟ\n" 144 | "• Uꜱᴇ /qmode ᴛᴏ ᴄʜᴀɴɢᴇ ᴍᴏᴅᴇ" 145 | ) 146 | 147 | async def process_qupload_file(client: Client, user_id: int): 148 | if qupload_users[user_id]["is_processing"]: 149 | return 150 | 151 | qupload_users[user_id]["is_processing"] = True 152 | 153 | while qupload_users[user_id]["processing_queue"]: 154 | message_data = qupload_users[user_id]["processing_queue"].pop(0) 155 | message = message_data["message"] 156 | 157 | try: 158 | max_retries = 3 159 | retry_count = 0 160 | copied_msg = None 161 | 162 | while retry_count < max_retries: 163 | try: 164 | copied_msg = await message.copy(config.DB_CHANNEL_ID) 165 | break 166 | except FloodWait as e: 167 | retry_count += 1 168 | wait_time = e.value 169 | await message.reply_text(f"⏳ Fʟᴏᴏᴅᴡᴀɪᴛ {wait_time} ꜱᴇᴄᴏɴᴅꜱ. Rᴇꜱᴜᴍɪɴɢ...") 170 | await asyncio.sleep(wait_time) 171 | except Exception as e: 172 | if retry_count >= max_retries - 1: 173 | raise 174 | await asyncio.sleep(2) 175 | 176 | if not copied_msg: 177 | await message.reply_text("❌ Fᴀɪʟᴇᴅ ᴛᴏ ᴘʀᴏᴄᴇꜱꜱ ꜰɪʟᴇ ᴀꜰᴛᴇʀ ʀᴇᴛʀɪᴇꜱ") 178 | continue 179 | 180 | file_data = { 181 | "file_id": None, 182 | "file_name": "Unknown", 183 | "file_size": 0, 184 | "file_type": None, 185 | "uuid": str(uuid.uuid4()), 186 | "uploader_id": user_id, 187 | "message_id": copied_msg.id, 188 | "auto_delete": True, 189 | "auto_delete_time": getattr(config, 'DEFAULT_AUTO_DELETE', 30), 190 | } 191 | 192 | if message.document: 193 | file_data.update({ 194 | "file_id": message.document.file_id, 195 | "file_name": message.document.file_name or "document", 196 | "file_size": message.document.file_size, 197 | "file_type": "document" 198 | }) 199 | elif message.video: 200 | file_data.update({ 201 | "file_id": message.video.file_id, 202 | "file_name": message.video.file_name or "video.mp4", 203 | "file_size": message.video.file_size, 204 | "file_type": "video" 205 | }) 206 | elif message.audio: 207 | file_data.update({ 208 | "file_id": message.audio.file_id, 209 | "file_name": message.audio.file_name or "audio", 210 | "file_size": message.audio.file_size, 211 | "file_type": "audio" 212 | }) 213 | elif message.photo: 214 | file_data.update({ 215 | "file_id": message.photo.file_id, 216 | "file_name": f"photo_{file_data['uuid']}.jpg", 217 | "file_size": message.photo.file_size, 218 | "file_type": "photo" 219 | }) 220 | else: 221 | await message.reply_text("❌ Uɴꜱᴜᴘᴘᴏʀᴛᴇᴅ ꜰɪʟᴇ ᴛʏᴘᴇ!") 222 | continue 223 | 224 | if not file_data["file_id"]: 225 | await message.reply_text("❌ Cᴏᴜʟᴅ ɴᴏᴛ ᴘʀᴏᴄᴇꜱꜱ ꜰɪʟᴇ!") 226 | continue 227 | 228 | if file_data["file_size"] > config.MAX_FILE_SIZE: 229 | await message.reply_text(f"❌ Fɪʟᴇ ᴛᴏᴏ ʟᴀʀɢᴇ!\nMᴀxɪᴍᴜᴍ ꜱɪᴢᴇ: {humanbytes(config.MAX_FILE_SIZE)}") 230 | continue 231 | 232 | mode = qupload_modes.get(user_id, "filename") 233 | 234 | if mode == "caption" and message.caption: 235 | quality = extract_quality(message.caption, "caption") 236 | else: 237 | quality = extract_quality(file_data["file_name"], "filename") 238 | 239 | file_data["quality"] = quality 240 | 241 | file_uuid = await db.add_file(file_data) 242 | 243 | if quality not in qupload_users[user_id]["files"]: 244 | qupload_users[user_id]["files"][quality] = [] 245 | 246 | qupload_users[user_id]["files"][quality].append(file_uuid) 247 | qupload_users[user_id]["processed_count"] += 1 248 | 249 | await message.reply_text( 250 | f"✅ **Fɪʟᴇ Aᴅᴅᴇᴅ** ({qupload_users[user_id]['processed_count']})\n" 251 | f"🎬 **Qᴜᴀʟɪᴛʏ:** {quality}" 252 | ) 253 | 254 | except FloodWait as e: 255 | qupload_users[user_id]["processing_queue"].insert(0, message_data) 256 | wait_time = e.value 257 | await message.reply_text(f"⏳ Fʟᴏᴏᴅᴡᴀɪᴛ {wait_time} ꜱᴇᴄᴏɴᴅꜱ. Rᴇꜱᴜᴍɪɴɢ...") 258 | await asyncio.sleep(wait_time) 259 | continue 260 | 261 | except Exception as e: 262 | await message.reply_text( 263 | f"❌ Pʀᴏᴄᴇꜱꜱɪɴɢ Fᴀɪʟᴇᴅ\n\n" 264 | f"Eʀʀᴏʀ: {str(e)}" 265 | ) 266 | 267 | await asyncio.sleep(0.5) 268 | 269 | qupload_users[user_id]["is_processing"] = False 270 | 271 | def is_qupload_active(user_id: int) -> bool: 272 | return user_id in qupload_users 273 | 274 | async def handle_qupload_file_internal(client: Client, message: Message, user_id: int): 275 | qupload_users[user_id]["processing_queue"].append({ 276 | "message": message 277 | }) 278 | asyncio.create_task(process_qupload_file(client, user_id)) 279 | 280 | async def handle_qupload_done_internal(client: Client, message: Message, user_id: int): 281 | await qupload_done_command(client, message) 282 | 283 | @Client.on_message(filters.command(["qdone", "qud"]) & filters.private) 284 | async def qupload_done_command(client: Client, message: Message): 285 | user_id = message.from_user.id 286 | 287 | admins = await get_all_admin_ids() 288 | 289 | if user_id not in admins: 290 | return 291 | 292 | if user_id not in qupload_users: 293 | await message.reply_text("⚠️ Qᴜᴀʟɪᴛʏ Uᴘʟᴏᴀᴅ ᴍᴏᴅᴇ ɪꜱ ɴᴏᴛ ᴀᴄᴛɪᴠᴇ! Uꜱᴇ /qu ᴛᴏ ꜱᴛᴀʀᴛ.") 294 | return 295 | 296 | status_msg = await message.reply_text("⏳ Wᴀɪᴛɪɴɢ ꜰᴏʀ ᴀʟʟ ꜰɪʟᴇꜱ ᴛᴏ ᴘʀᴏᴄᴇꜱꜱ...") 297 | 298 | while qupload_users[user_id]["processing_queue"] or qupload_users[user_id]["is_processing"]: 299 | await asyncio.sleep(1) 300 | 301 | if not qupload_users[user_id]["files"]: 302 | await status_msg.edit_text("❌ Nᴏ ꜰɪʟᴇꜱ ɪɴ ǫᴜᴀʟɪᴛʏ ᴜᴘʟᴏᴀᴅ! Sᴇɴᴅ ꜱᴏᴍᴇ ꜰɪʟᴇꜱ ꜰɪʀꜱᴛ.") 303 | del qupload_users[user_id] 304 | return 305 | 306 | try: 307 | await status_msg.edit_text("🔄 Gᴇɴᴇʀᴀᴛɪɴɢ Qᴜᴀʟɪᴛʏ-Bᴀꜱᴇᴅ Lɪɴᴋꜱ...") 308 | 309 | quality_order = { 310 | '2160p': 0, '4k': 0, 311 | '1440p': 1, 312 | '1080p': 2, 313 | '720p': 3, 314 | '480p': 4, 315 | '360p': 5, 316 | '240p': 6, 317 | 'BluRay': 7, 318 | 'WEB-DL': 8, 319 | 'WEBrip': 9, 320 | 'HDrip': 10, 321 | 'HDTV': 11, 322 | 'DVDrip': 12, 323 | 'TS': 13, 324 | 'CAM': 14, 325 | 'Unknown': 15 326 | } 327 | 328 | sorted_qualities = sorted( 329 | qupload_users[user_id]["files"].keys(), 330 | key=lambda x: quality_order.get(x, 99) 331 | ) 332 | 333 | quality_links = {} 334 | total_files = 0 335 | 336 | for quality in sorted_qualities: 337 | file_uuids = qupload_users[user_id]["files"][quality] 338 | total_files += len(file_uuids) 339 | 340 | if len(file_uuids) == 1: 341 | file_uuid = file_uuids[0] 342 | link = f"https://t.me/{config.BOT_USERNAME}?start={file_uuid}" 343 | quality_links[quality] = link 344 | else: 345 | batch_uuid = str(uuid.uuid4()) 346 | batch_data = { 347 | "uuid": batch_uuid, 348 | "files": file_uuids, 349 | "uploader_id": user_id, 350 | "created_at": datetime.now(pytz.UTC), 351 | "file_count": len(file_uuids), 352 | "auto_delete": True, 353 | "auto_delete_time": getattr(config, 'DEFAULT_AUTO_DELETE', 30), 354 | "quality": quality 355 | } 356 | await db.batch_collection.insert_one(batch_data) 357 | 358 | link = f"https://t.me/{config.BOT_USERNAME}?start=batch_{batch_uuid}" 359 | quality_links[quality] = link 360 | 361 | resolution_qualities = ['240p', '360p', '480p', '720p', '1080p', '1440p', '2160p', '4k'] 362 | rip_qualities = ['BluRay', 'WEB-DL', 'WEBrip', 'HDrip', 'HDTV', 'DVDrip', 'TS', 'CAM', 'Unknown'] 363 | 364 | lines = [] 365 | 366 | res_parts = [] 367 | for quality in resolution_qualities: 368 | if quality in quality_links: 369 | res_parts.append(f"{quality} - `{quality_links[quality]}`") 370 | 371 | if res_parts: 372 | lines.append(" | ".join(res_parts)) 373 | 374 | for quality in rip_qualities: 375 | if quality in quality_links: 376 | lines.append(f"{quality} - `{quality_links[quality]}`") 377 | 378 | links_text = "\n".join(lines) 379 | 380 | response_text = ( 381 | f"✅ **Qᴜᴀʟɪᴛʏ Uᴘʟᴏᴀᴅ Cᴏᴍᴘʟᴇᴛᴇᴅ**\n\n" 382 | f"📁 **Tᴏᴛᴀʟ Fɪʟᴇꜱ:** {total_files}\n" 383 | f"🎬 **Qᴜᴀʟɪᴛɪᴇꜱ:** {len(sorted_qualities)}\n\n" 384 | f"{links_text}" 385 | ) 386 | 387 | await status_msg.edit_text(response_text) 388 | 389 | del qupload_users[user_id] 390 | 391 | except Exception as e: 392 | await status_msg.edit_text( 393 | f"❌ **Qᴜᴀʟɪᴛʏ Uᴘʟᴏᴀᴅ Fᴀɪʟᴇᴅ**\n\n" 394 | f"Eʀʀᴏʀ: {str(e)}" 395 | ) 396 | if user_id in qupload_users: 397 | del qupload_users[user_id] 398 | 399 | @Client.on_message(filters.command("qcancel") & filters.private) 400 | async def qcancel_command(client: Client, message: Message): 401 | user_id = message.from_user.id 402 | 403 | admins = await get_all_admin_ids() 404 | 405 | if user_id not in admins: 406 | return 407 | 408 | if user_id in qupload_users: 409 | del qupload_users[user_id] 410 | await message.reply_text("❌ Qᴜᴀʟɪᴛʏ Uᴘʟᴏᴀᴅ ᴍᴏᴅᴇ ᴄᴀɴᴄᴇʟʟᴇᴅ!") 411 | else: 412 | await message.reply_text("⚠️ Qᴜᴀʟɪᴛʏ Uᴘʟᴏᴀᴅ ᴍᴏᴅᴇ ɪꜱ ɴᴏᴛ ᴀᴄᴛɪᴠᴇ!") 413 | -------------------------------------------------------------------------------- /handlers/user/start.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2021-2025 @thealphabotz - All Rights Reserved. 3 | 4 | from pyrogram import Client, filters 5 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 6 | from pyrogram.errors import FloodWait 7 | from database import Database 8 | from utils import ButtonManager 9 | import config 10 | import asyncio 11 | import logging 12 | import base64 13 | from ..utils.message_delete import schedule_message_deletion 14 | 15 | logger = logging.getLogger(__name__) 16 | db = Database() 17 | button_manager = ButtonManager() 18 | 19 | active_batch_users = set() 20 | 21 | class Emoji: 22 | WARNING = "\u26A0\uFE0F" 23 | SUCCESS = "\u2705" 24 | ERROR = "\u274C" 25 | LOADING = "\U0001F504" 26 | BOX = "\U0001F4E6" 27 | HOURGLASS = "\u231B" 28 | INBOX = "\U0001F4E5" 29 | ARROW = "\u279C" 30 | BULLET = "\u2022" 31 | 32 | async def decode_codex_link(encoded_string: str) -> tuple: 33 | try: 34 | padding_needed = len(encoded_string) % 4 35 | if padding_needed: 36 | encoded_string += '=' * (4 - padding_needed) 37 | 38 | try: 39 | string_bytes = base64.b64decode(encoded_string.encode("ascii")) 40 | except Exception: 41 | encoded_string += '=' * (4 - (len(encoded_string) % 4)) 42 | string_bytes = base64.b64decode(encoded_string.encode("ascii")) 43 | 44 | decoded = string_bytes.decode("ascii") 45 | if decoded.startswith("get-"): 46 | parts = decoded.split("-") 47 | if len(parts) == 2: 48 | msg_id = int(parts[1]) // abs(config.DB_CHANNEL_ID) 49 | return False, [msg_id] 50 | elif len(parts) == 3: 51 | first_id = int(parts[1]) // abs(config.DB_CHANNEL_ID) 52 | last_id = int(parts[2]) // abs(config.DB_CHANNEL_ID) 53 | return True, list(range(first_id, last_id + 1)) 54 | return False, [] 55 | except Exception as e: 56 | logger.error(f"Error decoding CodeXBotz link: {str(e)}") 57 | return False, [] 58 | 59 | async def send_batch_files(client: Client, message: Message, message_ids: list, is_codex: bool = False): 60 | user_id = message.from_user.id 61 | 62 | if user_id in active_batch_users: 63 | await message.reply_text( 64 | f"{Emoji.WARNING} **Pʟᴇᴀꜱᴇ Wᴀɪᴛ!**\n\n" 65 | f"Yᴏᴜ ᴀʟʀᴇᴀᴅʏ ʜᴀᴠᴇ ᴀɴ ᴏɴɢᴏɪɴɢ ʙᴀᴛᴄʜ ᴘʀᴏᴄᴇꜱꜱ.\n" 66 | f"Pʟᴇᴀꜱᴇ ᴡᴀɪᴛ ꜰᴏʀ ɪᴛ ᴛᴏ ᴄᴏᴍᴘʟᴇᴛᴇ ᴀɴᴅ ᴛʀʏ ᴀɢᴀɪɴ.", 67 | protect_content=config.PRIVACY_MODE 68 | ) 69 | return 70 | 71 | active_batch_users.add(user_id) 72 | 73 | try: 74 | status_msg = await message.reply_text( 75 | f"{Emoji.LOADING} **Pʀᴏᴄᴇꜱꜱɪɴɢ...**", 76 | protect_content=config.PRIVACY_MODE 77 | ) 78 | 79 | success_count = 0 80 | failed_count = 0 81 | sent_msgs = [] 82 | first_file_sent = False 83 | 84 | for idx, msg_id in enumerate(message_ids, 1): 85 | try: 86 | max_retries = 3 87 | retry_count = 0 88 | 89 | while retry_count < max_retries: 90 | try: 91 | msg = await client.copy_message( 92 | chat_id=message.chat.id, 93 | from_chat_id=config.DB_CHANNEL_ID, 94 | message_id=msg_id, 95 | protect_content=config.PRIVACY_MODE 96 | ) 97 | 98 | if msg and msg.id: 99 | sent_msgs.append(msg.id) 100 | success_count += 1 101 | 102 | if not first_file_sent: 103 | try: 104 | await status_msg.delete() 105 | except: 106 | pass 107 | first_file_sent = True 108 | 109 | break 110 | 111 | except FloodWait as e: 112 | wait_time = e.value 113 | if first_file_sent: 114 | flood_msg = await message.reply_text( 115 | f"{Emoji.HOURGLASS} **Fʟᴏᴏᴅᴡᴀɪᴛ {wait_time} ꜱᴇᴄᴏɴᴅꜱ. Rᴇꜱᴜᴍɪɴɢ...**\n\n" 116 | f"Sᴇɴᴛ: {success_count}/{len(message_ids)}", 117 | protect_content=config.PRIVACY_MODE 118 | ) 119 | else: 120 | try: 121 | await status_msg.edit_text( 122 | f"{Emoji.HOURGLASS} **Fʟᴏᴏᴅᴡᴀɪᴛ {wait_time} ꜱᴇᴄᴏɴᴅꜱ. Rᴇꜱᴜᴍɪɴɢ...**", 123 | protect_content=config.PRIVACY_MODE 124 | ) 125 | except: 126 | pass 127 | 128 | await asyncio.sleep(wait_time) 129 | retry_count += 1 130 | 131 | if first_file_sent: 132 | try: 133 | await flood_msg.delete() 134 | except: 135 | pass 136 | 137 | except Exception as e: 138 | if retry_count >= max_retries - 1: 139 | raise 140 | await asyncio.sleep(2) 141 | retry_count += 1 142 | 143 | except Exception as e: 144 | failed_count += 1 145 | logger.error(f"Batch file send error: {str(e)}") 146 | continue 147 | 148 | if success_count > 0 and config.AUTO_DELETE_TIME: 149 | delete_time = config.AUTO_DELETE_TIME 150 | info_msg = await client.send_message( 151 | chat_id=message.chat.id, 152 | text=f"{Emoji.HOURGLASS} **Aᴜᴛᴏ Dᴇʟᴇᴛᴇ Iɴꜰᴏʀᴍᴀᴛɪᴏɴ**\n\n" 153 | f"{Emoji.ARROW} Fɪʟᴇꜱ ᴡɪʟʟ ʙᴇ ᴅᴇʟᴇᴛᴇᴅ ɪɴ {delete_time} ᴍɪɴᴜᴛᴇꜱ.\n" 154 | f"{Emoji.ARROW} Fᴏʀᴡᴀʀᴅ ᴛᴏ ꜱᴀᴠᴇ ᴘᴇʀᴍᴀɴᴇɴᴛʟʏ.", 155 | protect_content=config.PRIVACY_MODE 156 | ) 157 | if info_msg and info_msg.id: 158 | sent_msgs.append(info_msg.id) 159 | asyncio.create_task(schedule_message_deletion( 160 | client, message.chat.id, sent_msgs, delete_time 161 | )) 162 | 163 | if not first_file_sent: 164 | try: 165 | await status_msg.edit_text( 166 | f"{Emoji.ERROR} **Nᴏ Fɪʟᴇꜱ Fᴏᴜɴᴅ**\n\n" 167 | f"Aʟʟ ꜰɪʟᴇꜱ ᴍᴀʏ ʜᴀᴠᴇ ʙᴇᴇɴ ᴅᴇʟᴇᴛᴇᴅ ᴏʀ ᴀʀᴇ ᴜɴᴀᴠᴀɪʟᴀʙʟᴇ." 168 | ) 169 | except: 170 | pass 171 | elif failed_count > 0: 172 | summary_msg = await message.reply_text( 173 | f"{Emoji.SUCCESS} **Sᴇɴᴛ:** {success_count} | {Emoji.ERROR} **Fᴀɪʟᴇᴅ:** {failed_count}", 174 | protect_content=config.PRIVACY_MODE 175 | ) 176 | 177 | finally: 178 | active_batch_users.discard(user_id) 179 | 180 | @Client.on_message(filters.command("start")) 181 | async def start_command(client: Client, message: Message): 182 | try: 183 | await db.add_user(message.from_user.id, message.from_user.mention) 184 | except Exception as e: 185 | logger.error(f"Error adding user to database: {str(e)}") 186 | 187 | user_method = message.from_user.mention if message.from_user.mention else message.from_user.first_name 188 | user_mention = message.from_user.mention if message.from_user.mention else f"[{message.from_user.first_name}](tg://user?id={message.from_user.id})" 189 | 190 | if len(message.command) > 1: 191 | command = message.command[1] 192 | file_uuid = message.command[1] 193 | 194 | force_sub_status = await button_manager.check_force_sub(client, message.from_user.id) 195 | if not force_sub_status: 196 | force_sub_text = f"**{Emoji.WARNING} Yᴏᴜ ᴍᴜꜱᴛ ᴊᴏɪɴ ᴏᴜʀ ᴄʜᴀɴɴᴇʟ(ꜱ) ᴛᴏ ᴜꜱᴇ ᴛʜɪꜱ ʙᴏᴛ!**\n\n" 197 | channels = [ 198 | (config.FORCE_SUB_CHANNEL, "Join Channel 1"), 199 | (config.FORCE_SUB_CHANNEL_2, "Join Channel 2"), 200 | (config.FORCE_SUB_CHANNEL_3, "Join Channel 3"), 201 | (config.FORCE_SUB_CHANNEL_4, "Join Channel 4") 202 | ] 203 | 204 | for ch_id, name in channels: 205 | if ch_id != 0: 206 | force_sub_text += f"{Emoji.BULLET} {name}\n" 207 | 208 | force_sub_text += "\nJᴏɪɴ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ(ꜱ) ᴀɴᴅ ᴛʀʏ ᴀɢᴀɪɴ." 209 | 210 | await message.reply_text( 211 | force_sub_text, 212 | reply_markup=button_manager.force_sub_button_new(file_uuid), 213 | protect_content=config.PRIVACY_MODE 214 | ) 215 | return 216 | 217 | is_codex_batch, message_ids = await decode_codex_link(command) 218 | 219 | if message_ids: 220 | if is_codex_batch: 221 | await send_batch_files(client, message, message_ids, is_codex=True) 222 | return 223 | else: 224 | try: 225 | msg = await client.copy_message( 226 | chat_id=message.chat.id, 227 | from_chat_id=config.DB_CHANNEL_ID, 228 | message_id=message_ids[0], 229 | protect_content=config.PRIVACY_MODE 230 | ) 231 | if msg and msg.id: 232 | if config.AUTO_DELETE_TIME: 233 | delete_time = config.AUTO_DELETE_TIME 234 | info_msg = await msg.reply_text( 235 | f"{Emoji.HOURGLASS} **Aᴜᴛᴏ Dᴇʟᴇᴛᴇ Iɴꜰᴏʀᴍᴀᴛɪᴏɴ**\n\n" 236 | f"{Emoji.ARROW} Fɪʟᴇ ᴡɪʟʟ ʙᴇ ᴅᴇʟᴇᴛᴇᴅ ɪɴ {delete_time} ᴍɪɴᴜᴛᴇꜱ.\n" 237 | f"{Emoji.ARROW} Fᴏʀᴡᴀʀᴅ ᴛᴏ ꜱᴀᴠᴇ ᴘᴇʀᴍᴀɴᴇɴᴛʟʏ.", 238 | protect_content=config.PRIVACY_MODE 239 | ) 240 | if info_msg and info_msg.id: 241 | asyncio.create_task(schedule_message_deletion( 242 | client, message.chat.id, [msg.id, info_msg.id], delete_time 243 | )) 244 | return 245 | except Exception: 246 | await message.reply_text( 247 | f"{Emoji.ERROR} Fɪʟᴇ ɴᴏᴛ ꜰᴏᴜɴᴅ ᴏʀ ʜᴀꜱ ʙᴇᴇɴ ᴅᴇʟᴇᴛᴇᴅ!", 248 | protect_content=config.PRIVACY_MODE 249 | ) 250 | return 251 | 252 | if command.startswith("batch_"): 253 | user_id = message.from_user.id 254 | 255 | if user_id in active_batch_users: 256 | await message.reply_text( 257 | f"{Emoji.WARNING} **Pʟᴇᴀꜱᴇ Wᴀɪᴛ!**\n\n" 258 | f"Yᴏᴜ ᴀʟʀᴇᴀᴅʏ ʜᴀᴠᴇ ᴀɴ ᴏɴɢᴏɪɴɢ ʙᴀᴛᴄʜ ᴘʀᴏᴄᴇꜱꜱ.\n" 259 | f"Pʟᴇᴀꜱᴇ ᴡᴀɪᴛ ꜰᴏʀ ɪᴛ ᴛᴏ ᴄᴏᴍᴘʟᴇᴛᴇ ᴀɴᴅ ᴛʀʏ ᴀɢᴀɪɴ.", 260 | protect_content=config.PRIVACY_MODE 261 | ) 262 | return 263 | 264 | batch_uuid = command.replace("batch_", "") 265 | batch_data = await db.get_batch(batch_uuid) 266 | 267 | if not batch_data: 268 | await message.reply_text( 269 | f"{Emoji.ERROR} Bᴀᴛᴄʜ ɴᴏᴛ ꜰᴏᴜɴᴅ ᴏʀ ʜᴀꜱ ʙᴇᴇɴ ᴅᴇʟᴇᴛᴇᴅ!", 270 | protect_content=config.PRIVACY_MODE 271 | ) 272 | return 273 | 274 | active_batch_users.add(user_id) 275 | 276 | try: 277 | status_msg = await message.reply_text( 278 | f"{Emoji.LOADING} **Pʀᴏᴄᴇꜱꜱɪɴɢ...**", 279 | protect_content=config.PRIVACY_MODE 280 | ) 281 | 282 | success_count = 0 283 | failed_count = 0 284 | sent_msgs = [] 285 | first_file_sent = False 286 | total_files = len(batch_data["files"]) 287 | 288 | for file_uuid in batch_data["files"]: 289 | file_data = await db.get_file(file_uuid) 290 | if file_data and "message_id" in file_data: 291 | try: 292 | max_retries = 3 293 | retry_count = 0 294 | 295 | while retry_count < max_retries: 296 | try: 297 | msg = await client.copy_message( 298 | chat_id=message.chat.id, 299 | from_chat_id=config.DB_CHANNEL_ID, 300 | message_id=file_data["message_id"], 301 | protect_content=config.PRIVACY_MODE 302 | ) 303 | 304 | if msg and msg.id: 305 | sent_msgs.append(msg.id) 306 | success_count += 1 307 | 308 | if not first_file_sent: 309 | try: 310 | await status_msg.delete() 311 | except: 312 | pass 313 | first_file_sent = True 314 | 315 | break 316 | 317 | except FloodWait as e: 318 | wait_time = e.value 319 | if first_file_sent: 320 | flood_msg = await message.reply_text( 321 | f"{Emoji.HOURGLASS} **Fʟᴏᴏᴅᴡᴀɪᴛ {wait_time} ꜱᴇᴄᴏɴᴅꜱ. Rᴇꜱᴜᴍɪɴɢ...**\n\n" 322 | f"Sᴇɴᴛ: {success_count}/{total_files}", 323 | protect_content=config.PRIVACY_MODE 324 | ) 325 | else: 326 | try: 327 | await status_msg.edit_text( 328 | f"{Emoji.HOURGLASS} **Fʟᴏᴏᴅᴡᴀɪᴛ {wait_time} ꜱᴇᴄᴏɴᴅꜱ. Rᴇꜱᴜᴍɪɴɢ...**", 329 | protect_content=config.PRIVACY_MODE 330 | ) 331 | except: 332 | pass 333 | 334 | await asyncio.sleep(wait_time) 335 | retry_count += 1 336 | 337 | if first_file_sent: 338 | try: 339 | await flood_msg.delete() 340 | except: 341 | pass 342 | 343 | except Exception as e: 344 | if retry_count >= max_retries - 1: 345 | raise 346 | await asyncio.sleep(2) 347 | retry_count += 1 348 | 349 | except Exception as e: 350 | failed_count += 1 351 | logger.error(f"Batch file send error: {str(e)}") 352 | continue 353 | 354 | if success_count > 0: 355 | await db.increment_batch_downloads(batch_uuid) 356 | if config.AUTO_DELETE_TIME: 357 | delete_time = config.AUTO_DELETE_TIME 358 | info_msg = await client.send_message( 359 | chat_id=message.chat.id, 360 | text=f"{Emoji.HOURGLASS} **Aᴜᴛᴏ Dᴇʟᴇᴛᴇ Iɴꜰᴏʀᴍᴀᴛɪᴏɴ**\n\n" 361 | f"{Emoji.ARROW} Fɪʟᴇꜱ ᴡɪʟʟ ʙᴇ ᴅᴇʟᴇᴛᴇᴅ ɪɴ {delete_time} ᴍɪɴᴜᴛᴇꜱ.\n" 362 | f"{Emoji.ARROW} Fᴏʀᴡᴀʀᴅ ᴛᴏ ꜱᴀᴠᴇ ᴘᴇʀᴍᴀɴᴇɴᴛʟʏ.", 363 | protect_content=config.PRIVACY_MODE 364 | ) 365 | if info_msg and info_msg.id: 366 | sent_msgs.append(info_msg.id) 367 | asyncio.create_task(schedule_message_deletion( 368 | client, message.chat.id, sent_msgs, delete_time 369 | )) 370 | 371 | if not first_file_sent: 372 | try: 373 | await status_msg.edit_text( 374 | f"{Emoji.ERROR} **Nᴏ Fɪʟᴇꜱ Fᴏᴜɴᴅ**\n\n" 375 | f"Aʟʟ ꜰɪʟᴇꜱ ᴍᴀʏ ʜᴀᴠᴇ ʙᴇᴇɴ ᴅᴇʟᴇᴛᴇᴅ." 376 | ) 377 | except: 378 | pass 379 | elif failed_count > 0: 380 | await message.reply_text( 381 | f"{Emoji.SUCCESS} **Sᴇɴᴛ:** {success_count} | {Emoji.ERROR} **Fᴀɪʟᴇᴅ:** {failed_count}", 382 | protect_content=config.PRIVACY_MODE 383 | ) 384 | 385 | finally: 386 | active_batch_users.discard(user_id) 387 | 388 | else: 389 | file_uuid = command 390 | file_data = await db.get_file(file_uuid) 391 | 392 | if not file_data: 393 | await message.reply_text( 394 | f"{Emoji.ERROR} Fɪʟᴇ ɴᴏᴛ ꜰᴏᴜɴᴅ ᴏʀ ʜᴀꜱ ʙᴇᴇɴ ᴅᴇʟᴇᴛᴇᴅ!", 395 | protect_content=config.PRIVACY_MODE 396 | ) 397 | return 398 | 399 | try: 400 | msg = await client.copy_message( 401 | chat_id=message.chat.id, 402 | from_chat_id=config.DB_CHANNEL_ID, 403 | message_id=file_data["message_id"], 404 | protect_content=config.PRIVACY_MODE 405 | ) 406 | 407 | if msg and msg.id: 408 | await db.increment_downloads(file_uuid) 409 | if config.AUTO_DELETE_TIME: 410 | delete_time = config.AUTO_DELETE_TIME 411 | info_msg = await msg.reply_text( 412 | f"{Emoji.HOURGLASS} **Aᴜᴛᴏ Dᴇʟᴇᴛᴇ Iɴꜰᴏʀᴍᴀᴛɪᴏɴ**\n\n" 413 | f"{Emoji.ARROW} Fɪʟᴇ ᴡɪʟʟ ʙᴇ ᴅᴇʟᴇᴛᴇᴅ ɪɴ {delete_time} ᴍɪɴᴜᴛᴇꜱ.\n" 414 | f"{Emoji.ARROW} Fᴏʀᴡᴀʀᴅ ᴛᴏ ꜱᴀᴠᴇ ᴘᴇʀᴍᴀɴᴇɴᴛʟʏ.", 415 | protect_content=config.PRIVACY_MODE 416 | ) 417 | if info_msg and info_msg.id: 418 | asyncio.create_task(schedule_message_deletion( 419 | client, message.chat.id, [msg.id, info_msg.id], delete_time 420 | )) 421 | 422 | except Exception as e: 423 | await message.reply_text( 424 | f"{Emoji.ERROR} Eʀʀᴏʀ: {str(e)}", 425 | protect_content=config.PRIVACY_MODE 426 | ) 427 | 428 | else: 429 | buttons = button_manager.start_button() 430 | await message.reply_photo( 431 | photo=config.START_PHOTO, 432 | caption=config.Messages.START_TEXT.format( 433 | bot_name=config.BOT_NAME, 434 | user_method=user_method, 435 | user_mention=user_mention 436 | ), 437 | reply_markup=buttons 438 | ) 439 | --------------------------------------------------------------------------------