├── 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 |

6 |
7 | **A Revolutionary Telegram File Sharing Bot with Quality Upload Mode**
8 |
9 |

10 |

11 |

12 |
13 |

14 |

15 |

16 |
17 |
18 |
19 | ---
20 |
21 | ## 🎯 What's New in V2.1
22 |
23 |
24 |
25 | ### 🚀 MAJOR FEATURE: QUALITY UPLOAD MODE
26 |
27 |

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 |

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 |

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 | |
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 | |
189 |
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 | |
204 |
205 |
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 | [](https://heroku.com/deploy?template=https://github.com/utkarshdubey2008/AlphaShare)
228 | [](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 |
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 |
--------------------------------------------------------------------------------