├── runtime.txt ├── Procfile ├── heroku.yml ├── koyab.yml ├── Dockerfile ├── start.sh ├── docker-compose.yml ├── plugins ├── mntgxo.py ├── webcode.py ├── Extra │ ├── sticker.py │ ├── echo.py │ ├── telegraph.py │ ├── share.py │ ├── shell.py │ ├── eval.py │ ├── password.py │ ├── carbon.py │ ├── tts.py │ ├── tr.py │ ├── paste.py │ ├── pin.py │ ├── feedback.py │ ├── json.py │ ├── promote.py │ ├── short.py │ └── font.py ├── channel.py ├── mnbots.py ├── movies_series.py ├── banned.py ├── etc.py ├── broadcast.py ├── inline.py ├── connection.py ├── mn_deletefiles.py ├── misc.py ├── index.py ├── filters.py ├── p_ttishow.py └── commands.py ├── logging.conf ├── requirements.txt ├── app.json ├── database ├── filters_mdb.py ├── connections_mdb.py ├── users_chats_db.py └── ia_filterdb.py ├── bot.py ├── info.py ├── README.md ├── Script.py ├── utils.py └── LICENSE /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.10.10 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: python3 bot.py 2 | worker: python3 bot.py 3 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | worker: Dockerfile -------------------------------------------------------------------------------- /koyab.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | worker: Dockerfile 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10 2 | WORKDIR /app 3 | COPY . /app/ 4 | RUN pip3 install -r requirements.txt 5 | CMD ["python3", "bot.py"] 6 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | if [ -z $UPSTREAM_REPO ] 2 | then 3 | echo "Cloning main Repository" 4 | git clone https://github.com/mn-bots/shobanafilterbot.git /shobanafilterbot 5 | else 6 | echo "Cloning Custom Repo from $UPSTREAM_REPO " 7 | git clone $UPSTREAM_REPO /shobanafilterbot 8 | fi 9 | cd /shobanafilterbot 10 | pip3 install -U -r requirements.txt 11 | echo "Starting Bot...." 12 | python3 bot.py 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.10" 2 | services: 3 | worker: 4 | build: . 5 | environment: 6 | BOT_TOKEN: $BOT_TOKEN 7 | API_ID: $API_ID 8 | API_HASH: $API_HASH 9 | CHANNELS: $CHANNELS 10 | ADMINS: $ADMINS 11 | LOG_CHANNEL: $LOG_CHANNEL 12 | DATABASE_NAME: $DATABASE_NAME 13 | DATABASE_URI: $DATABASE_URI 14 | HEROKU_API_KEY: $HEROKU_API_KEY 15 | -------------------------------------------------------------------------------- /plugins/mntgxo.py: -------------------------------------------------------------------------------- 1 | # @MrMNTG @MusammilN 2 | from pyrogram import filters, Client 3 | from pyrogram.types import Message 4 | from utils import JOIN_REQUEST_USERS 5 | from info import ADMINS 6 | 7 | @Client.on_message(filters.command("clear_join_users") & filters.user(ADMINS)) 8 | async def clear_join_users(_, message: Message): 9 | JOIN_REQUEST_USERS.clear() 10 | await message.reply_text("✅ Cleared all join request users.") 11 | -------------------------------------------------------------------------------- /plugins/webcode.py: -------------------------------------------------------------------------------- 1 | from aiohttp import web as webserver 2 | 3 | routes = webserver.RouteTableDef() 4 | 5 | async def bot_run(): 6 | _app = webserver.Application(client_max_size=30000000) 7 | _app.add_routes(routes) 8 | return _app 9 | 10 | @routes.get("/", allow_head=True) 11 | async def root_route_handler(request): 12 | return webserver.json_response(" Web Supported . . . ! This is a preview of WeB . . .! ! !") 13 | 14 | -------------------------------------------------------------------------------- /plugins/Extra/sticker.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | 3 | @Client.on_message(filters.command(["stickerid"])) 4 | async def stickerid(bot, message): 5 | if message.reply_to_message.sticker: 6 | await message.reply(f"**Sticker ID is** \n `{message.reply_to_message.sticker.file_id}` \n \n ** Unique ID is ** \n\n`{message.reply_to_message.sticker.file_unique_id}`", quote=True) 7 | else: 8 | await message.reply("Oops !! Not a sticker file") 9 | -------------------------------------------------------------------------------- /plugins/channel.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | from info import CHANNELS 3 | from database.ia_filterdb import save_file 4 | 5 | media_filter = filters.document | filters.video | filters.audio 6 | 7 | 8 | @Client.on_message(filters.chat(CHANNELS) & media_filter) 9 | async def media(bot, message): 10 | """Media Handler""" 11 | for file_type in ("document", "video"): 12 | media = getattr(message, file_type, None) 13 | if media is not None: 14 | break 15 | else: 16 | return 17 | 18 | media.file_type = file_type 19 | media.caption = message.caption 20 | await save_file(media) 21 | -------------------------------------------------------------------------------- /plugins/mnbots.py: -------------------------------------------------------------------------------- 1 | # @MrMNTG @MusammilN 2 | from pyrogram import Client 3 | from pyrogram.types import ChatJoinRequest 4 | from database.users_chats_db import db 5 | from utils import JOIN_REQUEST_USERS 6 | 7 | @Client.on_chat_join_request() 8 | async def join_request_handler(client, update: ChatJoinRequest): 9 | user_id = update.from_user.id 10 | chat_id = update.chat.id 11 | 12 | auth_channels = await db.get_auth_channels() 13 | if chat_id in auth_channels: 14 | if user_id not in JOIN_REQUEST_USERS: 15 | JOIN_REQUEST_USERS[user_id] = set() 16 | JOIN_REQUEST_USERS[user_id].add(chat_id) 17 | 18 | # @MrMNTG @MusammilN 19 | -------------------------------------------------------------------------------- /logging.conf: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root 3 | 4 | [handlers] 5 | keys=consoleHandler,fileHandler 6 | 7 | [formatters] 8 | keys=consoleFormatter,fileFormatter 9 | 10 | [logger_root] 11 | level=DEBUG 12 | handlers=consoleHandler,fileHandler 13 | 14 | [handler_consoleHandler] 15 | class=StreamHandler 16 | level=INFO 17 | formatter=consoleFormatter 18 | args=(sys.stdout,) 19 | 20 | [handler_fileHandler] 21 | class=FileHandler 22 | level=ERROR 23 | formatter=fileFormatter 24 | args=('TelegramBot.txt','w',) 25 | 26 | [formatter_consoleFormatter] 27 | format=%(asctime)s - %(lineno)d - %(name)s - %(module)s - %(levelname)s - %(message)s 28 | datefmt=%I:%M:%S %p 29 | 30 | [formatter_fileFormatter] 31 | format=[%(asctime)s:%(name)s:%(lineno)d:%(levelname)s] %(message)s 32 | datefmt=%m/%d/%Y %I:%M:%S %p 33 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles 2 | aiohttp 3 | apscheduler 4 | bs4 5 | cinemagoer 6 | colorama 7 | countryinfo 8 | datetime 9 | dnspython>=2.3.0 10 | ffmpeg 11 | ffmpeg-python>=0.2.0 12 | Flask 13 | fuzzywuzzy 14 | gTTS>=2.3.0 15 | googletrans>=3.1.0a0 16 | gunicorn>=20.1.0 17 | hachoir 18 | Heroku3 19 | humanize>=4.6.0 20 | httpx[http2] 21 | itsdangerous>=2.0.0 22 | jinja2 23 | NumPy 24 | openai>=0.25.0 25 | opencv-python-headless>=4.7.0.68 26 | Pillow>=9.4.0 27 | psutil>=5.9.0 28 | pycryptodome 29 | pygments 30 | pyromod 31 | pymongo==3.12.3 32 | motor==2.5.1 33 | marshmallow==3.20.1 34 | umongo==3.0.1 35 | PyPDF2 36 | python-decouple 37 | python-dotenv>=0.21.0 38 | python-Levenshtein 39 | pyshorteners 40 | requests 41 | search_engine_parser 42 | shortzy 43 | speedtest>=0.0.1 44 | speedtest-cli 45 | telegraph 46 | tgcrypto 47 | pyrotgfork 48 | ujson 49 | wget 50 | werkzeug>=2.0 51 | wheel 52 | yt-dlp 53 | youtube-dl 54 | youtube-search 55 | youtube-search-python>=1.4.6 56 | ytthumb 57 | git+https://github.com/agronholm/anyio 58 | git+https://github.com/Joelkb/cinemagoer 59 | 60 | -------------------------------------------------------------------------------- /plugins/movies_series.py: -------------------------------------------------------------------------------- 1 | #please give credits https://github.com/MN-BOTS 2 | from pyrogram.enums import ParseMode 3 | from pyrogram import Client, filters 4 | from pyrogram.types import Message 5 | from database.ia_filterdb import get_movie_list, get_series_grouped 6 | 7 | @Client.on_message(filters.private & filters.command("movies")) 8 | async def list_movies(bot: Client, message: Message): 9 | movies = await get_movie_list() 10 | if not movies: 11 | return await message.reply("❌ No recent movies found.") 12 | 13 | msg = "🎬 Latest Movies:\n\n" 14 | msg += "\n".join(f"✅ {m}" for m in movies) 15 | await message.reply(msg[:4096], parse_mode=ParseMode.HTML) 16 | 17 | @Client.on_message(filters.private & filters.command("series")) 18 | async def list_series(bot: Client, message: Message): 19 | series_data = await get_series_grouped() 20 | if not series_data: 21 | return await message.reply("❌ No recent series episodes found.") 22 | 23 | msg = "📺 Latest Series:\n\n" 24 | for title, episodes in series_data.items(): 25 | ep_list = ", ".join(str(e) for e in episodes) 26 | msg += f"✅ {title} - Episodes {ep_list}\n" 27 | 28 | await message.reply(msg[:4096], parse_mode=ParseMode.HTML) 29 | -------------------------------------------------------------------------------- /plugins/Extra/echo.py: -------------------------------------------------------------------------------- 1 | 2 | from pyrogram import Client, filters, enums 3 | from pyrogram.types import * 4 | 5 | @Client.on_message(filters.command("echo") & filters.group) 6 | async def echo(client, message): 7 | try: 8 | # Check user permissions 9 | user = await client.get_chat_member(message.chat.id, message.from_user.id) 10 | if user.status not in [enums.ChatMemberStatus.OWNER, enums.ChatMemberStatus.ADMINISTRATOR]: 11 | await message.reply_text("You don't have permission to use this command .") 12 | return 13 | except Exception as error: 14 | # Handle case where bot lacks permissions to get member info 15 | await message.reply_text(f"An error occurred. I may not have permission to check user status. {error}") 16 | return 17 | 18 | reply = message.reply_to_message 19 | chat_id = message.chat.id 20 | 21 | if not reply: 22 | # No message replied to 23 | await message.reply_text("Please reply to a message to echo its content.") 24 | return 25 | 26 | await reply.reply_text(message.text.split(None, 1)[1]) 27 | await message.delete() 28 | 29 | 30 | @Client.on_message(filters.command("echo") & filters.private) 31 | async def echoptp(client, message): 32 | await message.reply_text("Sorry dude This command Only work in group 😊") 33 | -------------------------------------------------------------------------------- /plugins/Extra/telegraph.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | from pyrogram import Client, filters 4 | from pyrogram.types import Message 5 | 6 | @Client.on_message(filters.command(["img", "cup", "telegraph"], prefixes="/") & filters.reply) 7 | async def c_upload(client, message: Message): 8 | reply = message.reply_to_message 9 | 10 | if not reply.media: 11 | return await message.reply_text("Reply to a media to upload it to Cloud.") 12 | 13 | if reply.document and reply.document.file_size > 512 * 1024 * 1024: # 512 MB 14 | return await message.reply_text("File size limit is 512 MB.") 15 | 16 | msg = await message.reply_text("Processing...") 17 | 18 | try: 19 | downloaded_media = await reply.download() 20 | 21 | if not downloaded_media: 22 | return await msg.edit_text("Something went wrong during download.") 23 | 24 | with open(downloaded_media, "rb") as f: 25 | data = f.read() 26 | resp = requests.post("https://envs.sh", files={"file": data}) 27 | if resp.status_code == 200: 28 | await msg.edit_text(f"`{resp.text}`") 29 | else: 30 | await msg.edit_text("Something went wrong. Please try again later.") 31 | 32 | os.remove(downloaded_media) 33 | 34 | except Exception as e: 35 | await msg.edit_text(f"Error: {str(e)}") 36 | 37 | -------------------------------------------------------------------------------- /plugins/Extra/share.py: -------------------------------------------------------------------------------- 1 | # Don't Remove Credit @VJ_Botz 2 | # Subscribe YouTube Channel For Amazing Bot @Tech_VJ 3 | # Ask Doubt on telegram @KingVJ01 4 | 5 | 6 | import os 7 | from pyrogram import Client, filters 8 | from urllib.parse import quote 9 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton 10 | 11 | @Client.on_message(filters.command(["share_text", "share", "sharetext"])) 12 | async def share_text(client, message): 13 | vj = await client.ask(chat_id = message.from_user.id, text = "Now Send me your text.") 14 | if vj and (vj.text or vj.caption): 15 | input_text = vj.text or vj.caption 16 | else: 17 | await vj.reply_text( 18 | text=f"**Notice:**\n\n1. Send Any Text Messages.\n2. No Media Support\n\n**Any Question Join Support Chat**", 19 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("Updates Channel", url=f"https://t.me/mnbots"), 20 | InlineKeyboardButton('ʀᴇᴘᴏ', url='https://github.com/mn-bots/ShobanaFilterBot')]]) 21 | ) 22 | return 23 | await vj.reply_text( 24 | text=f"**Here is Your Sharing Text 👇**\n\nhttps://t.me/share/url?url=" + quote(input_text), 25 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("♂️ Share", url=f"https://t.me/share/url?url={quote(input_text)}")]]) 26 | ) 27 | -------------------------------------------------------------------------------- /plugins/banned.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | from utils import temp 3 | from pyrogram.types import Message 4 | from database.users_chats_db import db 5 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup 6 | from info import SUPPORT_CHAT 7 | 8 | async def banned_users(_, client, message: Message): 9 | return ( 10 | message.from_user is not None or not message.sender_chat 11 | ) and message.from_user.id in temp.BANNED_USERS 12 | 13 | banned_user = filters.create(banned_users) 14 | 15 | async def disabled_chat(_, client, message: Message): 16 | return message.chat.id in temp.BANNED_CHATS 17 | 18 | disabled_group=filters.create(disabled_chat) 19 | 20 | 21 | @Client.on_message(filters.private & banned_user & filters.incoming) 22 | async def ban_reply(bot, message): 23 | ban = await db.get_ban_status(message.from_user.id) 24 | await message.reply(f'Sorry Dude, You are Banned to use Me. \nBan Reason: {ban["ban_reason"]}') 25 | 26 | @Client.on_message(filters.group & disabled_group & filters.incoming) 27 | async def grp_bd(bot, message): 28 | buttons = [[ 29 | InlineKeyboardButton('𝚂𝚞𝚙𝚙𝚘𝚛𝚝', url=f'https://t.me/mnbots_support') 30 | ]] 31 | reply_markup=InlineKeyboardMarkup(buttons) 32 | vazha = await db.get_chat(message.chat.id) 33 | k = await message.reply( 34 | text=f"CHAT NOT ALLOWED 🐞\n\nMy admins has restricted me from working here ! If you want to know more about it contact support..\nReason : {vazha['reason']}.", 35 | reply_markup=reply_markup) 36 | try: 37 | await k.pin() 38 | except: 39 | pass 40 | await bot.leave_chat(message.chat.id) 41 | -------------------------------------------------------------------------------- /plugins/Extra/shell.py: -------------------------------------------------------------------------------- 1 | import os 2 | import io 3 | import asyncio 4 | from pyrogram import Client, filters 5 | from info import ADMINS 6 | from subprocess import getoutput as run, TimeoutExpired 7 | 8 | 9 | @Client.on_message(filters.command(["sh", "shell"]) & filters.user(ADMINS)) 10 | async def shell(client, message): 11 | # Ensure the message contains a command 12 | if len(message.command) < 2: 13 | await message.reply("Please provide a shell command to execute.") 14 | return 15 | 16 | # Extract the code to be run 17 | code = message.text.split(None, 1)[1] 18 | message_text = await message.reply_text("`Running...`") 19 | 20 | try: 21 | # Run the shell command with a timeout (e.g., 60 seconds) 22 | output = await asyncio.to_thread(run, code) 23 | 24 | if len(output) > 4096: 25 | # If the output is too large, send it as a document 26 | with io.BytesIO(str.encode(output)) as out_file: 27 | out_file.name = "shell_output.txt" 28 | await message.reply_document(document=out_file, disable_notification=True) 29 | await message_text.delete() 30 | else: 31 | # Send the output as a regular message 32 | await message_text.edit(f"Output:\n{output}") 33 | except TimeoutExpired: 34 | # Handle command timeouts 35 | await message_text.edit("The command timed out. Please try again with a shorter command.") 36 | except Exception as e: 37 | # Catch any other errors (e.g., invalid command) 38 | await message_text.edit(f"An error occurred while running the command: {e}") 39 | print(f"Error executing shell command: {e}") 40 | -------------------------------------------------------------------------------- /plugins/Extra/eval.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | from pyrogram.errors import MessageTooLong 3 | import sys, os 4 | import re 5 | import traceback 6 | from io import StringIO 7 | from info import ADMINS 8 | 9 | @Client.on_message(filters.command("eval") & filters.user(ADMINS)) 10 | async def executor(client, message): 11 | try: 12 | code = message.text.split(" ", 1)[1] 13 | except: 14 | return await message.reply('Command Incomplete!\nUsage: /eval your_python_code') 15 | old_stderr = sys.stderr 16 | old_stdout = sys.stdout 17 | redirected_output = sys.stdout = StringIO() 18 | redirected_error = sys.stderr = StringIO() 19 | stdout, stderr, exc = None, None, None 20 | try: 21 | await aexec(code, client, message) 22 | except: 23 | exc = traceback.format_exc() 24 | stdout = redirected_output.getvalue() 25 | stderr = redirected_error.getvalue() 26 | sys.stdout = old_stdout 27 | sys.stderr = old_stderr 28 | evaluation = "" 29 | if exc: 30 | evaluation = exc 31 | elif stderr: 32 | evaluation = stderr 33 | elif stdout: 34 | evaluation = stdout 35 | else: 36 | evaluation = "Success!" 37 | final_output = f"Output:\n\n{evaluation}" 38 | try: 39 | await message.reply(final_output) 40 | except MessageTooLong: 41 | with open('eval.txt', 'w+') as outfile: 42 | outfile.write(final_output) 43 | await message.reply_document('eval.txt') 44 | os.remove('eval.txt') 45 | 46 | 47 | async def aexec(code, client, message): 48 | exec( 49 | "async def __aexec(client, message): " 50 | + "".join(f"\n {a}" for a in code.split("\n")) 51 | ) 52 | return await locals()["__aexec"](client, message) 53 | 54 | -------------------------------------------------------------------------------- /plugins/Extra/password.py: -------------------------------------------------------------------------------- 1 | import random 2 | from pyrogram import Client, filters, enums 3 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton 4 | 5 | 6 | @Client.on_message(filters.command(["genpassword", 'genpw'])) 7 | async def password(bot, update): 8 | message = await update.reply_text(text="`Processing...`") 9 | 10 | # Define available characters for password generation 11 | lowercase = "abcdefghijklmnopqrstuvwxyz" 12 | uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 13 | digits = "1234567890" 14 | special = "!@#$%^&*()_+" 15 | 16 | # Combine all characters 17 | all_characters = lowercase + uppercase + digits + special 18 | 19 | # Check if a length is provided; otherwise, use a random default choice 20 | if len(update.command) > 1: 21 | try: 22 | qw = update.text.split(" ", 1)[1] 23 | limit = int(qw) 24 | if limit < 4 or limit > 32: # Ensure the length is reasonable (between 4 and 32 characters) 25 | raise ValueError("Password length must be between 4 and 32 characters.") 26 | except (ValueError, IndexError): 27 | await message.edit_text("Please provide a valid password length (between 4 and 32 characters).") 28 | return 29 | else: 30 | # Default lengths to choose from 31 | ST = ["5", "7", "6", "9", "10", "12", "14", "16"] 32 | qw = random.choice(ST) 33 | limit = int(qw) 34 | 35 | # Generate random password 36 | random_value = "".join(random.sample(all_characters, limit)) 37 | 38 | # Prepare the response text 39 | txt = f"Limit: {str(limit)} \nPassword: {random_value}" 40 | 41 | # Inline buttons 42 | btn = InlineKeyboardMarkup([ 43 | [InlineKeyboardButton('MN Bots', url='https://t.me/mnbots'), 44 | InlineKeyboardButton('ʀᴇᴘᴏ', url='https://github.com/mn-bots/ShobanaFilterBot')] 45 | ]) 46 | 47 | # Edit the message to show the generated password 48 | await message.edit_text(text=txt, reply_markup=btn, parse_mode=enums.ParseMode.HTML) 49 | -------------------------------------------------------------------------------- /plugins/etc.py: -------------------------------------------------------------------------------- 1 | import random 2 | import re, asyncio, time, shutil, psutil, os, sys 3 | from pyrogram import Client, filters, enums 4 | from pyrogram.types import * 5 | from info import BOT_START_TIME, ADMINS 6 | from utils import humanbytes 7 | 8 | CMD = ["/", "."] 9 | 10 | @Client.on_message(filters.command("ping", CMD) & filters.user(ADMINS)) 11 | async def ping(_, message): 12 | start_t = time.time() 13 | rm = await message.reply_text("...........") 14 | end_t = time.time() 15 | time_taken_s = (end_t - start_t) * 1000 16 | await rm.edit(f"𝖯𝗂𝗇𝗀!\n{time_taken_s:.3f} ms") 17 | 18 | @Client.on_message(filters.command("usage") & filters.user(ADMINS)) 19 | async def stats(bot, update): 20 | currentTime = time.strftime("%Hh%Mm%Ss", time.gmtime(time.time() - BOT_START_TIME)) 21 | total, used, free = shutil.disk_usage(".") 22 | total = humanbytes(total) 23 | used = humanbytes(used) 24 | free = humanbytes(free) 25 | cpu_usage = psutil.cpu_percent() 26 | ram_usage = psutil.virtual_memory().percent 27 | disk_usage = psutil.disk_usage('/').percent 28 | 29 | ms_g = f"""⚙️ 𝖡𝗈𝗍 𝖲𝗍𝖺𝗍𝗎𝗌 30 | 31 | 🕔 𝖴𝗉𝗍𝗂𝗆𝖾: {currentTime} 32 | 🛠 𝖢𝖯𝖴 𝖴𝗌𝖺𝗀𝖾: {cpu_usage}% 33 | 🗜 𝖱𝖠𝖬 𝖴𝗌𝖺𝗀𝖾: {ram_usage}% 34 | 🗂 𝖳𝗈𝗍𝖺𝗅 𝖣𝗂𝗌𝗄 𝖲𝗉𝖺𝖼𝖾: {total} 35 | 🗳 𝖴𝗌𝖾𝖽 𝖲𝗉𝖺𝖼𝖾: {used} ({disk_usage}%) 36 | 📝 𝖥𝗋𝖾𝖾 𝖲𝗉𝖺𝖼𝖾: {free} """ 37 | 38 | msg = await bot.send_message(chat_id=update.chat.id, text="__𝖯𝗋𝗈𝖼𝖾𝗌𝗌𝗂𝗇𝗀...__", parse_mode=enums.ParseMode.MARKDOWN) 39 | await msg.edit_text(text=ms_g, parse_mode=enums.ParseMode.HTML) 40 | 41 | @Client.on_message(filters.command("restart") & filters.user(ADMINS)) 42 | async def stop_button(bot, message): 43 | msg = await bot.send_message(text="**𝖡𝗈𝗍 𝖨𝗌 𝖱𝖾𝗌𝗍𝖺𝗋𝗍𝗂𝗇𝗀...🪄**", chat_id=message.chat.id) 44 | await asyncio.sleep(3) 45 | await msg.edit("**𝖡𝗈𝗍 𝖱𝖾𝗌𝗍𝖺𝗋𝗍𝖾𝖽 𝖲𝗎𝖼𝖼𝖾𝗌𝗌𝖿𝗎𝗅𝗅𝗒 ! 𝖱𝖾𝖺𝖽𝗒 𝖳𝗈 𝖬𝗈𝗏𝖾 𝖮𝗇 💯**") 46 | os.execl(sys.executable, sys.executable, *sys.argv) 47 | -------------------------------------------------------------------------------- /plugins/Extra/carbon.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import logging 3 | from io import BytesIO 4 | from pyrogram import Client, filters 5 | 6 | # Configure logging 7 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') 8 | logger = logging.getLogger(__name__) 9 | 10 | async def make_carbon(code): 11 | """Generate a carbon image from code using Carbonara API.""" 12 | url = "https://carbonara.solopov.dev/api/cook" 13 | try: 14 | async with aiohttp.ClientSession() as session: 15 | async with session.post(url, json={"code": code}) as resp: 16 | if resp.status != 200: 17 | logger.error(f"Failed to fetch carbon image. Status code: {resp.status}") 18 | return None 19 | image = BytesIO(await resp.read()) 20 | image.name = "carbon.png" 21 | return image 22 | except Exception as e: 23 | logger.error(f"Error while making carbon: {e}") 24 | return None 25 | 26 | @Client.on_message(filters.command("carbon")) 27 | async def _carbon(client, message): 28 | """Handler for the /carbon command to generate a carbon image from replied text.""" 29 | replied = message.reply_to_message 30 | if not replied or not (replied.text or replied.caption): 31 | await message.reply_text("**Please reply to a text message to generate a carbon.**") 32 | return 33 | 34 | text_content = replied.text or replied.caption 35 | text = await message.reply("**Processing your request...**") 36 | 37 | carbon_image = await make_carbon(text_content) 38 | if carbon_image is None: 39 | await text.edit("**Failed to generate carbon. Please try again later.**") 40 | return 41 | 42 | try: 43 | await text.edit("**Uploading carbon image...**") 44 | await message.reply_photo(carbon_image, caption="Here is your carbon image!") 45 | except Exception as e: 46 | logger.error(f"Error while uploading carbon image: {e}") 47 | await text.edit("**Failed to upload carbon image.**") 48 | finally: 49 | carbon_image.close() 50 | await text.delete() 51 | 52 | # Add additional error handling or functionality if needed 53 | -------------------------------------------------------------------------------- /plugins/Extra/tts.py: -------------------------------------------------------------------------------- 1 | import traceback 2 | from asyncio import get_running_loop 3 | from io import BytesIO 4 | from googletrans import Translator, LANGUAGES 5 | from gtts import gTTS 6 | from pyrogram import Client, filters 7 | from pyrogram.types import Message 8 | 9 | 10 | async def convert(text): 11 | try: 12 | # Detect language using Google Translate API 13 | translator = Translator() 14 | detected_lang = translator.detect(text).lang 15 | lang_name = LANGUAGES.get(detected_lang, "Unknown") 16 | 17 | # Generate the TTS (Text-to-Speech) audio 18 | tts = gTTS(text, lang=detected_lang) 19 | 20 | # Use BytesIO to store the audio in memory 21 | audio = BytesIO() 22 | tts.write_to_fp(audio) 23 | audio.seek(0) # Rewind the BytesIO object to the start 24 | 25 | audio.name = f"{lang_name}.mp3" 26 | return audio, lang_name 27 | except Exception as e: 28 | print(f"Error during TTS conversion: {e}") 29 | return None, str(e) 30 | 31 | 32 | @Client.on_message(filters.command("tts")) 33 | async def text_to_speech(_, message: Message): 34 | if not message.reply_to_message: 35 | return await message.reply_text("Please reply to a message containing text.") 36 | 37 | if not message.reply_to_message.text: 38 | return await message.reply_text("The replied message does not contain any text.") 39 | 40 | text = message.reply_to_message.text 41 | m = await message.reply_text("Processing...") 42 | 43 | try: 44 | # Asynchronously handle the TTS conversion 45 | loop = get_running_loop() 46 | audio, lang_name = await loop.run_in_executor(None, convert, text) 47 | 48 | if audio: 49 | await message.reply_audio(audio, caption=f"Audio in {lang_name} language.") 50 | await m.delete() 51 | audio.close() # Ensure resource cleanup 52 | else: 53 | await m.edit(f"Error: {lang_name}") # lang_name contains the error message 54 | except Exception as e: 55 | await m.edit("An error occurred.") 56 | print(f"Error: {e}") 57 | e_traceback = traceback.format_exc() 58 | print(e_traceback) 59 | -------------------------------------------------------------------------------- /plugins/Extra/tr.py: -------------------------------------------------------------------------------- 1 | from googletrans import Translator, LANGUAGES 2 | from pyrogram import Client, filters 3 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton 4 | import re 5 | 6 | @Client.on_message(filters.command(["tr"])) 7 | async def translate(client, message): 8 | if message.reply_to_message: 9 | try: 10 | # Extract language code from the command, default to auto-detection if not specified 11 | text = message.text.strip() 12 | command_parts = text.split("/tr") 13 | 14 | if len(command_parts) > 1: 15 | lang_code = command_parts[1].strip().lower() 16 | if lang_code not in LANGUAGES: 17 | raise ValueError(f"Invalid language code: {lang_code}") 18 | else: 19 | lang_code = 'auto' # Use 'auto' for language detection 20 | 21 | tr_text = message.reply_to_message.text 22 | translator = Translator() 23 | 24 | # If language is 'auto', detect the source language 25 | if lang_code == 'auto': 26 | detected = translator.detect(tr_text) 27 | from_lang = detected.lang 28 | to_lang = 'auto' 29 | translated_text = translator.translate(tr_text, src=from_lang, dest=lang_code).text 30 | else: 31 | from_lang = 'auto' # Set to auto for now since user is specifying the destination 32 | to_lang = lang_code 33 | translated_text = translator.translate(tr_text, dest=to_lang).text 34 | 35 | # Construct response 36 | from_lang_name = LANGUAGES.get(from_lang, "Unknown") 37 | to_lang_name = LANGUAGES.get(to_lang, "Unknown") 38 | reply_text = f"Translated from {from_lang_name} to {to_lang_name}:\n\n{translated_text}" 39 | 40 | # Print to console for logging 41 | print(f"Translated from {from_lang_name} to {to_lang_name}: {translated_text}") 42 | 43 | # Reply with the translated text 44 | await message.reply_text(reply_text) 45 | 46 | except Exception as e: 47 | print(f"Error: {e}") 48 | await message.reply_text(f"An error occurred during translation: {e}") 49 | 50 | else: 51 | await message.reply_text("You can use this command by replying to a message.") 52 | -------------------------------------------------------------------------------- /plugins/Extra/paste.py: -------------------------------------------------------------------------------- 1 | import os, re, json, aiohttp, requests 2 | from pyrogram import Client, filters 3 | 4 | #Headers 5 | headers = { 6 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36", 7 | "content-type": "application/json", 8 | } 9 | 10 | #Pastebins 11 | async def p_paste(message, extension=None): 12 | siteurl = "https://pasty.lus.pm/api/v1/pastes" 13 | data = {"content": message} 14 | try: 15 | response = requests.post(url=siteurl, data=json.dumps(data), headers=headers) 16 | except Exception as e: 17 | return {"error": str(e)} 18 | if response.ok: 19 | response = response.json() 20 | purl = ( 21 | f"https://pasty.lus.pm/{response['id']}.{extension}" 22 | if extension 23 | else f"https://pasty.lus.pm/{response['id']}.txt" 24 | ) 25 | return { 26 | "url": purl, 27 | "raw": f"https://pasty.lus.pm/{response['id']}/raw", 28 | "bin": "Pasty", 29 | } 30 | return {"error": "UNABLE TO REACH pasty.lus.pm"} 31 | 32 | 33 | 34 | @Client.on_message(filters.command(["tgpaste", "pasty", "paste"])) 35 | async def pasty(client, message): 36 | pablo = await message.reply_text("`Pʟᴇᴀꜱᴇ Wᴀɪᴛ...`") 37 | tex_t = message.text 38 | if ' ' in message.text: 39 | message_s = message.text.split(" ", 1)[1] 40 | elif message.reply_to_message: 41 | message_s = message.reply_to_message.text 42 | else: 43 | await message.reply("Sᴏʀʀʏ No Iɴ Pᴜᴛ. Pʟᴇᴀꜱᴇ Rᴇᴩʟʏ To A Tᴇxᴛ Oʀ /paste Wɪᴛʜ Tᴇxᴛ") 44 | if not tex_t: 45 | if not message.reply_to_message: 46 | await pablo.edit("Oɴʟʏ Tᴇxᴛ Aɴᴅ Dᴏᴄᴜᴍᴇɴᴛs Aʀᴇ Sᴜᴘᴘᴏʀᴛᴇᴅ") 47 | return 48 | if not message.reply_to_message.text: 49 | file = await message.reply_to_message.download() 50 | m_list = open(file, "r").read() 51 | message_s = m_list 52 | os.remove(file) 53 | elif message.reply_to_message.text: 54 | message_s = message.reply_to_message.text 55 | 56 | ext = "py" 57 | x = await p_paste(message_s, ext) 58 | p_link = x["url"] 59 | p_raw = x["raw"] 60 | 61 | pasted = f"**Sᴜᴄᴄᴇssғᴜʟʟʏ Pᴀsᴛᴇ Tᴏ Pᴀsᴛʏ**\n\n**Lɪɴᴋ:** • [CʟɪᴄᴋHᴇʀᴇ]({p_link})\n\n**Rᴀᴡ Lɪɴᴋ:** • [CʟɪᴄᴋHᴇʀᴇ]({p_raw})" 62 | await pablo.edit(pasted, disable_web_page_preview=True) 63 | -------------------------------------------------------------------------------- /plugins/Extra/pin.py: -------------------------------------------------------------------------------- 1 | from pyrogram.types import Message 2 | from pyrogram import Client, filters 3 | from pyrogram.types import Message 4 | from pyrogram import filters, enums 5 | from pyrogram.types import * 6 | 7 | async def admin_check(message: Message) -> bool: 8 | if not message.from_user: 9 | return False 10 | 11 | if message.chat.type not in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]: 12 | return False 13 | 14 | if message.from_user.id in [ 15 | 777000, # Telegram Service Notifications 16 | 1087968824 # GroupAnonymousBot 17 | ]: 18 | return True 19 | 20 | client = message._client 21 | chat_id = message.chat.id 22 | user_id = message.from_user.id 23 | 24 | check_status = await client.get_chat_member( 25 | chat_id=chat_id, 26 | user_id=user_id 27 | ) 28 | admin_strings = [enums.ChatMemberStatus.OWNER, enums.ChatMemberStatus.ADMINISTRATOR] 29 | # https://git.colinshark.de/PyroBot/PyroBot/src/branch/master/pyrobot/modules/admin.py#L69 30 | if check_status.status not in admin_strings: 31 | return False 32 | else: 33 | return True 34 | 35 | async def admin_filter_f(filt, client, message): 36 | return await admin_check(message) 37 | 38 | admin_fliter = filters.create(func=admin_filter_f, name="AdminFilter") 39 | 40 | 41 | @Client.on_message(filters.command("pin") & admin_fliter) 42 | async def pin(_, message: Message): 43 | if not message.reply_to_message: 44 | return 45 | await message.reply_to_message.pin() 46 | await message.reply_text("I Have Pinned That message") 47 | 48 | 49 | @Client.on_message(filters.command("unpin") & admin_fliter) 50 | async def unpin(_, message: Message): 51 | if not message.reply_to_message: 52 | return 53 | await message.reply_to_message.unpin() 54 | await message.reply_text("I Have UnPinned That message") 55 | 56 | 57 | @Client.on_message(filters.command("unpin_all") & filters.group) 58 | async def unpinall_handler(client, message: Message): 59 | try: 60 | user = await client.get_chat_member(message.chat.id , message.from_user.id) 61 | if user.status not in [enums.ChatMemberStatus.OWNER , enums.ChatMemberStatus.ADMINISTRATOR]: 62 | raise PermissionError("You are not allowed to use this command") 63 | await client.unpin_all_chat_messages(message.chat.id) 64 | except Exception as e: 65 | await message.reply_text(f"{e}") 66 | -------------------------------------------------------------------------------- /plugins/Extra/feedback.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | from pyrogram.types import Message 3 | from info import LOG_CHANNEL 4 | 5 | # Function to handle feedback and bug reporting 6 | @Client.on_message(filters.command(["bug", "bugs", "feedback"])) 7 | async def bug_handler(client: Client, message: Message): 8 | try: 9 | # Check if the command has additional arguments or a reply to a message 10 | if len(message.command) < 2: 11 | if message.reply_to_message and message.reply_to_message.text: 12 | bug_report = message.reply_to_message.text.strip() 13 | else: 14 | return await message.reply_text( 15 | "Please reply to a text message or provide a description of the bug." 16 | ) 17 | else: 18 | bug_report = message.text.split(" ", 1)[1].strip() 19 | 20 | # Check for empty bug reports 21 | if not bug_report: 22 | return await message.reply_text("The bug description cannot be empty. Please try again.") 23 | 24 | # Construct the acknowledgment message 25 | response_message = ( 26 | f"Hi {message.from_user.mention},\n" 27 | "Thank you for reporting the issue. It has been forwarded to the developer." 28 | ) 29 | await message.reply_text(response_message) 30 | 31 | # Log the bug report to the designated channel 32 | log_message = ( 33 | f"#BugReport\n\n" 34 | f"**User:** {message.from_user.mention} ([User ID: {message.from_user.id}])\n" 35 | f"**Chat:** {message.chat.title if message.chat.type != 'private' else 'Private Chat'}\n" 36 | f"**Chat ID:** {message.chat.id}\n" 37 | f"**Bug Description:**\n{bug_report}" 38 | ) 39 | await client.send_message(LOG_CHANNEL, text=log_message) 40 | 41 | except Exception as e: 42 | # Error handling and reporting to developers 43 | await message.reply_text( 44 | "An unexpected error occurred while processing your request. Please try again later." 45 | ) 46 | error_message = ( 47 | f"#Error\n\n" 48 | f"**Error occurred in bug handler:**\n{str(e)}\n\n" 49 | f"**User:** {message.from_user.mention} ([User ID: {message.from_user.id}])\n" 50 | f"**Chat ID:** {message.chat.id}" 51 | ) 52 | await client.send_message(LOG_CHANNEL, text=error_message) 53 | 54 | -------------------------------------------------------------------------------- /plugins/broadcast.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters 2 | import datetime 3 | import time 4 | from database.users_chats_db import db 5 | from info import ADMINS 6 | from utils import broadcast_messages 7 | import asyncio 8 | 9 | BROADCAST_BATCH_SIZE = 500 # Now processes 500 users at a time 10 | BROADCAST_SLEEP = 1 # Small delay to avoid rate limits 11 | 12 | @Client.on_message(filters.command("broadcast") & filters.user(ADMINS) & filters.reply) 13 | async def broadcast(bot, message): 14 | users = await db.get_all_users() 15 | b_msg = message.reply_to_message 16 | sts = await message.reply_text("Broadcasting your messages...") 17 | 18 | start_time = time.time() 19 | total_users = await db.total_users_count() 20 | done, blocked, deleted, failed, success = 0, 0, 0, 0, 0 21 | 22 | async def send_message(user): 23 | nonlocal success, blocked, deleted, failed 24 | user_id = int(user['id']) 25 | pti, sh = await broadcast_messages(user_id, b_msg) 26 | 27 | if pti: 28 | success += 1 29 | else: 30 | if sh == "Blocked": 31 | blocked += 1 32 | await db.delete_user(user_id) # Remove blocked user 33 | elif sh == "Deleted": 34 | deleted += 1 35 | await db.delete_user(user_id) # Remove deleted user 36 | elif sh == "Error": 37 | failed += 1 38 | 39 | tasks = [] 40 | async for user in users: 41 | tasks.append(send_message(user)) 42 | done += 1 43 | 44 | # Process messages in batches of 500 45 | if len(tasks) >= BROADCAST_BATCH_SIZE: 46 | await asyncio.gather(*tasks) 47 | tasks = [] 48 | await sts.edit( 49 | f"Broadcast in progress:\n\nTotal Users: {total_users}\nCompleted: {done} / {total_users}\n" 50 | f"Success: {success} | Blocked: {blocked} | Deleted: {deleted} | Failed: {failed}" 51 | ) 52 | await asyncio.sleep(BROADCAST_SLEEP) # Small delay to avoid rate limits 53 | 54 | # Process remaining messages (if any) 55 | if tasks: 56 | await asyncio.gather(*tasks) 57 | 58 | time_taken = datetime.timedelta(seconds=int(time.time() - start_time)) 59 | await sts.edit( 60 | f"Broadcast Completed in {time_taken}.\n\nTotal Users: {total_users}\n" 61 | f"Success: {success} | Blocked: {blocked} | Deleted: {deleted} | Failed: {failed}" 62 | ) 63 | -------------------------------------------------------------------------------- /plugins/Extra/json.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from pyrogram import Client, filters 4 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton 5 | 6 | # Callback for displaying message as JSON 7 | @Client.on_message(filters.command(["json", "js", "showjson"])) 8 | async def jsonify(client, message): 9 | """ 10 | Handle the /json, /js, or /showjson command to display the JSON representation of a message. 11 | If the message is too large, it will save it as a file and send it instead. 12 | """ 13 | the_real_message = message.reply_to_message or message # Check for a replied message, else use the current message 14 | 15 | # Inline keyboard for closing the message 16 | close_button = InlineKeyboardMarkup( 17 | [[InlineKeyboardButton(text="𝙲𝙻𝙾𝚂𝙴", callback_data="close_data")]] 18 | ) 19 | 20 | try: 21 | # Format the message as JSON if possible, falling back to string representation 22 | formatted_message = json.dumps(the_real_message, default=str, indent=2, ensure_ascii=False) 23 | 24 | # Attempt to send the JSON representation of the message 25 | await message.reply_text( 26 | f"{formatted_message}", reply_markup=close_button, quote=True 27 | ) 28 | except Exception as e: 29 | # Handle cases where the message is too large to send directly 30 | temp_filename = "message_data.json" 31 | try: 32 | with open(temp_filename, "w", encoding="utf8") as out_file: 33 | json.dump(the_real_message, out_file, default=str, indent=2, ensure_ascii=False) 34 | 35 | # Send the JSON as a document with an error caption 36 | await message.reply_document( 37 | document=temp_filename, 38 | caption=f"Error: {e}", 39 | disable_notification=True, 40 | quote=True, 41 | reply_markup=close_button 42 | ) 43 | finally: 44 | # Ensure the temporary file is removed 45 | if os.path.exists(temp_filename): 46 | os.remove(temp_filename) 47 | 48 | # Add a callback handler for the close button 49 | @Client.on_callback_query(filters.regex("^close_data$")) 50 | async def close_callback(client, callback_query): 51 | """ 52 | Handle the close button callback to delete the JSON message. 53 | """ 54 | await callback_query.message.delete() 55 | await callback_query.answer("Closed", show_alert=False) 56 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shobanafilterbot", 3 | "description": "When you going to send file on telegram channel this bot will save that in database, So you can search that easily in inline mode", 4 | "stack": "container", 5 | "keywords": [ 6 | "telegram", 7 | "auto-filter", 8 | "filter", 9 | "best", 10 | "indian", 11 | "pyrogram", 12 | "media", 13 | "search", 14 | "channel", 15 | "index", 16 | "inline" 17 | ], 18 | "website": "https://github.com/mn-bots/shobanafilterbot", 19 | "repository": "https://github.com/mn-bots/shobanafilterbot", 20 | "env": { 21 | "BOT_TOKEN": { 22 | "description": "Your bot token.", 23 | "required": true 24 | }, 25 | "API_ID": { 26 | "description": "Get this value from https://my.telegram.org or @USERS_RO_BOT", 27 | "required": true 28 | }, 29 | "API_HASH": { 30 | "description": "Get this value from https://my.telegram.org or @USERS_RO_BOT", 31 | "required": true 32 | }, 33 | "CHANNELS": { 34 | "description": "Username or ID of channel or group. Separate multiple IDs by space.", 35 | "required": false 36 | }, 37 | "ADMINS": { 38 | "description": "Username or ID of Admin. Separate multiple Admins by space.", 39 | "required": true 40 | }, 41 | "PICS": { 42 | "description": "Add some telegraph link of pictures .", 43 | "required": false 44 | }, 45 | "LOG_CHANNEL": { 46 | "description": "Bot Logs,Give a channel id with -100xxxxxxx", 47 | "required": true 48 | }, 49 | "AUTH_USERS": { 50 | "description": "Username or ID of users to give access of inline search. Separate multiple users by space.\nLeave it empty if you don't want to restrict bot usage.", 51 | "required": false 52 | }, 53 | "AUTH_CHANNEL": { 54 | "description": "ID of channel.Make sure bot is admin in this channel. Without subscribing this channel users cannot use bot.", 55 | "required": false 56 | }, 57 | "DATABASE_URI": { 58 | "description": "mongoDB URI. Get this value from https://www.mongodb.com.", 59 | "required": true 60 | }, 61 | "DATABASE_NAME": { 62 | "description": "Name of the database in mongoDB.", 63 | "required": false 64 | }, 65 | "COLLECTION_NAME": { 66 | "description": "Name of the collections. Defaults to Telegram_files. If you are using the same database, then use different collection name for each bot", 67 | "value": "Anurag_files", 68 | "required": false 69 | } 70 | }, 71 | "addons": [], 72 | "buildpacks": [{ 73 | "url": "heroku/python" 74 | }], 75 | "formation": { 76 | "worker": { 77 | "quantity": 1, 78 | "size": "free" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /plugins/Extra/promote.py: -------------------------------------------------------------------------------- 1 | from pyrogram import * 2 | from pyrogram.types import * 3 | 4 | @Client.on_message(filters.command("promote") & filters.group) 5 | async def promoting(client, message): 6 | global new_admin 7 | if not message.reply_to_message: 8 | return await message.reply("use this command reply") 9 | reply = message.reply_to_message 10 | chat_id = message.chat.id 11 | new_admin = reply.from_user 12 | admin = message.from_user 13 | user_stats = await client.get_chat_member(chat_id, admin.id) 14 | bot_stats = await client.get_chat_member(chat_id, "self") 15 | if not bot_stats.privileges: 16 | return await message.reply("hey dude iam not admin") 17 | elif not user_stats.privileges: 18 | return await message.reply("Sorry dude you need admin") 19 | elif not bot_stats.privileges.can_promote_members: 20 | return await message.reply("i dont have admin rights ") 21 | elif not user_stats.privileges.can_promote_members: 22 | return await message.reply("you need admin rights 😒") 23 | elif user_stats.privileges.can_promote_members: 24 | msg = await message.reply_text("Promoting") 25 | await client.promote_chat_member( 26 | message.chat.id, 27 | new_admin.id, 28 | privileges=pyrogram.types.ChatPrivileges( 29 | can_change_info=True, 30 | can_delete_messages=True, 31 | can_pin_messages=True, 32 | can_invite_users=True, 33 | can_manage_video_chats=True, 34 | can_restrict_members=True 35 | )) 36 | await msg.edit(f"Alright!! Successful promoted") 37 | 38 | 39 | @Client.on_message(filters.command("demote") & filters.group) 40 | async def demote(client, message): 41 | global new_admin 42 | if not message.reply_to_message: 43 | return await message.reply("use this command reply") 44 | reply = message.reply_to_message 45 | chat_id = message.chat.id 46 | new_admin = reply.from_user 47 | admin = message.from_user 48 | user_stats = await client.get_chat_member(chat_id, admin.id) 49 | bot_stats = await client.get_chat_member(chat_id, "self") 50 | if not bot_stats.privileges: 51 | return await message.reply("hey dude iam not admin") 52 | elif not user_stats.privileges: 53 | return await message.reply("Sorry dude you need admin") 54 | elif not bot_stats.privileges.can_promote_members: 55 | return await message.reply("i dont have admin rights ") 56 | elif not user_stats.privileges.can_promote_members: 57 | return await message.reply("you need admin rights 😒") 58 | elif user_stats.privileges.can_promote_members: 59 | msg = await message.reply_text("`Proccing...`") 60 | await client.promote_chat_member( 61 | chat_id, 62 | new_admin.id, 63 | privileges=pyrogram.types.ChatPrivileges( 64 | can_change_info=False, 65 | can_invite_users=False, 66 | can_delete_messages=False, 67 | can_restrict_members=False, 68 | can_pin_messages=False, 69 | can_promote_members=False, 70 | can_manage_chat=False, 71 | can_manage_video_chats=False 72 | )) 73 | await msg.edit(f"Hmm!! demoted 🥺 ") 74 | -------------------------------------------------------------------------------- /plugins/Extra/short.py: -------------------------------------------------------------------------------- 1 | import os 2 | import aiohttp 3 | from pyrogram import Client, filters, enums 4 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultArticle, InputTextMessageContent 5 | from pyrogram.handlers import MessageHandler 6 | from pyshorteners import Shortener 7 | 8 | BITLY_API = os.environ.get("BITLY_API", "8df1df8c23f719e5cf97788cc2d40321ea30092b") 9 | CUTTLY_API = os.environ.get("CUTTLY_API", "f64dffbde033b6c307387dd50b7c76e505f1c") 10 | SHORTCM_API = os.environ.get("SHORTCM_API", "pk_...NIZv") 11 | GPLINKS_API = os.environ.get("GPLINKS_API", "008ccaedd6061ad1948838f410947603de9007a7") 12 | 13 | 14 | reply_markup = InlineKeyboardMarkup( 15 | [[ 16 | InlineKeyboardButton("𝘊𝘭𝘰𝘴𝘦", callback_data='close_data') 17 | ]] 18 | ) 19 | 20 | @Client.on_message(filters.command(["short"]) & filters.regex(r'https?://[^\s]+')) 21 | async def reply_shortens(bot, update): 22 | message = await update.reply_text( 23 | text="`Analysing your link...`", 24 | disable_web_page_preview=True, 25 | quote=True 26 | ) 27 | link = update.matches[0].group(0) 28 | shorten_urls = await short(link) 29 | await message.edit_text( 30 | text=shorten_urls, 31 | disable_web_page_preview=True 32 | ) 33 | 34 | @Client.on_inline_query(filters.regex(r'https?://[^\s]+')) 35 | async def inline_short(bot, update): 36 | link = update.matches[0].group(0), 37 | shorten_urls = await short(link) 38 | answers = [ 39 | InlineQueryResultArticle( 40 | title="Short Links", 41 | description=update.query, 42 | input_message_content=InputTextMessageContent( 43 | message_text=shorten_urls, 44 | disable_web_page_preview=True 45 | ), 46 | reply_to_message_id=message.id 47 | ) 48 | ] 49 | await bot.answer_inline_query( 50 | inline_query_id=update.id, 51 | results=answers 52 | ) 53 | 54 | async def short(link): 55 | shorten_urls = "**--Shorted URLs--**\n" 56 | 57 | # Bit.ly shorten 58 | if BITLY_API: 59 | try: 60 | s = Shortener(api_key=BITLY_API) 61 | url = s.bitly.short(link) 62 | shorten_urls += f"\n**Bit.ly :-** {url}" 63 | except Exception as error: 64 | print(f"Bit.ly error :- {error}") 65 | 66 | # TinyURL.com shorten 67 | try: 68 | s = Shortener() 69 | url = s.tinyurl.short(link) 70 | shorten_urls += f"\n**TinyURL.com :-** {url}" 71 | except Exception as error: 72 | print(f"TinyURL.com error :- {error}") 73 | 74 | # GPLinks shorten 75 | try: 76 | api_url = "https://gplinks.in/api" 77 | params = {'api': GPLINKS_API, 'url': link} 78 | async with aiohttp.ClientSession() as session: 79 | async with session.get(api_url, params=params, raise_for_status=True) as response: 80 | data = await response.json() 81 | url = data["shortenedUrl"] 82 | shorten_urls += f"\n**GPLinks.in :-** {url}" 83 | except Exception as error: 84 | print(f"GPLink error :- {error}") 85 | 86 | # Send the text 87 | try: 88 | shorten_urls += "" 89 | return shorten_urls 90 | except Exception as error: 91 | return error 92 | -------------------------------------------------------------------------------- /database/filters_mdb.py: -------------------------------------------------------------------------------- 1 | import pymongo 2 | from pyrogram import enums 3 | from info import DATABASE_URI, DATABASE_NAME 4 | import logging 5 | logger = logging.getLogger(__name__) 6 | logger.setLevel(logging.ERROR) 7 | 8 | myclient = pymongo.MongoClient(DATABASE_URI) 9 | mydb = myclient[DATABASE_NAME] 10 | 11 | 12 | 13 | async def add_filter(grp_id, text, reply_text, btn, file, alert): 14 | mycol = mydb[str(grp_id)] 15 | # mycol.create_index([('text', 'text')]) 16 | 17 | data = { 18 | 'text':str(text), 19 | 'reply':str(reply_text), 20 | 'btn':str(btn), 21 | 'file':str(file), 22 | 'alert':str(alert) 23 | } 24 | 25 | try: 26 | mycol.update_one({'text': str(text)}, {"$set": data}, upsert=True) 27 | except: 28 | logger.exception('Some error occured!', exc_info=True) 29 | 30 | 31 | async def find_filter(group_id, name): 32 | mycol = mydb[str(group_id)] 33 | 34 | query = mycol.find( {"text":name}) 35 | # query = mycol.find( { "$text": {"$search": name}}) 36 | try: 37 | for file in query: 38 | reply_text = file['reply'] 39 | btn = file['btn'] 40 | fileid = file['file'] 41 | try: 42 | alert = file['alert'] 43 | except: 44 | alert = None 45 | return reply_text, btn, alert, fileid 46 | except: 47 | return None, None, None, None 48 | 49 | 50 | async def get_filters(group_id): 51 | mycol = mydb[str(group_id)] 52 | 53 | texts = [] 54 | query = mycol.find() 55 | try: 56 | for file in query: 57 | text = file['text'] 58 | texts.append(text) 59 | except: 60 | pass 61 | return texts 62 | 63 | 64 | async def delete_filter(message, text, group_id): 65 | mycol = mydb[str(group_id)] 66 | 67 | myquery = {'text':text } 68 | query = mycol.count_documents(myquery) 69 | if query == 1: 70 | mycol.delete_one(myquery) 71 | await message.reply_text( 72 | f"'`{text}`' deleted. I'll not respond to that filter anymore.", 73 | quote=True, 74 | parse_mode=enums.ParseMode.MARKDOWN 75 | ) 76 | else: 77 | await message.reply_text("Couldn't find that filter!", quote=True) 78 | 79 | 80 | async def del_all(message, group_id, title): 81 | if str(group_id) not in mydb.list_collection_names(): 82 | await message.edit_text(f"Nothing to remove in {title}!") 83 | return 84 | 85 | mycol = mydb[str(group_id)] 86 | try: 87 | mycol.drop() 88 | await message.edit_text(f"All filters from {title} has been removed") 89 | except: 90 | await message.edit_text("Couldn't remove all filters from group!") 91 | return 92 | 93 | 94 | async def count_filters(group_id): 95 | mycol = mydb[str(group_id)] 96 | 97 | count = mycol.count() 98 | return False if count == 0 else count 99 | 100 | 101 | async def filter_stats(): 102 | collections = mydb.list_collection_names() 103 | 104 | if "CONNECTION" in collections: 105 | collections.remove("CONNECTION") 106 | 107 | totalcount = 0 108 | for collection in collections: 109 | mycol = mydb[collection] 110 | count = mycol.count() 111 | totalcount += count 112 | 113 | totalcollections = len(collections) 114 | 115 | return totalcollections, totalcount 116 | -------------------------------------------------------------------------------- /database/connections_mdb.py: -------------------------------------------------------------------------------- 1 | import pymongo 2 | 3 | from info import DATABASE_URI, DATABASE_NAME 4 | 5 | import logging 6 | logger = logging.getLogger(__name__) 7 | logger.setLevel(logging.ERROR) 8 | 9 | myclient = pymongo.MongoClient(DATABASE_URI) 10 | mydb = myclient[DATABASE_NAME] 11 | mycol = mydb['CONNECTION'] 12 | 13 | 14 | async def add_connection(group_id, user_id): 15 | query = mycol.find_one( 16 | { "_id": user_id }, 17 | { "_id": 0, "active_group": 0 } 18 | ) 19 | if query is not None: 20 | group_ids = [x["group_id"] for x in query["group_details"]] 21 | if group_id in group_ids: 22 | return False 23 | 24 | group_details = { 25 | "group_id" : group_id 26 | } 27 | 28 | data = { 29 | '_id': user_id, 30 | 'group_details' : [group_details], 31 | 'active_group' : group_id, 32 | } 33 | 34 | if mycol.count_documents( {"_id": user_id} ) == 0: 35 | try: 36 | mycol.insert_one(data) 37 | return True 38 | except: 39 | logger.exception('Some error occurred!', exc_info=True) 40 | 41 | else: 42 | try: 43 | mycol.update_one( 44 | {'_id': user_id}, 45 | { 46 | "$push": {"group_details": group_details}, 47 | "$set": {"active_group" : group_id} 48 | } 49 | ) 50 | return True 51 | except: 52 | logger.exception('Some error occurred!', exc_info=True) 53 | 54 | 55 | async def active_connection(user_id): 56 | 57 | query = mycol.find_one( 58 | { "_id": user_id }, 59 | { "_id": 0, "group_details": 0 } 60 | ) 61 | if not query: 62 | return None 63 | 64 | group_id = query['active_group'] 65 | return int(group_id) if group_id != None else None 66 | 67 | 68 | async def all_connections(user_id): 69 | query = mycol.find_one( 70 | { "_id": user_id }, 71 | { "_id": 0, "active_group": 0 } 72 | ) 73 | if query is not None: 74 | return [x["group_id"] for x in query["group_details"]] 75 | else: 76 | return None 77 | 78 | 79 | async def if_active(user_id, group_id): 80 | query = mycol.find_one( 81 | { "_id": user_id }, 82 | { "_id": 0, "group_details": 0 } 83 | ) 84 | return query is not None and query['active_group'] == group_id 85 | 86 | 87 | async def make_active(user_id, group_id): 88 | update = mycol.update_one( 89 | {'_id': user_id}, 90 | {"$set": {"active_group" : group_id}} 91 | ) 92 | return update.modified_count != 0 93 | 94 | 95 | async def make_inactive(user_id): 96 | update = mycol.update_one( 97 | {'_id': user_id}, 98 | {"$set": {"active_group" : None}} 99 | ) 100 | return update.modified_count != 0 101 | 102 | 103 | async def delete_connection(user_id, group_id): 104 | 105 | try: 106 | update = mycol.update_one( 107 | {"_id": user_id}, 108 | {"$pull" : { "group_details" : {"group_id":group_id} } } 109 | ) 110 | if update.modified_count == 0: 111 | return False 112 | query = mycol.find_one( 113 | { "_id": user_id }, 114 | { "_id": 0 } 115 | ) 116 | if len(query["group_details"]) >= 1: 117 | if query['active_group'] == group_id: 118 | prvs_group_id = query["group_details"][len(query["group_details"]) - 1]["group_id"] 119 | 120 | mycol.update_one( 121 | {'_id': user_id}, 122 | {"$set": {"active_group" : prvs_group_id}} 123 | ) 124 | else: 125 | mycol.update_one( 126 | {'_id': user_id}, 127 | {"$set": {"active_group" : None}} 128 | ) 129 | return True 130 | except Exception as e: 131 | logger.exception(f'Some error occurred! {e}', exc_info=True) 132 | return False 133 | 134 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | # @MrMNTG @MusammilN 2 | #please give credits https://github.com/MN-BOTS/ShobanaFilterBot 3 | import logging 4 | import logging.config 5 | import os 6 | import sys 7 | import asyncio 8 | from datetime import date, datetime 9 | import pytz 10 | import aiohttp 11 | 12 | # Get logging configurations 13 | logging.config.fileConfig('logging.conf') 14 | logging.getLogger().setLevel(logging.INFO) 15 | logging.getLogger("pyrogram").setLevel(logging.ERROR) 16 | logging.getLogger("imdbpy").setLevel(logging.ERROR) 17 | logging.getLogger("asyncio").setLevel(logging.CRITICAL - 1) 18 | 19 | import tgcrypto 20 | from pyrogram import Client, __version__ 21 | from pyrogram.raw.all import layer 22 | from database.ia_filterdb import Media 23 | from database.users_chats_db import db 24 | from info import SESSION, API_ID, API_HASH, BOT_TOKEN, LOG_STR, LOG_CHANNEL, KEEP_ALIVE_URL, DEFAULT_AUTH_CHANNELS 25 | from utils import temp 26 | from typing import Union, Optional, AsyncGenerator 27 | from pyrogram import types 28 | from Script import script 29 | from os import environ 30 | from aiohttp import web as webserver 31 | 32 | # Peer ID invalid fix 33 | from pyrogram import utils as pyroutils 34 | pyroutils.MIN_CHAT_ID = -999999999999 35 | pyroutils.MIN_CHANNEL_ID = -100999999999999 36 | 37 | from plugins.webcode import bot_run 38 | 39 | PORT_CODE = environ.get("PORT", "8080") 40 | 41 | 42 | # ✅ Add this block 43 | async def preload_auth_channels(): 44 | if not await db.get_auth_channels(): 45 | await db.set_auth_channels(DEFAULT_AUTH_CHANNELS) 46 | logging.info("Set default AUTH_CHANNELs in DB.") 47 | 48 | async def keep_alive(): 49 | """Send a request every 111 seconds to keep the bot alive (if required).""" 50 | async with aiohttp.ClientSession() as session: 51 | while True: 52 | try: 53 | await session.get(KEEP_ALIVE_URL) 54 | logging.info("Sent keep-alive request.") 55 | except Exception as e: 56 | logging.error(f"Keep-alive request failed: {e}") 57 | await asyncio.sleep(111) 58 | 59 | 60 | class Bot(Client): 61 | 62 | def __init__(self): 63 | super().__init__( 64 | name=SESSION, 65 | api_id=API_ID, 66 | api_hash=API_HASH, 67 | bot_token=BOT_TOKEN, 68 | workers=50, 69 | plugins={"root": "plugins"}, 70 | sleep_threshold=5, 71 | ) 72 | 73 | async def kulasthree(self): 74 | while True: 75 | await asyncio.sleep(24 * 60 * 60) 76 | logging.info("🔄 Bot is restarting") 77 | await self.send_message(chat_id=LOG_CHANNEL, text="🔄 Bot is restarting ...") 78 | os.execl(sys.executable, sys.executable, *sys.argv) 79 | 80 | async def start(self, **kwargs): 81 | b_users, b_chats = await db.get_banned() 82 | temp.BANNED_USERS = b_users 83 | temp.BANNED_CHATS = b_chats 84 | await super().start() 85 | await Media.ensure_indexes() 86 | me = await self.get_me() 87 | temp.ME = me.id 88 | temp.U_NAME = me.username 89 | temp.B_NAME = me.first_name 90 | self.username = '@' + me.username 91 | 92 | # ✅ preload auth channels from info.py if DB is empty 93 | await preload_auth_channels() 94 | 95 | logging.info(f"{me.first_name} running on Pyrogram v{__version__} (Layer {layer}) started on {me.username}.") 96 | logging.info(LOG_STR) 97 | await self.send_message(chat_id=LOG_CHANNEL, text=script.RESTART_TXT) 98 | 99 | print("mntg4u") 100 | 101 | tz = pytz.timezone('Asia/Kolkata') 102 | today = date.today() 103 | now = datetime.now(tz) 104 | time = now.strftime("%H:%M:%S %p") 105 | await self.send_message(chat_id=LOG_CHANNEL, text=script.RESTART_GC_TXT.format(today, time)) 106 | 107 | asyncio.create_task(self.kulasthree()) 108 | asyncio.create_task(keep_alive()) 109 | 110 | client = webserver.AppRunner(await bot_run()) 111 | await client.setup() 112 | bind_address = "0.0.0.0" 113 | await webserver.TCPSite(client, bind_address, PORT_CODE).start() 114 | 115 | async def stop(self, *args): 116 | await super().stop() 117 | logging.info("Bot stopped. Bye.") 118 | 119 | async def iter_messages( 120 | self, 121 | chat_id: Union[int, str], 122 | limit: int, 123 | offset: int = 0, 124 | ) -> Optional[AsyncGenerator["types.Message", None]]: 125 | current = offset 126 | while True: 127 | new_diff = min(200, limit - current) 128 | if new_diff <= 0: 129 | return 130 | messages = await self.get_messages(chat_id, list(range(current, current + new_diff + 1))) 131 | for message in messages: 132 | yield message 133 | current += 1 134 | 135 | 136 | app = Bot() 137 | app.run() 138 | -------------------------------------------------------------------------------- /plugins/inline.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pyrogram import Client, emoji, filters 3 | from pyrogram.errors.exceptions.bad_request_400 import QueryIdInvalid 4 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, InlineQueryResultCachedDocument, InlineQuery 5 | from database.ia_filterdb import get_search_results 6 | from utils import is_subscribed, get_size, temp 7 | from info import CACHE_TIME, AUTH_USERS, CUSTOM_FILE_CAPTION 8 | from utils import create_invite_links 9 | 10 | logger = logging.getLogger(__name__) 11 | cache_time = 0 if AUTH_USERS else CACHE_TIME 12 | 13 | async def inline_users(query: InlineQuery): 14 | if AUTH_USERS: 15 | if query.from_user and query.from_user.id in AUTH_USERS: 16 | return True 17 | else: 18 | return False 19 | if query.from_user and query.from_user.id not in temp.BANNED_USERS: 20 | return True 21 | return False 22 | 23 | @Client.on_inline_query() 24 | async def answer(bot, query): 25 | """Show search results for given inline query""" 26 | 27 | if not await inline_users(query): 28 | await query.answer(results=[], 29 | cache_time=0, 30 | switch_pm_text='okDa', 31 | switch_pm_parameter="hehe") 32 | return 33 | 34 | if not await is_subscribed(query.from_user.id, bot): 35 | invite_links = await create_invite_links(bot) 36 | first_link = next(iter(invite_links.values()), None) 37 | 38 | if first_link: 39 | await query.answer( 40 | results=[], 41 | cache_time=0, 42 | switch_pm_text="📢 Please join the updates channel to use this bot", 43 | switch_pm_parameter="subscribe" 44 | ) 45 | else: 46 | await query.answer( 47 | results=[], 48 | cache_time=0, 49 | switch_pm_text="⚠️ Bot owner hasn't set up the required channels properly.", 50 | switch_pm_parameter="error" 51 | ) 52 | return 53 | 54 | results = [] 55 | if '|' in query.query: 56 | string, file_type = query.query.split('|', maxsplit=1) 57 | string = string.strip() 58 | file_type = file_type.strip().lower() 59 | else: 60 | string = query.query.strip() 61 | file_type = None 62 | 63 | offset = int(query.offset or 0) 64 | reply_markup = get_reply_markup(query=string) 65 | files, next_offset, total = await get_search_results(string, 66 | file_type=file_type, 67 | max_results=10, 68 | offset=offset) 69 | 70 | for file in files: 71 | title=file.file_name 72 | size=get_size(file.file_size) 73 | f_caption=file.caption 74 | if CUSTOM_FILE_CAPTION: 75 | try: 76 | f_caption=CUSTOM_FILE_CAPTION.format(file_name= '' if title is None else title, file_size='' if size is None else size, file_caption='' if f_caption is None else f_caption) 77 | except Exception as e: 78 | logger.exception(e) 79 | f_caption=f_caption 80 | if f_caption is None: 81 | f_caption = f"{file.file_name}" 82 | results.append( 83 | InlineQueryResultCachedDocument( 84 | title=file.file_name, 85 | document_file_id=file.file_id, 86 | caption=f_caption, 87 | description=f'Size: {get_size(file.file_size)}\nType: {file.file_type}', 88 | reply_markup=reply_markup)) 89 | 90 | if results: 91 | switch_pm_text = f"{emoji.FILE_FOLDER} Results " 92 | if string: 93 | switch_pm_text += f" for {string}" 94 | try: 95 | await query.answer(results=results, 96 | is_personal = True, 97 | cache_time=cache_time, 98 | switch_pm_text=switch_pm_text, 99 | switch_pm_parameter="start", 100 | next_offset=str(next_offset)) 101 | except QueryIdInvalid: 102 | pass 103 | except Exception as e: 104 | logging.exception(str(e)) 105 | else: 106 | switch_pm_text = f'{emoji.CROSS_MARK} No results' 107 | if string: 108 | switch_pm_text += f' for "{string}"' 109 | 110 | await query.answer(results=[], 111 | is_personal = True, 112 | cache_time=cache_time, 113 | switch_pm_text=switch_pm_text, 114 | switch_pm_parameter="okay") 115 | 116 | 117 | def get_reply_markup(query): 118 | buttons = [ 119 | [ 120 | InlineKeyboardButton('Search again', switch_inline_query_current_chat=query) 121 | ] 122 | ] 123 | return InlineKeyboardMarkup(buttons) 124 | 125 | 126 | -------------------------------------------------------------------------------- /info.py: -------------------------------------------------------------------------------- 1 | import re 2 | from os import environ 3 | from Script import script 4 | from time import time 5 | 6 | id_pattern = re.compile(r'^.\d+$') 7 | 8 | def is_enabled(value, default): 9 | if value.lower() in ["true", "yes", "1", "enable", "y"]: 10 | return True 11 | elif value.lower() in ["false", "no", "0", "disable", "n"]: 12 | return False 13 | else: 14 | return default 15 | 16 | #Bot information 17 | SESSION = environ.get('SESSION', 'Media_search') 18 | API_ID = int(environ.get('API_ID', '')) 19 | API_HASH = environ.get('API_HASH', '') 20 | BOT_TOKEN = environ.get('BOT_TOKEN', '') 21 | 22 | # Keep-Alive URL 23 | KEEP_ALIVE_URL = environ.get("KEEP_ALIVE_URL", "https://burning-brittney-leech2-3bc21fb5.koyeb.app/") # <-- Add this line 24 | #hyper link 25 | HYPER_MODE = bool(environ.get('HYPER_MODE', False)) 26 | #request fsub 27 | REQUEST_FSUB_MODE = bool(environ.get('REQUEST_FSUB_MODE', True)) 28 | # Bot settings 29 | BOT_START_TIME = time() 30 | CACHE_TIME = int(environ.get('CACHE_TIME', 300)) 31 | USE_CAPTION_FILTER = bool(environ.get('USE_CAPTION_FILTER', False)) 32 | PICS = (environ.get('PICS', 'https://graph.org/file/2ed90a79eb533d86f8a0f.jpg https://graph.org/file/a0da24dacf4b7bec376a3.jpg https://graph.org/file/457aa9d0e485925088be6.jpg https://graph.org/file/041f7b57c6950070ba16e.jpg https://graph.org/file/f36511f6042d74d95b5df.jpg https://graph.org/file/a30d30b3bc49bd8745533.jpg https://graph.org/file/ce71502cf614059ce1de5.jpg')).split() 33 | 34 | # Admins, Channels & Users 35 | ADMINS = [int(admin) if id_pattern.search(admin) else admin for admin in environ.get('ADMINS', '1892771262').split()] 36 | CHANNELS = [int(ch) if id_pattern.search(ch) else ch for ch in environ.get('CHANNELS', '-1002490892111 -1002097504396').split()] 37 | auth_users = [int(user) if id_pattern.search(user) else user for user in environ.get('AUTH_USERS', '').split()] 38 | AUTH_USERS = (auth_users + ADMINS) if auth_users else [] 39 | auth_grp = environ.get('AUTH_GROUP') 40 | DEFAULT_AUTH_CHANNELS = [int(x) for x in environ.get("AUTH_CHANNEL", "").split() if x.lstrip('-').isdigit()] 41 | AUTH_GROUPS = [int(ch) for ch in auth_grp.split()] if auth_grp else None 42 | 43 | # MongoDB information 44 | DATABASE_URI = environ.get('DATABASE_URI', "") 45 | DATABASE_NAME = environ.get('DATABASE_NAME', "Cluster0") 46 | COLLECTION_NAME = environ.get('COLLECTION_NAME', 'mn_files') 47 | 48 | # File Channel Settings 49 | FILE_CHANNELS = [int(ch) for ch in environ.get('FILE_CHANNELS', '-1002831639976 -1002607076908 -1002869981026').split()] 50 | FILE_CHANNEL_SENDING_MODE = is_enabled(environ.get('FILE_CHANNEL_SENDING_MODE', 'False'), False) 51 | FILE_AUTO_DELETE_SECONDS = int(environ.get('FILE_AUTO_DELETE_SECONDS', 3600)) # Default: 1 hour 52 | 53 | # Others 54 | LOG_CHANNEL = int(environ.get('LOG_CHANNEL', '-1002704640995')) 55 | SUPPORT_CHAT = environ.get('SUPPORT_CHAT', 'mnbots_support') 56 | P_TTI_SHOW_OFF = is_enabled((environ.get('P_TTI_SHOW_OFF', 'False')), False) 57 | IMDB = is_enabled((environ.get('IMDB', 'False')), False) 58 | SINGLE_BUTTON = is_enabled((environ.get('SINGLE_BUTTON', 'True')), True) 59 | CUSTOM_FILE_CAPTION = environ.get("CUSTOM_FILE_CAPTION", f"{script.CUSTOM_FILE_CAPTION}") 60 | BATCH_FILE_CAPTION = environ.get("BATCH_FILE_CAPTION", "📂 File Name: {file_name}\n\n ♻ File Size:{file_size} \n\n Latest Movies - ") 61 | IMDB_TEMPLATE = environ.get("IMDB_TEMPLATE", "🏷 𝖳𝗂𝗍𝗅𝖾: {title} \n🔮 𝖸𝖾𝖺𝗋: {year} \n⭐️ 𝖱𝖺𝗍𝗂𝗇𝗀𝗌: {rating}/ 10 \n🎭 𝖦𝖾𝗇𝖾𝗋𝗌: {genres}") 62 | LONG_IMDB_DESCRIPTION = is_enabled(environ.get("LONG_IMDB_DESCRIPTION", "False"), False) 63 | SPELL_CHECK_REPLY = is_enabled(environ.get("SPELL_CHECK_REPLY", "True"), True) 64 | MAX_LIST_ELM = environ.get("MAX_LIST_ELM", None) 65 | INDEX_REQ_CHANNEL = int(environ.get('INDEX_REQ_CHANNEL', LOG_CHANNEL)) 66 | FILE_STORE_CHANNEL = [int(ch) for ch in (environ.get('FILE_STORE_CHANNEL', '')).split()] 67 | MELCOW_NEW_USERS = is_enabled((environ.get('MELCOW_NEW_USERS', "True")), True) 68 | PROTECT_CONTENT = is_enabled((environ.get('PROTECT_CONTENT', "False")), False) 69 | PUBLIC_FILE_STORE = is_enabled((environ.get('PUBLIC_FILE_STORE', "False")), True) 70 | 71 | LOG_STR = "Current Cusomized Configurations are:-\n" 72 | LOG_STR += ("IMDB Results are enabled, Bot will be showing imdb details for you queries.\n" if IMDB else "IMBD Results are disabled.\n") 73 | LOG_STR += ("P_TTI_SHOW_OFF found , Users will be redirected to send /start to Bot PM instead of sending file file directly\n" if P_TTI_SHOW_OFF else "P_TTI_SHOW_OFF is disabled files will be send in PM, instead of sending start.\n") 74 | LOG_STR += ("SINGLE_BUTTON is Found, filename and files size will be shown in a single button instead of two separate buttons\n" if SINGLE_BUTTON else "SINGLE_BUTTON is disabled , filename and file_sixe will be shown as different buttons\n") 75 | LOG_STR += (f"CUSTOM_FILE_CAPTION enabled with value {CUSTOM_FILE_CAPTION}, your files will be send along with this customized caption.\n" if CUSTOM_FILE_CAPTION else "No CUSTOM_FILE_CAPTION Found, Default captions of file will be used.\n") 76 | LOG_STR += ("Long IMDB storyline enabled." if LONG_IMDB_DESCRIPTION else "LONG_IMDB_DESCRIPTION is disabled , Plot will be shorter.\n") 77 | LOG_STR += ("Spell Check Mode Is Enabled, bot will be suggesting related movies if movie not found\n" if SPELL_CHECK_REPLY else "SPELL_CHECK_REPLY Mode disabled\n") 78 | LOG_STR += (f"MAX_LIST_ELM Found, long list will be shortened to first {MAX_LIST_ELM} elements\n" if MAX_LIST_ELM else "Full List of casts and crew will be shown in imdb template, restrict them by adding a value to MAX_LIST_ELM\n") 79 | LOG_STR += f"Your current IMDB template is {IMDB_TEMPLATE}" 80 | -------------------------------------------------------------------------------- /database/users_chats_db.py: -------------------------------------------------------------------------------- 1 | # https://github.com/odysseusmax/animated-lamp/blob/master/bot/database/database.py 2 | # @MrMNTG @MusammilN 3 | #please give credits https://github.com/MN-BOTS/ShobanaFilterBot 4 | import motor.motor_asyncio 5 | from info import DATABASE_NAME, DATABASE_URI, IMDB, IMDB_TEMPLATE, MELCOW_NEW_USERS, P_TTI_SHOW_OFF, SINGLE_BUTTON, SPELL_CHECK_REPLY, PROTECT_CONTENT 6 | 7 | # @MrMNTG @MusammilN 8 | #please give credits https://github.com/MN-BOTS/ShobanaFilterBot 9 | 10 | class Database: 11 | 12 | def __init__(self, uri, database_name): 13 | self._client = motor.motor_asyncio.AsyncIOMotorClient(uri) 14 | self.db = self._client[database_name] 15 | self.col = self.db.users 16 | self.grp = self.db.groups 17 | self.config = self.db.config 18 | 19 | 20 | def new_user(self, id, name): 21 | return dict( 22 | id = id, 23 | name = name, 24 | ban_status=dict( 25 | is_banned=False, 26 | ban_reason="", 27 | ), 28 | ) 29 | 30 | 31 | def new_group(self, id, title): 32 | return dict( 33 | id = id, 34 | title = title, 35 | chat_status=dict( 36 | is_disabled=False, 37 | reason="", 38 | ), 39 | ) 40 | 41 | async def add_user(self, id, name): 42 | user = self.new_user(id, name) 43 | await self.col.insert_one(user) 44 | 45 | async def is_user_exist(self, id): 46 | user = await self.col.find_one({'id':int(id)}) 47 | return bool(user) 48 | 49 | async def total_users_count(self): 50 | count = await self.col.count_documents({}) 51 | return count 52 | 53 | async def remove_ban(self, id): 54 | ban_status = dict( 55 | is_banned=False, 56 | ban_reason='' 57 | ) 58 | await self.col.update_one({'id': id}, {'$set': {'ban_status': ban_status}}) 59 | 60 | async def ban_user(self, user_id, ban_reason="No Reason"): 61 | ban_status = dict( 62 | is_banned=True, 63 | ban_reason=ban_reason 64 | ) 65 | await self.col.update_one({'id': user_id}, {'$set': {'ban_status': ban_status}}) 66 | 67 | async def get_ban_status(self, id): 68 | default = dict( 69 | is_banned=False, 70 | ban_reason='' 71 | ) 72 | user = await self.col.find_one({'id':int(id)}) 73 | if not user: 74 | return default 75 | return user.get('ban_status', default) 76 | 77 | async def get_all_users(self): 78 | return self.col.find({}) 79 | 80 | 81 | async def delete_user(self, user_id): 82 | await self.col.delete_many({'id': int(user_id)}) 83 | 84 | 85 | async def get_banned(self): 86 | users = self.col.find({'ban_status.is_banned': True}) 87 | chats = self.grp.find({'chat_status.is_disabled': True}) 88 | b_chats = [chat['id'] async for chat in chats] 89 | b_users = [user['id'] async for user in users] 90 | return b_users, b_chats 91 | 92 | 93 | 94 | async def add_chat(self, chat, title): 95 | chat = self.new_group(chat, title) 96 | await self.grp.insert_one(chat) 97 | 98 | 99 | async def get_chat(self, chat): 100 | chat = await self.grp.find_one({'id':int(chat)}) 101 | return False if not chat else chat.get('chat_status') 102 | 103 | 104 | async def re_enable_chat(self, id): 105 | chat_status=dict( 106 | is_disabled=False, 107 | reason="", 108 | ) 109 | await self.grp.update_one({'id': int(id)}, {'$set': {'chat_status': chat_status}}) 110 | 111 | async def update_settings(self, id, settings): 112 | await self.grp.update_one({'id': int(id)}, {'$set': {'settings': settings}}) 113 | 114 | 115 | async def get_settings(self, id): 116 | default = { 117 | 'button': SINGLE_BUTTON, 118 | 'botpm': P_TTI_SHOW_OFF, 119 | 'file_secure': PROTECT_CONTENT, 120 | 'imdb': IMDB, 121 | 'spell_check': SPELL_CHECK_REPLY, 122 | 'welcome': MELCOW_NEW_USERS, 123 | 'template': IMDB_TEMPLATE 124 | } 125 | chat = await self.grp.find_one({'id':int(id)}) 126 | if chat: 127 | return chat.get('settings', default) 128 | return default 129 | 130 | 131 | async def disable_chat(self, chat, reason="No Reason"): 132 | chat_status=dict( 133 | is_disabled=True, 134 | reason=reason, 135 | ) 136 | await self.grp.update_one({'id': int(chat)}, {'$set': {'chat_status': chat_status}}) 137 | 138 | 139 | async def total_chat_count(self): 140 | count = await self.grp.count_documents({}) 141 | return count 142 | 143 | 144 | async def get_all_chats(self): 145 | return self.grp.find({}) 146 | 147 | async def set_auth_channels(self, channels: list[int]): 148 | # Store the list of auth channel IDs in a singleton document 149 | await self.config.update_one( 150 | {"_id": "auth_channels"}, 151 | {"$set": {"channels": channels}}, 152 | upsert=True, 153 | ) 154 | 155 | async def get_auth_channels(self) -> list[int]: 156 | doc = await self.config.find_one({"_id": "auth_channels"}) 157 | if doc and "channels" in doc: 158 | return doc["channels"] 159 | return [] 160 | 161 | async def get_db_size(self): 162 | return (await self.db.command("dbstats"))['dataSize'] 163 | 164 | 165 | db = Database(DATABASE_URI, DATABASE_NAME) 166 | 167 | # @MrMNTG @MusammilN 168 | #please give credits https://github.com/MN-BOTS/ShobanaFilterBot 169 | -------------------------------------------------------------------------------- /plugins/connection.py: -------------------------------------------------------------------------------- 1 | from pyrogram import filters, Client, enums 2 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup 3 | from database.connections_mdb import add_connection, all_connections, if_active, delete_connection 4 | from info import ADMINS 5 | import logging 6 | 7 | logger = logging.getLogger(__name__) 8 | logger.setLevel(logging.ERROR) 9 | 10 | 11 | @Client.on_message((filters.private | filters.group) & filters.command('connect')) 12 | async def addconnection(client, message): 13 | userid = message.from_user.id if message.from_user else None 14 | if not userid: 15 | return await message.reply(f"You are anonymous admin. Use /connect {message.chat.id} in PM") 16 | chat_type = message.chat.type 17 | 18 | if chat_type == enums.ChatType.PRIVATE: 19 | try: 20 | cmd, group_id = message.text.split(" ", 1) 21 | except: 22 | await message.reply_text( 23 | "Enter in correct format!\n\n" 24 | "/connect groupid\n\n" 25 | "Get your Group id by adding this bot to your group and use /id", 26 | quote=True 27 | ) 28 | return 29 | 30 | elif chat_type in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]: 31 | group_id = message.chat.id 32 | 33 | try: 34 | st = await client.get_chat_member(group_id, userid) 35 | if ( 36 | st.status != enums.ChatMemberStatus.ADMINISTRATOR 37 | and st.status != enums.ChatMemberStatus.OWNER 38 | and userid not in ADMINS 39 | ): 40 | await message.reply_text("You should be an admin in Given group!", quote=True) 41 | return 42 | except Exception as e: 43 | logger.exception(e) 44 | await message.reply_text( 45 | "Invalid Group ID!\n\nIf correct, Make sure I'm present in your group!!", 46 | quote=True, 47 | ) 48 | 49 | return 50 | try: 51 | st = await client.get_chat_member(group_id, "me") 52 | if st.status == enums.ChatMemberStatus.ADMINISTRATOR: 53 | ttl = await client.get_chat(group_id) 54 | title = ttl.title 55 | 56 | addcon = await add_connection(str(group_id), str(userid)) 57 | if addcon: 58 | await message.reply_text( 59 | f"Successfully connected to **{title}**\nNow manage your group from my pm !", 60 | quote=True, 61 | parse_mode=enums.ParseMode.MARKDOWN 62 | ) 63 | if chat_type in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]: 64 | await client.send_message( 65 | userid, 66 | f"Connected to **{title}** !", 67 | parse_mode=enums.ParseMode.MARKDOWN 68 | ) 69 | else: 70 | await message.reply_text( 71 | "You're already connected to this chat!", 72 | quote=True 73 | ) 74 | else: 75 | await message.reply_text("Add me as an admin in group", quote=True) 76 | except Exception as e: 77 | logger.exception(e) 78 | await message.reply_text('Some error occurred! Try again later.', quote=True) 79 | return 80 | 81 | 82 | @Client.on_message((filters.private | filters.group) & filters.command('disconnect')) 83 | async def deleteconnection(client, message): 84 | userid = message.from_user.id if message.from_user else None 85 | if not userid: 86 | return await message.reply(f"You are anonymous admin. Use /connect {message.chat.id} in PM") 87 | chat_type = message.chat.type 88 | 89 | if chat_type == enums.ChatType.PRIVATE: 90 | await message.reply_text("Run /connections to view or disconnect from groups!", quote=True) 91 | 92 | elif chat_type in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]: 93 | group_id = message.chat.id 94 | 95 | st = await client.get_chat_member(group_id, userid) 96 | if ( 97 | st.status != enums.ChatMemberStatus.ADMINISTRATOR 98 | and st.status != enums.ChatMemberStatus.OWNER 99 | and str(userid) not in ADMINS 100 | ): 101 | return 102 | 103 | delcon = await delete_connection(str(userid), str(group_id)) 104 | if delcon: 105 | await message.reply_text("Successfully disconnected from this chat", quote=True) 106 | else: 107 | await message.reply_text("This chat isn't connected to me!\nDo /connect to connect.", quote=True) 108 | 109 | 110 | @Client.on_message(filters.private & filters.command(["connections"])) 111 | async def connections(client, message): 112 | userid = message.from_user.id 113 | 114 | groupids = await all_connections(str(userid)) 115 | if groupids is None: 116 | await message.reply_text( 117 | "There are no active connections!! Connect to some groups first.", 118 | quote=True 119 | ) 120 | return 121 | buttons = [] 122 | for groupid in groupids: 123 | try: 124 | ttl = await client.get_chat(int(groupid)) 125 | title = ttl.title 126 | active = await if_active(str(userid), str(groupid)) 127 | act = " - ACTIVE" if active else "" 128 | buttons.append( 129 | [ 130 | InlineKeyboardButton( 131 | text=f"{title}{act}", callback_data=f"groupcb:{groupid}:{act}" 132 | ) 133 | ] 134 | ) 135 | except: 136 | pass 137 | if buttons: 138 | await message.reply_text( 139 | "Your connected group details ;\n\n", 140 | reply_markup=InlineKeyboardMarkup(buttons), 141 | quote=True 142 | ) 143 | else: 144 | await message.reply_text( 145 | "There are no active connections!! Connect to some groups first.", 146 | quote=True 147 | ) 148 | -------------------------------------------------------------------------------- /plugins/mn_deletefiles.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import asyncio 3 | import re 4 | from pyrogram import Client, filters, enums 5 | from pyrogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery 6 | 7 | # Ensure this import path is correct for your bot's setup 8 | from database.ia_filterdb import Media 9 | 10 | # You MUST define ADMINS in your bot's config (e.g., info.py) 11 | from info import ADMINS 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | # Constants for Batch Deletion 16 | BATCH_SIZE = 20 # Number of files to delete in each batch 17 | SLEEP_TIME = 2 # Seconds to wait between batches to avoid overloading the DB/bot 18 | 19 | 20 | @Client.on_message(filters.command("deletefiles") & filters.user(ADMINS)) 21 | async def deletemultiplefiles(bot: Client, message: Message): 22 | """ 23 | Handles the /deletefiles command to prompt for confirmation before deleting 24 | files from the database based on a keyword in their filenames. 25 | This command is restricted to private chat with the bot for safety. 26 | """ 27 | if message.chat.type != enums.ChatType.PRIVATE: 28 | return await message.reply_text( 29 | f"Hey {message.from_user.mention}, this command won't work in groups. It only works in my PM!", 30 | parse_mode=enums.ParseMode.HTML 31 | ) 32 | 33 | try: 34 | # Extract the keyword from the command (e.g., '/deletefiles keyword') 35 | keyword = message.text.split(" ", 1)[1].strip() 36 | if not keyword: 37 | return await message.reply_text( 38 | f"Hey {message.from_user.mention}, give me a keyword along with the command to delete files.\n" 39 | "Usage: `/deletefiles `\nExample: `/deletefiles unwanted_movie`", 40 | parse_mode=enums.ParseMode.HTML 41 | ) 42 | except IndexError: # Catches cases where no keyword is provided after /deletefiles 43 | return await message.reply_text( 44 | f"Hey {message.from_user.mention}, give me a keyword along with the command to delete files.\n" 45 | "Usage: `/deletefiles `\nExample: `/deletefiles unwanted_movie`", 46 | parse_mode=enums.ParseMode.HTML 47 | ) 48 | 49 | # Create inline keyboard for confirmation 50 | confirm_button = InlineKeyboardButton("Yes, Continue !", callback_data=f"confirm_delete_files#{keyword}") 51 | abort_button = InlineKeyboardButton("No, Abort operation !", callback_data="close_message") 52 | 53 | markup = InlineKeyboardMarkup([[confirm_button], [abort_button]]) 54 | 55 | await message.reply_text( 56 | text=f"Are you sure? Do you want to continue deleting files with the keyword: '{keyword}'?\n\n" 57 | "Note: This is a destructive action and cannot be undone!", 58 | reply_markup=markup, 59 | parse_mode=enums.ParseMode.HTML, 60 | quote=True 61 | ) 62 | 63 | @Client.on_callback_query(filters.regex(r'^confirm_delete_files#')) 64 | async def confirm_and_delete_files_by_keyword(bot: Client, query: CallbackQuery): 65 | """ 66 | Handles the callback query from the /deletefiles confirmation message. 67 | Performs batch deletion of files from the Media collection. 68 | """ 69 | await query.answer() # Acknowledge the callback query 70 | 71 | # Extract the keyword from the callback_data 72 | command_prefix, keyword = query.data.split("#", 1) 73 | 74 | # Build regex to match filenames containing the keyword. 75 | raw_pattern = r'(\b|[\.\+\-_])' + re.escape(keyword) + r'(\b|[\.\+\-_])' 76 | regex = re.compile(raw_pattern, flags=re.IGNORECASE) 77 | 78 | # Filter query targets the 'file_name' field 79 | filter_query = {'file_name': regex} 80 | 81 | await query.message.edit_text(f"🔍 Searching for files containing **'{keyword}'** in their filenames...", parse_mode=enums.ParseMode.HTML) 82 | 83 | # Get the initial count of matching documents 84 | initial_count = await Media.count_documents(filter_query) 85 | if initial_count == 0: 86 | return await query.message.edit_text( 87 | f"❌ No files found with **'{keyword}'** in their filenames. Deletion aborted.", 88 | parse_mode=enums.ParseMode.HTML 89 | ) 90 | 91 | await query.message.edit_text( 92 | f"Found `{initial_count}` files containing **'{keyword}'** in their filenames. Starting batch deletion...", 93 | parse_mode=enums.ParseMode.HTML 94 | ) 95 | 96 | deleted_count = 0 97 | # Loop to delete in batches 98 | while True: 99 | # Fetch IDs of documents to delete in the current batch. 100 | documents_to_delete = await Media.collection.find(filter_query, {"_id": 1}).limit(BATCH_SIZE).to_list(length=BATCH_SIZE) 101 | 102 | if not documents_to_delete: 103 | break # No more documents left to delete 104 | 105 | # Create a list of '_id' values for the current batch 106 | ids_to_delete = [doc["_id"] for doc in documents_to_delete] 107 | 108 | # Perform the batch deletion using the collected IDs 109 | batch_result = await Media.collection.delete_many({"_id": {"$in": ids_to_delete}}) 110 | 111 | deleted_in_batch = batch_result.deleted_count 112 | deleted_count += deleted_in_batch 113 | 114 | await query.message.edit_text( 115 | f"🗑️ Deleted `{deleted_in_batch}` files in current batch. Total deleted: `{deleted_count}` / `{initial_count}`", 116 | parse_mode=enums.ParseMode.HTML 117 | ) 118 | 119 | if deleted_count >= initial_count or deleted_in_batch == 0: 120 | break # Exit loop if all found files are deleted or no more were deleted in the last batch 121 | 122 | await asyncio.sleep(SLEEP_TIME) # Wait before the next batch 123 | 124 | await query.message.edit_text( 125 | f"✅ Finished deletion process for keyword: **'{keyword}'**. Total files deleted: `{deleted_count}` from database.", 126 | parse_mode=enums.ParseMode.HTML 127 | ) 128 | 129 | @Client.on_callback_query(filters.regex(r'^close_message$')) 130 | async def close_message(bot: Client, query: CallbackQuery): 131 | """ 132 | Handles the 'close_message' callback to simply delete the message. 133 | """ 134 | await query.answer() 135 | await query.message.delete() 136 | -------------------------------------------------------------------------------- /database/ia_filterdb.py: -------------------------------------------------------------------------------- 1 | # @MrMNTG @MusammilN 2 | #please give credits https://github.com/MN-BOTS/ShobanaFilterBot 3 | import logging 4 | from struct import pack 5 | import re 6 | import base64 7 | from pyrogram.file_id import FileId 8 | from pymongo.errors import DuplicateKeyError 9 | from umongo import Instance, Document, fields 10 | from motor.motor_asyncio import AsyncIOMotorClient 11 | from marshmallow.exceptions import ValidationError 12 | from info import DATABASE_URI, DATABASE_NAME, COLLECTION_NAME, USE_CAPTION_FILTER 13 | 14 | logger = logging.getLogger(__name__) 15 | logger.setLevel(logging.INFO) 16 | 17 | 18 | client = AsyncIOMotorClient(DATABASE_URI) 19 | db = client[DATABASE_NAME] 20 | instance = Instance.from_db(db) 21 | 22 | @instance.register 23 | class Media(Document): 24 | file_id = fields.StrField(attribute='_id') 25 | file_ref = fields.StrField(allow_none=True) 26 | file_name = fields.StrField(required=True) 27 | file_size = fields.IntField(required=True) 28 | file_type = fields.StrField(allow_none=True) 29 | mime_type = fields.StrField(allow_none=True) 30 | caption = fields.StrField(allow_none=True) 31 | 32 | class Meta: 33 | indexes = ('$file_name', ) 34 | collection_name = COLLECTION_NAME 35 | 36 | 37 | async def save_file(media): 38 | """Save file in database""" 39 | 40 | # TODO: Find better way to get same file_id for same media to avoid duplicates 41 | file_id, file_ref = unpack_new_file_id(media.file_id) 42 | file_name = re.sub(r"(_|\-|\.|\+)", " ", str(media.file_name)) 43 | try: 44 | file = Media( 45 | file_id=file_id, 46 | file_ref=file_ref, 47 | file_name=file_name, 48 | file_size=media.file_size, 49 | file_type=media.file_type, 50 | mime_type=media.mime_type, 51 | caption=media.caption.html if media.caption else None, 52 | ) 53 | except ValidationError: 54 | logger.exception('Error occurred while saving file in database') 55 | return False, 2 56 | else: 57 | try: 58 | await file.commit() 59 | except DuplicateKeyError: 60 | logger.warning( 61 | f'{getattr(media, "file_name", "NO_FILE")} is already saved in database' 62 | ) 63 | 64 | return False, 0 65 | else: 66 | logger.info(f'{getattr(media, "file_name", "NO_FILE")} is saved to database') 67 | return True, 1 68 | 69 | 70 | 71 | async def get_search_results(query, file_type=None, max_results=10, offset=0, filter=False): 72 | """For given query return (results, next_offset)""" 73 | 74 | query = query.strip() 75 | #if filter: 76 | #better ? 77 | #query = query.replace(' ', r'(\s|\.|\+|\-|_)') 78 | #raw_pattern = r'(\s|_|\-|\.|\+)' + query + r'(\s|_|\-|\.|\+)' 79 | if not query: 80 | raw_pattern = '.' 81 | elif ' ' not in query: 82 | raw_pattern = r'(\b|[\.\+\-_])' + query + r'(\b|[\.\+\-_])' 83 | else: 84 | raw_pattern = query.replace(' ', r'.*[\s\.\+\-_]') 85 | 86 | try: 87 | regex = re.compile(raw_pattern, flags=re.IGNORECASE) 88 | except: 89 | return [] 90 | 91 | if USE_CAPTION_FILTER: 92 | filter = {'$or': [{'file_name': regex}, {'caption': regex}]} 93 | else: 94 | filter = {'file_name': regex} 95 | 96 | if file_type: 97 | filter['file_type'] = file_type 98 | 99 | total_results = await Media.count_documents(filter) 100 | next_offset = offset + max_results 101 | 102 | if next_offset > total_results: 103 | next_offset = '' 104 | 105 | cursor = Media.find(filter) 106 | # Sort by recent 107 | cursor.sort('$natural', -1) 108 | # Slice files according to offset and max results 109 | cursor.skip(offset).limit(max_results) 110 | # Get list of files 111 | files = await cursor.to_list(length=max_results) 112 | 113 | return files, next_offset, total_results 114 | 115 | 116 | 117 | async def get_file_details(query): 118 | filter = {'file_id': query} 119 | cursor = Media.find(filter) 120 | filedetails = await cursor.to_list(length=1) 121 | return filedetails 122 | 123 | 124 | def encode_file_id(s: bytes) -> str: 125 | r = b"" 126 | n = 0 127 | 128 | for i in s + bytes([22]) + bytes([4]): 129 | if i == 0: 130 | n += 1 131 | else: 132 | if n: 133 | r += b"\x00" + bytes([n]) 134 | n = 0 135 | 136 | r += bytes([i]) 137 | 138 | return base64.urlsafe_b64encode(r).decode().rstrip("=") 139 | 140 | 141 | def encode_file_ref(file_ref: bytes) -> str: 142 | return base64.urlsafe_b64encode(file_ref).decode().rstrip("=") 143 | 144 | 145 | def unpack_new_file_id(new_file_id): 146 | """Return file_id, file_ref""" 147 | decoded = FileId.decode(new_file_id) 148 | file_id = encode_file_id( 149 | pack( 150 | "= limit: 173 | break 174 | return results 175 | 176 | async def get_series_grouped(limit=30): 177 | cursor = Media.find().sort("$natural", -1).limit(150) 178 | files = await cursor.to_list(length=150) 179 | grouped = defaultdict(list) 180 | 181 | for file in files: 182 | name = getattr(file, "file_name", "") 183 | match = re.search(r"(.*?)(?:S\d{1,2}|Season\s*\d+).*?(?:E|Ep|Episode)?(\d{1,2})", name, re.I) 184 | if match: 185 | title = match.group(1).strip().title() 186 | episode = int(match.group(2)) 187 | grouped[title].append(episode) 188 | 189 | return { 190 | title: sorted(set(eps))[:10] 191 | for title, eps in grouped.items() if eps 192 | } 193 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Shobana Filter Bot 4 |

5 | 6 |

7 | A powerful and versatile Telegram bot designed for filtering, automation, and much more! 8 |

9 |
10 | 11 | Stars 12 | 13 | 14 | Forks 15 | 16 | 17 | Repo Size 18 | 19 | 20 | Last Commit 21 | 22 | 23 | Contributors 24 | 25 | 26 | License 27 | 28 | 29 | Python 30 | 31 | 32 | Pyrogram 33 | 34 |
35 | 36 | 37 | ## ✨ Features 38 | 39 | - ✅ Auto Filter 40 | - ✅ Manual Filter 41 | - ✅ IMDB Search and Info 42 | - ✅ Admin Commands 43 | - ✅ Broadcast Messages 44 | - ✅ File Indexing 45 | - ✅ Inline Search 46 | - ✅ Random Pics Generator 47 | - ✅ User and Chat Stats 48 | - ✅ Ban, Unban, Enable, Disable Commands 49 | - ✅ File Storage 50 | - ✅ Auto-Approval for Requests 51 | - ✅ Shortener Link Support (`/short`) 52 | - ✅ Feedback System 53 | - ✅ Font Styling (`/font`) 54 | - ✅ User Promotion/Demotion 55 | - ✅ Pin/Unpin Messages 56 | - ✅ Image-to-Link Conversion 57 | - ✅ Auto Delete: Automatically removes user messages after processing, so you don't need a separate auto-delete bot 58 | - ✅ Auto Restart 59 | - ✅ Keep Alive Function: Prevents the bot from sleeping or shutting down unexpectedly on platforms like Koyeb, eliminating the need for external uptime services like UptimeRobot. 60 | - ✅ /movies and /series Commands: Instantly fetch and display the most recently added movies or series with these commands. 61 | - ✅ Hyperlink Mode: When enabled, search results are sent as clickable hyperlinks instead of using callback buttons for easier access. 62 | - ✅ Multiple Request FSub support: You can add multiple channels. Easily update the required channels with the /fsub command, e.g., /fsub (channel1 id) (channel2 id) (channel3 id). 63 | - ✅ Delete Files by Query: Use the /deletefiles command to delete all files containing a specific word in their name. For example, /deletefiles predvd removes all files with 'predvd' in their filename. 64 | - ✅ Auto delete for files. 65 | - ✅ Channel file sending mode with multiple channel support. 66 | 67 | ## 🔧 Variables 68 | 69 | ### Required 70 | - `BOT_TOKEN`: Obtain via [@BotFather](https://telegram.dog/BotFather). 71 | - `API_ID`: Get this from [Telegram Apps](https://my.telegram.org/apps). 72 | - `API_HASH`: Also from [Telegram Apps](https://my.telegram.org/apps). 73 | - `CHANNELS`: Telegram channel/group usernames or IDs (space-separated). 74 | - `ADMINS`: Admin usernames or IDs (space-separated). 75 | - `DATABASE_URI`: MongoDB URI ([Learn More](https://youtu.be/1G1XwEOnxxo)). 76 | - `DATABASE_NAME`: MongoDB database name ([Learn More](https://youtu.be/Miajl2amrKo)). 77 | - `LOG_CHANNEL`: Telegram channel for activity logs. 78 | 79 | ### Optional 80 | - `PICS`: Telegraph links for images in start message (space-separated). 81 | - `FILE_STORE_CHANNEL`: Channels for file storage (space-separated). 82 | - Refer to [info.py](https://github.com/mn-bots/ShobanaFilterBot/blob/main/info.py) for more details. 83 | 84 | --- 85 | 86 | ## 🚀 Deployment 87 | 88 | ### Deploy to Koyeb 89 |
Click to Expand 90 |

91 | 92 | Deploy to Koyeb 93 | 94 |

95 |
96 | 97 | ### Deploy to VPS 98 |
99 | Click to Expand 100 |

101 | 102 |

bash
103 | git clone https://github.com/mn-bots/ShobanaFilterBot
104 | # Install dependencies
105 | pip3 install -U -r requirements.txt
106 | # Configure variables in info.py and start the bot
107 | python3 bot.py
108 |


109 | 110 | 💬 Support 111 |

Telegram Group Telegram Channel


112 | 🙏 Credits 113 |
  • Dan for the Pyrogram Library
  • Mahesh for the Media Search Bot
  • EvamariaTG for the EvaMaria Bot
  • Trojanz for Unlimited Filter Bot
  • Goutham for ping feature
  • MN TG for editing and modifying this repository(Currently It's Me)
  • If your intrested to Collab with us Just fork this repo and create pull request ------ Click Here To Fork Repo

114 | 📜 Disclaimer 115 |

GNU AGPLv3

This project is licensed under the GNU AGPL 3.0. Selling this code for monetary gain is strictly prohibited.


116 | -------------------------------------------------------------------------------- /plugins/Extra/font.py: -------------------------------------------------------------------------------- 1 | import os 2 | from plugins.Extra.fotnt_string import Fonts 3 | from pyrogram import Client, filters 4 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup 5 | 6 | 7 | @Client.on_message(filters.private & filters.command(["font"])) 8 | async def style_buttons(c, m, cb=False): 9 | buttons = [[ 10 | InlineKeyboardButton('𝚃𝚢𝚙𝚎𝚠𝚛𝚒𝚝𝚎𝚛', callback_data='style+typewriter'), 11 | InlineKeyboardButton('𝕆𝕦𝕥𝕝𝕚𝕟𝕖', callback_data='style+outline'), 12 | InlineKeyboardButton('𝐒𝐞𝐫𝐢𝐟', callback_data='style+serif'), 13 | ],[ 14 | InlineKeyboardButton('𝑺𝒆𝒓𝒊𝒇', callback_data='style+bold_cool'), 15 | InlineKeyboardButton('𝑆𝑒𝑟𝑖𝑓', callback_data='style+cool'), 16 | InlineKeyboardButton('Sᴍᴀʟʟ Cᴀᴘs', callback_data='style+small_cap'), 17 | ],[ 18 | InlineKeyboardButton('𝓈𝒸𝓇𝒾𝓅𝓉', callback_data='style+script'), 19 | InlineKeyboardButton('𝓼𝓬𝓻𝓲𝓹𝓽', callback_data='style+script_bolt'), 20 | InlineKeyboardButton('ᵗⁱⁿʸ', callback_data='style+tiny'), 21 | ],[ 22 | InlineKeyboardButton('ᑕOᗰIᑕ', callback_data='style+comic'), 23 | InlineKeyboardButton('𝗦𝗮𝗻𝘀', callback_data='style+sans'), 24 | InlineKeyboardButton('𝙎𝙖𝙣𝙨', callback_data='style+slant_sans'), 25 | ],[ 26 | InlineKeyboardButton('𝘚𝘢𝘯𝘴', callback_data='style+slant'), 27 | InlineKeyboardButton('𝖲𝖺𝗇𝗌', callback_data='style+sim'), 28 | InlineKeyboardButton('Ⓒ︎Ⓘ︎Ⓡ︎Ⓒ︎Ⓛ︎Ⓔ︎Ⓢ︎', callback_data='style+circles') 29 | ],[ 30 | InlineKeyboardButton('🅒︎🅘︎🅡︎🅒︎🅛︎🅔︎🅢︎', callback_data='style+circle_dark'), 31 | InlineKeyboardButton('𝔊𝔬𝔱𝔥𝔦𝔠', callback_data='style+gothic'), 32 | InlineKeyboardButton('𝕲𝖔𝖙𝖍𝖎𝖈', callback_data='style+gothic_bolt'), 33 | ],[ 34 | InlineKeyboardButton('C͜͡l͜͡o͜͡u͜͡d͜͡s͜͡', callback_data='style+cloud'), 35 | InlineKeyboardButton('H̆̈ă̈p̆̈p̆̈y̆̈', callback_data='style+happy'), 36 | InlineKeyboardButton('S̑̈ȃ̈d̑̈', callback_data='style+sad'), 37 | ],[ 38 | InlineKeyboardButton('Next ➡️', callback_data="nxt") 39 | ]] 40 | if not cb: 41 | if ' ' in m.text: 42 | title = m.text.split(" ", 1)[1] 43 | await m.reply_text(title, reply_markup=InlineKeyboardMarkup(buttons), reply_to_message_id=m.id) 44 | else: 45 | await m.reply_text(text="Ente Any Text Eg:- `/font [text]`") 46 | else: 47 | await m.answer() 48 | await m.message.edit_reply_markup(InlineKeyboardMarkup(buttons)) 49 | 50 | 51 | @Client.on_callback_query(filters.regex('^nxt')) 52 | async def nxt(c, m): 53 | if m.data == "nxt": 54 | buttons = [[ 55 | InlineKeyboardButton('🇸 🇵 🇪 🇨 🇮 🇦 🇱 ', callback_data='style+special'), 56 | InlineKeyboardButton('🅂🅀🅄🄰🅁🄴🅂', callback_data='style+squares'), 57 | InlineKeyboardButton('🆂︎🆀︎🆄︎🅰︎🆁︎🅴︎🆂︎', callback_data='style+squares_bold'), 58 | ],[ 59 | InlineKeyboardButton('ꪖꪀᦔꪖꪶꪊᥴ𝓲ꪖ', callback_data='style+andalucia'), 60 | InlineKeyboardButton('爪卂几ᘜ卂', callback_data='style+manga'), 61 | InlineKeyboardButton('S̾t̾i̾n̾k̾y̾', callback_data='style+stinky'), 62 | ],[ 63 | InlineKeyboardButton('B̥ͦu̥ͦb̥ͦb̥ͦl̥ͦe̥ͦs̥ͦ', callback_data='style+bubbles'), 64 | InlineKeyboardButton('U͟n͟d͟e͟r͟l͟i͟n͟e͟', callback_data='style+underline'), 65 | InlineKeyboardButton('꒒ꍏꀷꌩꌃꀎꁅ', callback_data='style+ladybug'), 66 | ],[ 67 | InlineKeyboardButton('R҉a҉y҉s҉', callback_data='style+rays'), 68 | InlineKeyboardButton('B҈i҈r҈d҈s҈', callback_data='style+birds'), 69 | InlineKeyboardButton('S̸l̸a̸s̸h̸', callback_data='style+slash'), 70 | ],[ 71 | InlineKeyboardButton('s⃠t⃠o⃠p⃠', callback_data='style+stop'), 72 | InlineKeyboardButton('S̺͆k̺͆y̺͆l̺͆i̺͆n̺͆e̺͆', callback_data='style+skyline'), 73 | InlineKeyboardButton('A͎r͎r͎o͎w͎s͎', callback_data='style+arrows'), 74 | ],[ 75 | InlineKeyboardButton('ዪሀክቿነ', callback_data='style+qvnes'), 76 | InlineKeyboardButton('S̶t̶r̶i̶k̶e̶', callback_data='style+strike'), 77 | InlineKeyboardButton('F༙r༙o༙z༙e༙n༙', callback_data='style+frozen') 78 | ],[ 79 | InlineKeyboardButton('⬅️ Back', callback_data='nxt+0') 80 | ]] 81 | await m.answer() 82 | await m.message.edit_reply_markup(InlineKeyboardMarkup(buttons)) 83 | else: 84 | await style_buttons(c, m, cb=True) 85 | 86 | 87 | @Client.on_callback_query(filters.regex('^style')) 88 | async def style(c, m): 89 | await m.answer() 90 | cmd, style = m.data.split('+') 91 | 92 | if style == 'typewriter': 93 | cls = Fonts.typewriter 94 | if style == 'outline': 95 | cls = Fonts.outline 96 | if style == 'serif': 97 | cls = Fonts.serief 98 | if style == 'bold_cool': 99 | cls = Fonts.bold_cool 100 | if style == 'cool': 101 | cls = Fonts.cool 102 | if style == 'small_cap': 103 | cls = Fonts.smallcap 104 | if style == 'script': 105 | cls = Fonts.script 106 | if style == 'script_bolt': 107 | cls = Fonts.bold_script 108 | if style == 'tiny': 109 | cls = Fonts.tiny 110 | if style == 'comic': 111 | cls = Fonts.comic 112 | if style == 'sans': 113 | cls = Fonts.san 114 | if style == 'slant_sans': 115 | cls = Fonts.slant_san 116 | if style == 'slant': 117 | cls = Fonts.slant 118 | if style == 'sim': 119 | cls = Fonts.sim 120 | if style == 'circles': 121 | cls = Fonts.circles 122 | if style == 'circle_dark': 123 | cls = Fonts.dark_circle 124 | if style == 'gothic': 125 | cls = Fonts.gothic 126 | if style == 'gothic_bolt': 127 | cls = Fonts.bold_gothic 128 | if style == 'cloud': 129 | cls = Fonts.cloud 130 | if style == 'happy': 131 | cls = Fonts.happy 132 | if style == 'sad': 133 | cls = Fonts.sad 134 | if style == 'special': 135 | cls = Fonts.special 136 | if style == 'squares': 137 | cls = Fonts.square 138 | if style == 'squares_bold': 139 | cls = Fonts.dark_square 140 | if style == 'andalucia': 141 | cls = Fonts.andalucia 142 | if style == 'manga': 143 | cls = Fonts.manga 144 | if style == 'stinky': 145 | cls = Fonts.stinky 146 | if style == 'bubbles': 147 | cls = Fonts.bubbles 148 | if style == 'underline': 149 | cls = Fonts.underline 150 | if style == 'ladybug': 151 | cls = Fonts.ladybug 152 | if style == 'rays': 153 | cls = Fonts.rays 154 | if style == 'birds': 155 | cls = Fonts.birds 156 | if style == 'slash': 157 | cls = Fonts.slash 158 | if style == 'stop': 159 | cls = Fonts.stop 160 | if style == 'skyline': 161 | cls = Fonts.skyline 162 | if style == 'arrows': 163 | cls = Fonts.arrows 164 | if style == 'qvnes': 165 | cls = Fonts.rvnes 166 | if style == 'strike': 167 | cls = Fonts.strike 168 | if style == 'frozen': 169 | cls = Fonts.frozen 170 | 171 | r, oldtxt = m.message.reply_to_message.text.split(None, 1) 172 | new_text = cls(oldtxt) 173 | try: 174 | await m.message.edit_text(f"`{new_text}`\n\n👆 Click To Copy", reply_markup=m.message.reply_markup) 175 | except Exception as e: 176 | print(e) 177 | -------------------------------------------------------------------------------- /Script.py: -------------------------------------------------------------------------------- 1 | class script(object): 2 | START_TXT = """ Hᴇʟʟᴏ {}. 3 | 𝖨𝗆 𝖺𝗇 𝖺𝗎𝗍𝗈 𝖿𝗂𝗅𝗍𝖾𝗋 𝖻𝗈𝗍 𝗐𝗁𝗂𝖼𝗁 𝖼𝖺𝗇 𝗉𝗋𝗈𝗏𝗂𝖽𝖾 𝗆𝗈𝗏𝗂𝖾𝗌 𝗂𝗇 𝗒𝗈𝗎𝗋 𝗀𝗋𝗈𝗎𝗉𝗌. 4 | + 𝖠𝖽𝖽 𝖬𝖾 𝖳𝗈 𝖸𝗈𝗎𝗋 𝖦𝗋𝗈𝗎𝗉 + 𝖺𝗇𝖽 𝗉𝗋𝗈𝗆𝗈𝗍𝖾 𝗆𝖾 𝖺𝗌 𝖺𝖽𝗆𝗂𝗇 𝗍𝗈 𝗅𝖾𝗍 𝗆𝖾 𝗀𝖾𝗍 𝗂𝗇 𝖺𝖼𝗍𝗂𝗈𝗇.""" 5 | HELP_TXT = """ 6 | Hey {} 7 | Currently using free server so please Dont kill Me... 8 | """ 9 | ABOUT_TXT = """ 10 | ◎ Cʀᴇᴀᴛᴏʀ: MN - TG 11 | ◎ Lᴀɴɢᴜᴀɢᴇ: Pʏᴛʜᴏɴ 3 12 | ◎ Dᴀᴛᴀ Bᴀsᴇ: Mᴏɴɢᴏ DB 13 | ◎ Bᴏᴛ Sᴇʀᴠᴇʀ: KoYeb""" 14 | SOURCE_TXT = """NOTE: 15 | - Shobana Filter Bot is a open source project. 16 | - Source - Click Here to get source code 17 | 18 | DEVS: 19 | - MN - TG""" 20 | MANUELFILTER_TXT = """Help: Filters 21 | - Filter is the feature were users can set automated replies for a particular keyword and shobana will respond whenever a keyword is found the message 22 | NOTE: 23 | 1. This Bot should have admin privillage. 24 | 2. only admins can add filters in a chat. 25 | 3. alert buttons have a limit of 64 characters. 26 | 27 | Commands and Usage: 28 | • /filter - add a filter in chat 29 | • /filters - list all the filters of a chat 30 | • /del - delete a specific filter in chat 31 | • /delall - delete the whole filters in a chat (chat owner only)""" 32 | BUTTON_TXT = """Help: Buttons 33 | 34 | - This Bot Supports both url and alert inline buttons. 35 | 36 | NOTE: 37 | 1. Telegram will not allows you to send buttons without any content, so content is mandatory. 38 | 2. This Bot supports buttons with any telegram media type. 39 | 3. Buttons should be properly parsed as markdown format 40 | 41 | URL buttons: 42 | [Button Text](buttonurl:https://github.com/mn-bots/ShobanaFilterBot) 43 | 44 | Alert buttons: 45 | [Button Text](buttonalert:This is an alert message)""" 46 | AUTOFILTER_TXT = """ 47 | 48 | ɴᴏᴛᴇ: Fɪʟᴇ Iɴᴅᴇx 49 | 1. ᴍᴀᴋᴇ ᴍᴇ ᴛʜᴇ ᴀᴅᴍɪɴ ᴏꜰ ʏᴏᴜʀ ᴄʜᴀɴɴᴇʟ ɪꜰ ɪᴛ'ꜱ ᴘʀɪᴠᴀᴛᴇ. 50 | 2. ᴍᴀᴋᴇ ꜱᴜʀᴇ ᴛʜᴀᴛ ʏᴏᴜʀ ᴄʜᴀɴɴᴇʟ ᴅᴏᴇꜱ ɴᴏᴛ ᴄᴏɴᴛᴀɪɴꜱ ᴄᴀᴍʀɪᴘꜱ, ᴘᴏʀɴ ᴀɴᴅ ꜰᴀᴋᴇ ꜰɪʟᴇꜱ. 51 | 3. ꜰᴏʀᴡᴀʀᴅ ᴛʜᴇ ʟᴀꜱᴛ ᴍᴇꜱꜱᴀɢᴇ ᴛᴏ ᴍᴇ ᴡɪᴛʜ Qᴜᴏᴛᴇꜱ. ɪ'ʟʟ ᴀᴅᴅ ᴀʟʟ ᴛʜᴇ ꜰɪʟᴇꜱ ɪɴ ᴛʜᴀᴛ ᴄʜᴀɴɴᴇʟ ᴛᴏ ᴍʏ ᴅʙ. 52 | 53 | Nᴏᴛᴇ: AᴜᴛᴏFɪʟᴛᴇʀ 54 | 1. Aᴅᴅ ᴛʜᴇ ʙᴏᴛ ᴀs ᴀᴅᴍɪɴ ᴏɴ ʏᴏᴜʀ ɢʀᴏᴜᴘ. 55 | 2. Usᴇ /connect ᴀɴᴅ ᴄᴏɴɴᴇᴄᴛ ʏᴏᴜʀ ɢʀᴏᴜᴘ ᴛᴏ ᴛʜᴇ ʙᴏᴛ. 56 | 3. Usᴇ /settings ᴏɴ ʙᴏᴛ's PM ᴀɴᴅ ᴛᴜʀɴ ᴏɴ AᴜᴛᴏFɪʟᴛᴇʀ ᴏɴ ᴛʜᴇ sᴇᴛᴛɪɴɢs ᴍᴇɴᴜ..""" 57 | CONNECTION_TXT = """Help: Connections 58 | 59 | - Used to connect bot to PM for managing filters 60 | - it helps to avoid spamming in groups. 61 | 62 | NOTE: 63 | 1. Only admins can add a connection. 64 | 2. Send /connect for connecting me to ur PM 65 | 66 | Commands and Usage: 67 | • /connect - connect a particular chat to your PM 68 | • /disconnect - disconnect from a chat 69 | • /connections - list all your connections""" 70 | EXTRAMOD_TXT = """Help: Extra Modules 71 | 72 | NOTE: 73 | these are the extra features of ShobanaFilterBot 74 | 75 | Commands and Usage: 76 | • /id - get id of a specified user. 77 | • /info - get information about a user. 78 | • /imdb - get the film information from IMDb source. 79 | • /search - get the film information from various sources. 80 | • /start - Check I'm Alive. 81 | • /ping - check ping. 82 | • /usage - usage of bot. 83 | • /info - User info . 84 | • /id - User id . 85 | • /broadcast - Broadcast (owner only). 86 | """ 87 | ADMIN_TXT = """Help: Admin mods 88 | 89 | NOTE: 90 | This module only works for my admins 91 | 92 | Commands and Usage: 93 | • /logs - to get the rescent errors 94 | • /stats - to get status of files in db. 95 | • /delete - to delete a specific file from db. 96 | • /users - to get list of my users and ids. 97 | • /chats - to get list of the my chats and ids 98 | • /leave - to leave from a chat. 99 | • /disable - do disable a chat. 100 | • /ban - to ban a user. 101 | • /unban - to unban a user. 102 | • /channel - to get list of total connected channels 103 | • /broadcast - to broadcast a message to all users""" 104 | STATUS_TXT = """★ 𝚃𝙾𝚃𝙰𝙻 𝙵𝙸𝙻𝙴𝚂: {} 105 | 𝚃𝙾𝚃𝙰𝙻 𝚄𝚂𝙴𝚁𝚂: {} 106 | 𝚃𝙾𝚃𝙰𝙻 𝙲𝙷𝙰𝚃𝚂: {} 107 | 𝚄𝚂𝙴𝙳 𝚂𝚃𝙾𝚁𝙰𝙶𝙴: {} 108 | 𝙵𝚁𝙴𝙴 𝚂𝚃𝙾𝚁𝙰𝙶𝙴: {} """ 109 | LOG_TEXT_G = """#NewGroup 110 | Group = {}({}) 111 | Total Members = {} 112 | Added By - {} 113 | """ 114 | RESULT_TXT="""
Hey,
115 |
Jᴜsᴛ Sᴇᴇ Wʜᴀᴛ I Found Fᴏʀ Yᴏᴜʀ Qᴜᴇʀʏ
""" 116 | 117 | CUSTOM_FILE_CAPTION = """📂Fɪʟᴇɴᴀᴍᴇ : {file_name} 118 | FɪʟᴇSɪᴢᴇ : {file_size} 119 | 120 | ╔═ ᴊᴏɪɴ ᴡɪᴛʜ ᴜs ═╗ 121 | Jᴏɪɴ :- [MAIN CHANNEL](https://t.me/mn_movies2) 122 | Jᴏɪɴ :- [Movie Group 1](https://t.me/mn_movies3) 123 | Jᴏɪɴ :- [Movie Group 2](https://t.me/malayalam_movies_group2) 124 | Jᴏɪɴ :- [Movie Group 3](https://t.me/Netflix_Group3) 125 | Jᴏɪɴ :- [Movie Group 4](https://t.me/cinima_theerthadana_kendram) 126 | Jᴏɪɴ :- [Movie Group 5](https://t.me/malayalam_movies_nbot) 127 | Jᴏɪɴ :- [Movie Group 6](https://t.me/seriesgroups) 128 | Jᴏɪɴ :- [Movie Group 7](https://t.me/New_indian_cinemas) 129 | ╚═  ᴊᴏɪɴ ᴡɪᴛʜ ᴜs    ═╝ 130 | 131 | ⚠️ This file will be deleted from here within 1 minute as it has copyright ... !!! 132 | 133 | കോപ്പിറൈറ്റ് ഉള്ളതുകൊണ്ട് ഫയൽ 1 മിനിറ്റിനുള്ളിൽ ഇവിടെനിന്നും ഡിലീറ്റ് ആകുന്നതാണ് അതുകൊണ്ട് ഇവിടെ നിന്നും മറ്റെവിടെക്കെങ്കിലും മാറ്റിയതിന് ശേഷം ഡൗൺലോഡ് ചെയ്യുക! 134 | """ 135 | 136 | 137 | RESTART_GC_TXT = """ 138 | 𝖡𝗈𝗍 𝖱𝖾𝗌𝗍𝖺𝗋𝗍𝖾𝖽 ! 139 | 140 | 📅 𝖣𝖺𝗍𝖾 : {} 141 | ⏰ 𝖳𝗂𝗆𝖾 : {} 142 | 🌐 𝖳𝗂𝗆𝖾𝗓𝗈𝗇𝖾 : Asia/Kolkata 143 | 🛠️ 𝖡𝗎𝗂𝗅𝖽 𝖲𝗍𝖺𝗍𝗎𝗌 : 𝗏1 [ 𝖲𝗍able ]""" 144 | 145 | LOG_TEXT_P = """#NewUser 146 | ID - {} 147 | Name - {} 148 | """ 149 | SPOLL_NOT_FND=""" 150 | I couldn't find anything related to your request. 151 | Try reading the instruction below 152 |
153 | 1️ Ask in Correct Spelling 154 | 2️ Don't ask Movies which are not Realased on OTT PLATFORMS 155 | 3️ Possible ASK [movie name langauge] like this or [movie year]
156 | OR 157 | Tʜɪs Mᴏᴠɪᴇ Is Nᴏᴛ Aᴅᴅᴇᴅ Tᴏ DB 158 |
Report To ADMIN BY USING /bugs command 
159 | """ 160 | #SPELL CHECK LANGUAGES TO KNOW callback 161 | ENG_SPELL="""Please Note Below📓 162 | 1️⃣ Ask in Correct Spelling 163 | 2️⃣ Don't ask Movies which are not Realased on OTT PLATFORMS 164 | 3️⃣ Possible ASK [movie name langauge] like this or [movie year] 165 | """ 166 | MAL_SPELL="""ദയവായി താഴെ ശ്രദ്ധിക്കുക📓 167 | 1️⃣ ശരിയായ അക്ഷരവിന്യാസത്തിൽ ചോദിക്കുക 168 | 2️⃣ OTT പ്ലാറ്റ്‌ഫോമുകളിൽ റിലീസ് ചെയ്യാത്ത സിനിമകൾ ചോദിക്കരുത് 169 | 3️⃣ ഇത് പോലെ [സിനിമയുടെ പേര് ഭാഷ] അല്ലെങ്കിൽ [സിനിമ വർഷം] ചോദിക്കാം 170 | """ 171 | HIN_SPELL="""कृपया नीचे ध्यान दें📓 172 | 1️⃣ सही वर्तनी में पूछें 173 | 2️⃣ उन फिल्मों के बारे में न पूछें जो ओटीटी प्लेटफॉर्म पर रिलीज नहीं हुई हैं 174 | 3️⃣ संभव है पूछें [मूवी का नाम भाषा] इस तरह या [मूवी वर्ष] 175 | """ 176 | TAM_SPELL="""கீழே கவனிக்கவும்📓 177 | 1️⃣ சரியான எழுத்துப்பிழையில் கேளுங்கள் 178 | 2️⃣ வெளியாகாத திரைப்படங்களைக் கேட்காதீர்கள் 179 | 3️⃣ இந்த வடிவத்தில் கேளுங்கள் [திரைப்படத்தின் பெயர், ஆண்டு] 180 | """ 181 | 182 | CHK_MOV_ALRT="""♻️ ᴄʜᴇᴄᴋɪɴɢ ꜰɪʟᴇ ᴏɴ ᴍʏ ᴅᴀᴛᴀʙᴀꜱᴇ... ♻️""" 183 | 184 | OLD_MES=""" 𝐘𝐨𝐮 𝐚𝐫𝐞 𝐮𝐬𝐢𝐧𝐠 𝐨𝐧𝐞 𝐨𝐟 𝐦𝐲 𝐨𝐥𝐝 𝐦𝐞𝐬𝐬𝐚𝐠𝐞𝐬🤔, 𝐩𝐥𝐞𝐚𝐬𝐞 𝐬𝐞𝐧𝐝 𝐭𝐡𝐞 𝐫𝐞𝐪𝐮𝐞𝐬𝐭 𝐚𝐠𝐚𝐢𝐧""" 185 | 186 | MOV_NT_FND="""Tʜɪs Mᴏᴠɪᴇ Is Nᴏᴛ Yᴇᴛ Rᴇᴀʟᴇsᴇᴅ Oʀ Aᴅᴅᴇᴅ Tᴏ DB 187 |
Report To ADMIN BY USING /bugs command 
188 | """ 189 | RESTART_TXT = """ 190 | 𝖡𝗈𝗍 𝖱𝖾𝗌𝗍𝖺𝗋𝗍𝖾𝖽 ✅""" 191 | 192 | -------------------------------------------------------------------------------- /plugins/misc.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pyrogram import Client, filters, enums 3 | from pyrogram.errors.exceptions.bad_request_400 import UserNotParticipant, MediaEmpty, PhotoInvalidDimensions, WebpageMediaEmpty 4 | from info import IMDB_TEMPLATE 5 | from utils import extract_user, get_file_id, get_poster, last_online 6 | import time 7 | from datetime import datetime 8 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery 9 | import logging 10 | logger = logging.getLogger(__name__) 11 | logger.setLevel(logging.ERROR) 12 | 13 | @Client.on_message(filters.command('id')) 14 | async def showid(client, message): 15 | chat_type = message.chat.type 16 | if chat_type == enums.ChatType.PRIVATE: 17 | user_id = message.chat.id 18 | first = message.from_user.first_name 19 | last = message.from_user.last_name or "" 20 | username = message.from_user.username 21 | dc_id = message.from_user.dc_id or "" 22 | await message.reply_text( 23 | f"➲ First Name: {first}\n➲ Last Name: {last}\n➲ Username: {username}\n➲ Telegram ID: {user_id}\n➲ Data Centre: {dc_id}", 24 | quote=True 25 | ) 26 | 27 | elif chat_type in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]: 28 | _id = "" 29 | _id += ( 30 | "➲ Chat ID: " 31 | f"{message.chat.id}\n" 32 | ) 33 | if message.reply_to_message: 34 | _id += ( 35 | "➲ User ID: " 36 | f"{message.from_user.id if message.from_user else 'Anonymous'}\n" 37 | "➲ Replied User ID: " 38 | f"{message.reply_to_message.from_user.id if message.reply_to_message.from_user else 'Anonymous'}\n" 39 | ) 40 | file_info = get_file_id(message.reply_to_message) 41 | else: 42 | _id += ( 43 | "➲ User ID: " 44 | f"{message.from_user.id if message.from_user else 'Anonymous'}\n" 45 | ) 46 | file_info = get_file_id(message) 47 | if file_info: 48 | _id += ( 49 | f"{file_info.message_type}: " 50 | f"{file_info.file_id}\n" 51 | ) 52 | await message.reply_text( 53 | _id, 54 | quote=True 55 | ) 56 | 57 | @Client.on_message(filters.command(["info"])) 58 | async def who_is(client, message): 59 | # https://github.com/SpEcHiDe/PyroGramBot/blob/master/pyrobot/plugins/admemes/whois.py#L19 60 | status_message = await message.reply_text( 61 | "`Fetching user info...`" 62 | ) 63 | await status_message.edit( 64 | "`Processing user info...`" 65 | ) 66 | from_user = None 67 | from_user_id, _ = extract_user(message) 68 | try: 69 | from_user = await client.get_users(from_user_id) 70 | except Exception as error: 71 | await status_message.edit(str(error)) 72 | return 73 | if from_user is None: 74 | return await status_message.edit("no valid user_id / message specified") 75 | message_out_str = "" 76 | message_out_str += f"➲First Name: {from_user.first_name}\n" 77 | last_name = from_user.last_name or "None" 78 | message_out_str += f"➲Last Name: {last_name}\n" 79 | message_out_str += f"➲Telegram ID: {from_user.id}\n" 80 | username = from_user.username or "None" 81 | dc_id = from_user.dc_id or "[User Doesn't Have A Valid DP]" 82 | message_out_str += f"➲Data Centre: {dc_id}\n" 83 | message_out_str += f"➲User Name: @{username}\n" 84 | message_out_str += f"➲User 𝖫𝗂𝗇𝗄: Click Here\n" 85 | if message.chat.type in ((enums.ChatType.SUPERGROUP, enums.ChatType.CHANNEL)): 86 | try: 87 | chat_member_p = await message.chat.get_member(from_user.id) 88 | joined_date = ( 89 | chat_member_p.joined_date or datetime.now() 90 | ).strftime("%Y.%m.%d %H:%M:%S") 91 | message_out_str += ( 92 | "➲Joined this Chat on: " 93 | f"{joined_date}" 94 | "\n" 95 | ) 96 | except UserNotParticipant: 97 | pass 98 | chat_photo = from_user.photo 99 | if chat_photo: 100 | local_user_photo = await client.download_media( 101 | message=chat_photo.big_file_id 102 | ) 103 | buttons = [[ 104 | InlineKeyboardButton('🔐 Close', callback_data='close_data') 105 | ]] 106 | reply_markup = InlineKeyboardMarkup(buttons) 107 | await message.reply_photo( 108 | photo=local_user_photo, 109 | quote=True, 110 | reply_markup=reply_markup, 111 | caption=message_out_str, 112 | parse_mode=enums.ParseMode.HTML, 113 | disable_notification=True 114 | ) 115 | os.remove(local_user_photo) 116 | else: 117 | buttons = [[ 118 | InlineKeyboardButton('🔐 Close', callback_data='close_data') 119 | ]] 120 | reply_markup = InlineKeyboardMarkup(buttons) 121 | await message.reply_text( 122 | text=message_out_str, 123 | reply_markup=reply_markup, 124 | quote=True, 125 | parse_mode=enums.ParseMode.HTML, 126 | disable_notification=True 127 | ) 128 | await status_message.delete() 129 | 130 | @Client.on_message(filters.command(["imdb", 'search'])) 131 | async def imdb_search(client, message): 132 | if ' ' in message.text: 133 | k = await message.reply('Searching ImDB') 134 | r, title = message.text.split(None, 1) 135 | movies = await get_poster(title, bulk=True) 136 | if not movies: 137 | return await message.reply("No results Found") 138 | btn = [ 139 | [ 140 | InlineKeyboardButton( 141 | text=f"{movie.get('title')} - {movie.get('year')}", 142 | callback_data=f"imdb#{movie.movieID}", 143 | ) 144 | ] 145 | for movie in movies 146 | ] 147 | await k.edit('Here is what i found on IMDb', reply_markup=InlineKeyboardMarkup(btn)) 148 | else: 149 | await message.reply('Give me a movie / series Name') 150 | 151 | @Client.on_callback_query(filters.regex('^imdb')) 152 | async def imdb_callback(bot: Client, quer_y: CallbackQuery): 153 | i, movie = quer_y.data.split('#') 154 | imdb = await get_poster(query=movie, id=True) 155 | btn = [ 156 | [ 157 | InlineKeyboardButton( 158 | text=f"{imdb.get('title')}", 159 | url=imdb['url'], 160 | ) 161 | ] 162 | ] 163 | message = quer_y.message.reply_to_message or quer_y.message 164 | if imdb: 165 | caption = IMDB_TEMPLATE.format( 166 | query = imdb['title'], 167 | title = imdb['title'], 168 | votes = imdb['votes'], 169 | aka = imdb["aka"], 170 | seasons = imdb["seasons"], 171 | box_office = imdb['box_office'], 172 | localized_title = imdb['localized_title'], 173 | kind = imdb['kind'], 174 | imdb_id = imdb["imdb_id"], 175 | cast = imdb["cast"], 176 | runtime = imdb["runtime"], 177 | countries = imdb["countries"], 178 | certificates = imdb["certificates"], 179 | languages = imdb["languages"], 180 | director = imdb["director"], 181 | writer = imdb["writer"], 182 | producer = imdb["producer"], 183 | composer = imdb["composer"], 184 | cinematographer = imdb["cinematographer"], 185 | music_team = imdb["music_team"], 186 | distributors = imdb["distributors"], 187 | release_date = imdb['release_date'], 188 | year = imdb['year'], 189 | genres = imdb['genres'], 190 | poster = imdb['poster'], 191 | plot = imdb['plot'], 192 | rating = imdb['rating'], 193 | url = imdb['url'], 194 | **locals() 195 | ) 196 | else: 197 | caption = "No Results" 198 | if imdb.get('poster'): 199 | try: 200 | await quer_y.message.reply_photo(photo=imdb['poster'], caption=caption, reply_markup=InlineKeyboardMarkup(btn)) 201 | except (MediaEmpty, PhotoInvalidDimensions, WebpageMediaEmpty): 202 | pic = imdb.get('poster') 203 | poster = pic.replace('.jpg', "._V1_UX360.jpg") 204 | await quer_y.message.reply_photo(photo=poster, caption=caption, reply_markup=InlineKeyboardMarkup(btn)) 205 | except Exception as e: 206 | logger.exception(e) 207 | await quer_y.message.reply(caption, reply_markup=InlineKeyboardMarkup(btn), disable_web_page_preview=False) 208 | await quer_y.message.delete() 209 | else: 210 | await quer_y.message.edit(caption, reply_markup=InlineKeyboardMarkup(btn), disable_web_page_preview=False) 211 | await quer_y.answer() 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /plugins/index.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import asyncio 3 | from pyrogram import Client, filters, enums 4 | from pyrogram.errors import FloodWait 5 | from pyrogram.errors.exceptions.bad_request_400 import ChannelInvalid, ChatAdminRequired, UsernameInvalid, UsernameNotModified 6 | from info import ADMINS 7 | from info import INDEX_REQ_CHANNEL as LOG_CHANNEL 8 | from database.ia_filterdb import save_file 9 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton 10 | from utils import temp 11 | import re 12 | logger = logging.getLogger(__name__) 13 | logger.setLevel(logging.INFO) 14 | lock = asyncio.Lock() 15 | 16 | 17 | @Client.on_callback_query(filters.regex(r'^index')) 18 | async def index_files(bot, query): 19 | if query.data.startswith('index_cancel'): 20 | temp.CANCEL = True 21 | return await query.answer("Cancelling Indexing") 22 | _, raju, chat, lst_msg_id, from_user = query.data.split("#") 23 | if raju == 'reject': 24 | await query.message.delete() 25 | await bot.send_message(int(from_user), 26 | f'Your Submission for indexing {chat} has been decliened by our moderators.', 27 | reply_to_message_id=int(lst_msg_id)) 28 | return 29 | 30 | if lock.locked(): 31 | return await query.answer('Wait until previous process complete.', show_alert=True) 32 | msg = query.message 33 | 34 | await query.answer('Processing...⏳', show_alert=True) 35 | if int(from_user) not in ADMINS: 36 | await bot.send_message(int(from_user), 37 | f'Your Submission for indexing {chat} has been accepted by our moderators and will be added soon.', 38 | reply_to_message_id=int(lst_msg_id)) 39 | await msg.edit( 40 | "Starting Indexing", 41 | reply_markup=InlineKeyboardMarkup( 42 | [[InlineKeyboardButton('Cancel', callback_data='index_cancel')]] 43 | ) 44 | ) 45 | try: 46 | chat = int(chat) 47 | except: 48 | chat = chat 49 | await index_files_to_db(int(lst_msg_id), chat, msg, bot) 50 | 51 | 52 | @Client.on_message((filters.forwarded | (filters.regex("(https://)?(t\.me/|telegram\.me/|telegram\.dog/)(c/)?(\d+|[a-zA-Z_0-9]+)/(\d+)$")) & filters.text ) & filters.private & filters.incoming) 53 | async def send_for_index(bot, message): 54 | if message.text: 55 | regex = re.compile("(https://)?(t\.me/|telegram\.me/|telegram\.dog/)(c/)?(\d+|[a-zA-Z_0-9]+)/(\d+)$") 56 | match = regex.match(message.text) 57 | if not match: 58 | return await message.reply('Invalid link') 59 | chat_id = match.group(4) 60 | last_msg_id = int(match.group(5)) 61 | if chat_id.isnumeric(): 62 | chat_id = int(("-100" + chat_id)) 63 | elif message.forward_from_chat.type == enums.ChatType.CHANNEL: 64 | last_msg_id = message.forward_from_message_id 65 | chat_id = message.forward_from_chat.username or message.forward_from_chat.id 66 | else: 67 | return 68 | try: 69 | await bot.get_chat(chat_id) 70 | except ChannelInvalid: 71 | return await message.reply('This may be a private channel / group. Make me an admin over there to index the files.') 72 | except (UsernameInvalid, UsernameNotModified): 73 | return await message.reply('Invalid Link specified.') 74 | except Exception as e: 75 | logger.exception(e) 76 | return await message.reply(f'Errors - {e}') 77 | try: 78 | k = await bot.get_messages(chat_id, last_msg_id) 79 | except: 80 | return await message.reply('Make Sure That Iam An Admin In The Channel, if channel is private') 81 | if k.empty: 82 | return await message.reply('This may be group and iam not a admin of the group.') 83 | 84 | if message.from_user.id in ADMINS: 85 | buttons = [ 86 | [ 87 | InlineKeyboardButton('Yes', 88 | callback_data=f'index#accept#{chat_id}#{last_msg_id}#{message.from_user.id}') 89 | ], 90 | [ 91 | InlineKeyboardButton('close', callback_data='close_data'), 92 | ] 93 | ] 94 | reply_markup = InlineKeyboardMarkup(buttons) 95 | return await message.reply( 96 | f'Do you Want To Index This Channel/ Group ?\n\nChat ID/ Username: {chat_id}\nLast Message ID: {last_msg_id}', 97 | reply_markup=reply_markup) 98 | 99 | if type(chat_id) is int: 100 | try: 101 | link = (await bot.create_chat_invite_link(chat_id)).invite_link 102 | except ChatAdminRequired: 103 | return await message.reply('Make sure iam an admin in the chat and have permission to invite users.') 104 | else: 105 | link = f"@{message.forward_from_chat.username}" 106 | buttons = [ 107 | [ 108 | InlineKeyboardButton('Accept Index', 109 | callback_data=f'index#accept#{chat_id}#{last_msg_id}#{message.from_user.id}') 110 | ], 111 | [ 112 | InlineKeyboardButton('Reject Index', 113 | callback_data=f'index#reject#{chat_id}#{message.id}#{message.from_user.id}'), 114 | ] 115 | ] 116 | reply_markup = InlineKeyboardMarkup(buttons) 117 | await bot.send_message(LOG_CHANNEL, 118 | f'#IndexRequest\n\nBy : {message.from_user.mention} ({message.from_user.id})\nChat ID/ Username - {chat_id}\nLast Message ID - {last_msg_id}\nInviteLink - {link}', 119 | reply_markup=reply_markup) 120 | await message.reply('ThankYou For the Contribution, Wait For My Moderators to verify the files.') 121 | 122 | 123 | @Client.on_message(filters.command('setskip') & filters.user(ADMINS)) 124 | async def set_skip_number(bot, message): 125 | if ' ' in message.text: 126 | _, skip = message.text.split(" ") 127 | try: 128 | skip = int(skip) 129 | except: 130 | return await message.reply("Skip number should be an integer.") 131 | await message.reply(f"Successfully set SKIP number as {skip}") 132 | temp.CURRENT = int(skip) 133 | else: 134 | await message.reply("Give me a skip number") 135 | 136 | 137 | async def index_files_to_db(lst_msg_id, chat, msg, bot): 138 | total_files = 0 139 | duplicate = 0 140 | errors = 0 141 | deleted = 0 142 | no_media = 0 143 | unsupported = 0 144 | async with lock: 145 | try: 146 | current = temp.CURRENT 147 | temp.CANCEL = False 148 | async for message in bot.iter_messages(chat, lst_msg_id, temp.CURRENT): 149 | if temp.CANCEL: 150 | await msg.edit(f"Successfully Cancelled!!\n\nSaved {total_files} files to dataBase!\nDuplicate Files Skipped: {duplicate}\nDeleted Messages Skipped: {deleted}\nNon-Media messages skipped: {no_media + unsupported}(Unsupported Media - `{unsupported}` )\nErrors Occurred: {errors}") 151 | break 152 | current += 1 153 | if current % 80 == 0: 154 | can = [[InlineKeyboardButton('Cancel', callback_data='index_cancel')]] 155 | reply = InlineKeyboardMarkup(can) 156 | await msg.edit_text( 157 | text=f"Total messages fetched: {current}\nTotal messages saved: {total_files}\nDuplicate Files Skipped: {duplicate}\nDeleted Messages Skipped: {deleted}\nNon-Media messages skipped: {no_media + unsupported}(Unsupported Media - `{unsupported}` )\nErrors Occurred: {errors}", 158 | reply_markup=reply) 159 | if message.empty: 160 | deleted += 1 161 | continue 162 | elif not message.media: 163 | no_media += 1 164 | continue 165 | elif message.media not in [enums.MessageMediaType.VIDEO, enums.MessageMediaType.AUDIO, enums.MessageMediaType.DOCUMENT]: 166 | unsupported += 1 167 | continue 168 | media = getattr(message, message.media.value, None) 169 | if not media: 170 | unsupported += 1 171 | continue 172 | media.file_type = message.media.value 173 | media.caption = message.caption 174 | aynav, vnay = await save_file(media) 175 | if aynav: 176 | total_files += 1 177 | elif vnay == 0: 178 | duplicate += 1 179 | elif vnay == 2: 180 | errors += 1 181 | except Exception as e: 182 | logger.exception(e) 183 | await msg.edit(f'Error: {e}') 184 | else: 185 | await msg.edit(f'Succesfully saved {total_files} to dataBase!\nDuplicate Files Skipped: {duplicate}\nDeleted Messages Skipped: {deleted}\nNon-Media messages skipped: {no_media + unsupported}(Unsupported Media - `{unsupported}` )\nErrors Occurred: {errors}') 186 | -------------------------------------------------------------------------------- /plugins/filters.py: -------------------------------------------------------------------------------- 1 | import io 2 | from pyrogram import filters, Client, enums 3 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup 4 | from database.filters_mdb import( 5 | add_filter, 6 | get_filters, 7 | delete_filter, 8 | count_filters 9 | ) 10 | 11 | from database.connections_mdb import active_connection 12 | from utils import get_file_id, parser, split_quotes 13 | from info import ADMINS 14 | 15 | 16 | @Client.on_message(filters.command(['filter', 'add']) & filters.incoming) 17 | async def addfilter(client, message): 18 | userid = message.from_user.id if message.from_user else None 19 | if not userid: 20 | return await message.reply(f"You are anonymous admin. Use /connect {message.chat.id} in PM") 21 | chat_type = message.chat.type 22 | args = message.text.html.split(None, 1) 23 | 24 | if chat_type == enums.ChatType.PRIVATE: 25 | grpid = await active_connection(str(userid)) 26 | if grpid is not None: 27 | grp_id = grpid 28 | try: 29 | chat = await client.get_chat(grpid) 30 | title = chat.title 31 | except: 32 | await message.reply_text("Make sure I'm present in your group!!", quote=True) 33 | return 34 | else: 35 | await message.reply_text("I'm not connected to any groups!", quote=True) 36 | return 37 | 38 | elif chat_type in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]: 39 | grp_id = message.chat.id 40 | title = message.chat.title 41 | 42 | else: 43 | return 44 | 45 | st = await client.get_chat_member(grp_id, userid) 46 | if ( 47 | st.status != enums.ChatMemberStatus.ADMINISTRATOR 48 | and st.status != enums.ChatMemberStatus.OWNER 49 | and str(userid) not in ADMINS 50 | ): 51 | return 52 | 53 | 54 | if len(args) < 2: 55 | await message.reply_text("Command Incomplete :(", quote=True) 56 | return 57 | 58 | extracted = split_quotes(args[1]) 59 | text = extracted[0].lower() 60 | 61 | if not message.reply_to_message and len(extracted) < 2: 62 | await message.reply_text("Add some content to save your filter!", quote=True) 63 | return 64 | 65 | if (len(extracted) >= 2) and not message.reply_to_message: 66 | reply_text, btn, alert = parser(extracted[1], text) 67 | fileid = None 68 | if not reply_text: 69 | await message.reply_text("You cannot have buttons alone, give some text to go with it!", quote=True) 70 | return 71 | 72 | elif message.reply_to_message and message.reply_to_message.reply_markup: 73 | try: 74 | rm = message.reply_to_message.reply_markup 75 | btn = rm.inline_keyboard 76 | msg = get_file_id(message.reply_to_message) 77 | if msg: 78 | fileid = msg.file_id 79 | reply_text = message.reply_to_message.caption.html 80 | else: 81 | reply_text = message.reply_to_message.text.html 82 | fileid = None 83 | alert = None 84 | except: 85 | reply_text = "" 86 | btn = "[]" 87 | fileid = None 88 | alert = None 89 | 90 | elif message.reply_to_message and message.reply_to_message.media: 91 | try: 92 | msg = get_file_id(message.reply_to_message) 93 | fileid = msg.file_id if msg else None 94 | reply_text, btn, alert = parser(extracted[1], text) if message.reply_to_message.sticker else parser(message.reply_to_message.caption.html, text) 95 | except: 96 | reply_text = "" 97 | btn = "[]" 98 | alert = None 99 | elif message.reply_to_message and message.reply_to_message.text: 100 | try: 101 | fileid = None 102 | reply_text, btn, alert = parser(message.reply_to_message.text.html, text) 103 | except: 104 | reply_text = "" 105 | btn = "[]" 106 | alert = None 107 | else: 108 | return 109 | 110 | await add_filter(grp_id, text, reply_text, btn, fileid, alert) 111 | 112 | await message.reply_text( 113 | f"Filter for `{text}` added in **{title}**", 114 | quote=True, 115 | parse_mode=enums.ParseMode.MARKDOWN 116 | ) 117 | 118 | 119 | @Client.on_message(filters.command(['viewfilters', 'filters']) & filters.incoming) 120 | async def get_all(client, message): 121 | 122 | chat_type = message.chat.type 123 | userid = message.from_user.id if message.from_user else None 124 | if not userid: 125 | return await message.reply(f"You are anonymous admin. Use /connect {message.chat.id} in PM") 126 | if chat_type == enums.ChatType.PRIVATE: 127 | grpid = await active_connection(str(userid)) 128 | if grpid is not None: 129 | grp_id = grpid 130 | try: 131 | chat = await client.get_chat(grpid) 132 | title = chat.title 133 | except: 134 | await message.reply_text("Make sure I'm present in your group!!", quote=True) 135 | return 136 | else: 137 | await message.reply_text("I'm not connected to any groups!", quote=True) 138 | return 139 | 140 | elif chat_type in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]: 141 | grp_id = message.chat.id 142 | title = message.chat.title 143 | 144 | else: 145 | return 146 | 147 | st = await client.get_chat_member(grp_id, userid) 148 | if ( 149 | st.status != enums.ChatMemberStatus.ADMINISTRATOR 150 | and st.status != enums.ChatMemberStatus.OWNER 151 | and str(userid) not in ADMINS 152 | ): 153 | return 154 | 155 | texts = await get_filters(grp_id) 156 | count = await count_filters(grp_id) 157 | if count: 158 | filterlist = f"Total number of filters in **{title}** : {count}\n\n" 159 | 160 | for text in texts: 161 | keywords = " × `{}`\n".format(text) 162 | 163 | filterlist += keywords 164 | 165 | if len(filterlist) > 4096: 166 | with io.BytesIO(str.encode(filterlist.replace("`", ""))) as keyword_file: 167 | keyword_file.name = "keywords.txt" 168 | await message.reply_document( 169 | document=keyword_file, 170 | quote=True 171 | ) 172 | return 173 | else: 174 | filterlist = f"There are no active filters in **{title}**" 175 | 176 | await message.reply_text( 177 | text=filterlist, 178 | quote=True, 179 | parse_mode=enums.ParseMode.MARKDOWN 180 | ) 181 | 182 | @Client.on_message(filters.command('del') & filters.incoming) 183 | async def deletefilter(client, message): 184 | userid = message.from_user.id if message.from_user else None 185 | if not userid: 186 | return await message.reply(f"You are anonymous admin. Use /connect {message.chat.id} in PM") 187 | chat_type = message.chat.type 188 | 189 | if chat_type == enums.ChatType.PRIVATE: 190 | grpid = await active_connection(str(userid)) 191 | if grpid is not None: 192 | grp_id = grpid 193 | try: 194 | chat = await client.get_chat(grpid) 195 | title = chat.title 196 | except: 197 | await message.reply_text("Make sure I'm present in your group!!", quote=True) 198 | return 199 | else: 200 | await message.reply_text("I'm not connected to any groups!", quote=True) 201 | return 202 | 203 | elif chat_type in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]: 204 | grp_id = message.chat.id 205 | title = message.chat.title 206 | 207 | else: 208 | return 209 | 210 | st = await client.get_chat_member(grp_id, userid) 211 | if ( 212 | st.status != enums.ChatMemberStatus.ADMINISTRATOR 213 | and st.status != enums.ChatMemberStatus.OWNER 214 | and str(userid) not in ADMINS 215 | ): 216 | return 217 | 218 | try: 219 | cmd, text = message.text.split(" ", 1) 220 | except: 221 | await message.reply_text( 222 | "Mention the filtername which you wanna delete!\n\n" 223 | "/del filtername\n\n" 224 | "Use /viewfilters to view all available filters", 225 | quote=True 226 | ) 227 | return 228 | 229 | query = text.lower() 230 | 231 | await delete_filter(message, query, grp_id) 232 | 233 | 234 | @Client.on_message(filters.command('delall') & filters.incoming) 235 | async def delallconfirm(client, message): 236 | userid = message.from_user.id if message.from_user else None 237 | if not userid: 238 | return await message.reply(f"You are anonymous admin. Use /connect {message.chat.id} in PM") 239 | chat_type = message.chat.type 240 | 241 | if chat_type == enums.ChatType.PRIVATE: 242 | grpid = await active_connection(str(userid)) 243 | if grpid is not None: 244 | grp_id = grpid 245 | try: 246 | chat = await client.get_chat(grpid) 247 | title = chat.title 248 | except: 249 | await message.reply_text("Make sure I'm present in your group!!", quote=True) 250 | return 251 | else: 252 | await message.reply_text("I'm not connected to any groups!", quote=True) 253 | return 254 | 255 | elif chat_type in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]: 256 | grp_id = message.chat.id 257 | title = message.chat.title 258 | 259 | else: 260 | return 261 | 262 | 263 | st = await client.get_chat_member(grp_id, userid) 264 | if (st.status == enums.ChatMemberStatus.OWNER) or (str(userid) in ADMINS): 265 | await message.reply_text( 266 | f"This will delete all filters from '{title}'.\nDo you want to continue??", 267 | reply_markup=InlineKeyboardMarkup([ 268 | [InlineKeyboardButton(text="YES",callback_data="delallconfirm")], 269 | [InlineKeyboardButton(text="CANCEL",callback_data="delallcancel")] 270 | ]), 271 | quote=True 272 | ) 273 | 274 | -------------------------------------------------------------------------------- /plugins/p_ttishow.py: -------------------------------------------------------------------------------- 1 | from pyrogram import Client, filters, enums 2 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup 3 | from pyrogram.errors.exceptions.bad_request_400 import MessageTooLong, PeerIdInvalid 4 | from info import ADMINS, LOG_CHANNEL, SUPPORT_CHAT, MELCOW_NEW_USERS 5 | from database.users_chats_db import db 6 | from database.ia_filterdb import Media 7 | from utils import get_size, temp, get_settings 8 | from Script import script 9 | from pyrogram.errors import ChatAdminRequired 10 | 11 | """-----------------------------------------https://t.me/GetTGLink/4179 --------------------------------------""" 12 | 13 | @Client.on_message(filters.new_chat_members & filters.group) 14 | async def save_group(bot, message): 15 | r_j_check = [u.id for u in message.new_chat_members] 16 | if temp.ME in r_j_check: 17 | if not await db.get_chat(message.chat.id): 18 | total=await bot.get_chat_members_count(message.chat.id) 19 | r_j = message.from_user.mention if message.from_user else "Anonymous" 20 | await bot.send_message(LOG_CHANNEL, script.LOG_TEXT_G.format(message.chat.title, message.chat.id, total, r_j)) 21 | await db.add_chat(message.chat.id, message.chat.title) 22 | if message.chat.id in temp.BANNED_CHATS: 23 | # Inspired from a boat of a banana tree 24 | buttons = [[ 25 | InlineKeyboardButton('𝚂𝚄𝙿𝙿𝙾𝚁𝚃', url=f'https://t.me/{SUPPORT_CHAT}') 26 | ]] 27 | reply_markup=InlineKeyboardMarkup(buttons) 28 | k = await message.reply( 29 | text='CHAT NOT ALLOWED 🐞\n\n𝙼𝚈 𝙰𝙳𝙼𝙸𝙽𝚂 𝙷𝙰𝚂 𝚁𝙴𝚂𝚃𝚁𝙸𝙲𝚃𝙴𝙳 𝙼𝙴 𝙵𝚁𝙾𝙼 𝚆𝙾𝚁𝙺𝙸𝙽𝙶 𝙷𝙴𝚁𝙴 !𝙸𝙵 𝚈𝙾𝚄 𝚆𝙰𝙽𝚃 𝚃𝙾 𝙺𝙽𝙾𝚆 𝙼𝙾𝚁𝙴 𝙰𝙱𝙾𝚄𝚃 𝙸𝚃 𝙲𝙾𝙽𝚃𝙰𝙲𝚃 𝙾𝚆𝙽𝙴𝚁...', 30 | reply_markup=reply_markup, 31 | ) 32 | 33 | try: 34 | await k.pin() 35 | except: 36 | pass 37 | await bot.leave_chat(message.chat.id) 38 | return 39 | buttons = [[ 40 | InlineKeyboardButton(f'ᴏᴛᴛ ᴜᴘᴅᴀᴛᴇs​', url='https://t.me/new_ott_movies3'), 41 | InlineKeyboardButton(f'ᴍᴀɪɴ ᴄʜᴀɴɴᴇʟ', url='https://t.me/mn_movies2') 42 | ]] 43 | reply_markup=InlineKeyboardMarkup(buttons) 44 | await message.reply_text( 45 | text=f"›› 𝚃𝙷𝙰𝙽𝙺𝚂 𝚃𝙾 𝙰𝙳𝙳 𝙼𝙴 𝚃𝙾 𝚈𝙾𝚄𝚁 𝙶𝚁𝙾𝚄𝙿. {message.chat.title} ❣️\n›› 𝙳𝙾𝙽'𝚃 𝙵𝙾𝚁𝙶𝙴𝚃 𝚃𝙾 𝙼𝙰𝙺𝙴 𝙼𝙴 𝙰𝙳𝙼𝙸𝙽.⚡⚡.", 46 | reply_markup=reply_markup) 47 | else: 48 | settings = await get_settings(message.chat.id) 49 | if settings["welcome"]: 50 | for u in message.new_chat_members: 51 | if (temp.MELCOW).get('welcome') is not None: 52 | try: 53 | await (temp.MELCOW['welcome']).delete() 54 | except: 55 | pass 56 | temp.MELCOW['welcome'] = await message.reply_video( 57 | video="https://mangandi-2-0.onrender.com/Xdgv.mp4", 58 | caption=f'
ʜᴇʏ, {u.mention} 👋🏻\nᴡᴇʟᴄᴏᴍᴇ ᴛᴏ ᴏᴜʀ ɢʀᴏᴜᴘ {message.chat.title}\n\nʏᴏᴜ ᴄᴀɴ ꜰɪɴᴅ ᴍᴏᴠɪᴇꜱ / ꜱᴇʀɪᴇꜱ / ᴀɴɪᴍᴇꜱ ᴇᴛᴄ. ꜰʀᴏᴍ ʜᴇʀᴇ. ᴇɴᴊᴏʏ😉.
', 59 | reply_markup=InlineKeyboardMarkup( [ [ InlineKeyboardButton('ɢʀᴏᴜᴘ', url='htpps://t.me/mn_movies_group2') ] ] ) 60 | ) 61 | 62 | @Client.on_message(filters.command('leave') & filters.user(ADMINS)) 63 | async def leave_a_chat(bot, message): 64 | if len(message.command) == 1: 65 | return await message.reply('Give me a chat id') 66 | chat = message.command[1] 67 | try: 68 | chat = int(chat) 69 | except: 70 | chat = chat 71 | try: 72 | buttons = [[ 73 | InlineKeyboardButton('𝚂𝚄𝙿𝙿𝙾𝚁𝚃', url=f'https://t.me/{SUPPORT_CHAT}') 74 | ]] 75 | reply_markup=InlineKeyboardMarkup(buttons) 76 | await bot.send_message( 77 | chat_id=chat, 78 | text='Hello Friends, \nMy admin has told me to leave from group so i go! If you wanna add me again contact my support group.', 79 | reply_markup=reply_markup, 80 | ) 81 | 82 | await bot.leave_chat(chat) 83 | await message.reply(f"left the chat `{chat}`") 84 | except Exception as e: 85 | await message.reply(f'Error - {e}') 86 | 87 | @Client.on_message(filters.command('disable') & filters.user(ADMINS)) 88 | async def disable_chat(bot, message): 89 | if len(message.command) == 1: 90 | return await message.reply('Give me a chat id') 91 | r = message.text.split(None) 92 | if len(r) > 2: 93 | reason = message.text.split(None, 2)[2] 94 | chat = message.text.split(None, 2)[1] 95 | else: 96 | chat = message.command[1] 97 | reason = "No reason Provided" 98 | try: 99 | chat_ = int(chat) 100 | except: 101 | return await message.reply('Give Me A Valid Chat ID') 102 | cha_t = await db.get_chat(int(chat_)) 103 | if not cha_t: 104 | return await message.reply("Chat Not Found In DB") 105 | if cha_t['is_disabled']: 106 | return await message.reply(f"This chat is already disabled:\nReason- {cha_t['reason']} ") 107 | await db.disable_chat(int(chat_), reason) 108 | temp.BANNED_CHATS.append(int(chat_)) 109 | await message.reply('Chat Successfully Disabled') 110 | try: 111 | buttons = [[ 112 | InlineKeyboardButton('𝚂𝚄𝙿𝙿𝙾𝚁𝚃', url=f'https://t.me/{SUPPORT_CHAT}') 113 | ]] 114 | reply_markup=InlineKeyboardMarkup(buttons) 115 | await bot.send_message( 116 | chat_id=chat_, 117 | text=f'Hello Friends, \nMy admin has told me to leave from group so i go! If you wanna add me again contact my support group. \nReason : {reason}', 118 | reply_markup=reply_markup) 119 | await bot.leave_chat(chat_) 120 | except Exception as e: 121 | await message.reply(f"Error - {e}") 122 | 123 | 124 | @Client.on_message(filters.command('enable') & filters.user(ADMINS)) 125 | async def re_enable_chat(bot, message): 126 | if len(message.command) == 1: 127 | return await message.reply('Give me a chat id') 128 | chat = message.command[1] 129 | try: 130 | chat_ = int(chat) 131 | except: 132 | return await message.reply('Give Me A Valid Chat ID') 133 | sts = await db.get_chat(int(chat)) 134 | if not sts: 135 | return await message.reply("Chat Not Found In DB !") 136 | if not sts.get('is_disabled'): 137 | return await message.reply('This chat is not yet disabled.') 138 | await db.re_enable_chat(int(chat_)) 139 | temp.BANNED_CHATS.remove(int(chat_)) 140 | await message.reply("Chat Successfully re-enabled") 141 | 142 | 143 | @Client.on_message(filters.command('stats') & filters.incoming) 144 | async def get_stats(bot, message): 145 | if message.from_user.id in ADMINS: 146 | rju = await message.reply('𝙰𝙲𝙲𝙴𝚂𝚂𝙸𝙽𝙶 𝚂𝚃𝙰𝚃𝚄𝚂 𝙳𝙴𝚃𝙰𝙸𝙻𝚂...') 147 | total_users = await db.total_users_count() 148 | totl_chats = await db.total_chat_count() 149 | files = await Media.count_documents() 150 | size = await db.get_db_size() 151 | free = 536870912 - size 152 | size = get_size(size) 153 | free = get_size(free) 154 | await rju.edit(script.STATUS_TXT.format(files, total_users, totl_chats, size, free)) 155 | else: 156 | k = await message.reply_text("Sᴏʀʀʏ ᴛʜɪꜱ ᴄᴏᴍᴍᴀɴᴅ ᴏɴʟʏ ᴀᴅᴍɪɴꜱ") 157 | await asyncio.sleep(10) 158 | await k.delete() 159 | await message.delete() 160 | 161 | 162 | # a function for trespassing into others groups, Inspired by a Vazha 163 | # Not to be used , But Just to showcase his vazhatharam. 164 | @Client.on_message(filters.command('invite') & filters.user(ADMINS)) 165 | async def gen_invite(bot, message): 166 | if len(message.command) == 1: 167 | return await message.reply('Give me a chat id') 168 | chat = message.command[1] 169 | try: 170 | chat = int(chat) 171 | except: 172 | return await message.reply('Give Me A Valid Chat ID') 173 | try: 174 | link = await bot.create_chat_invite_link(chat) 175 | except ChatAdminRequired: 176 | return await message.reply("Invite Link Generation Failed, Iam Not Having Sufficient Rights") 177 | except Exception as e: 178 | return await message.reply(f'Error {e}') 179 | await message.reply(f'Here is your Invite Link {link.invite_link}') 180 | 181 | @Client.on_message(filters.command('ban') & filters.user(ADMINS)) 182 | async def ban_a_user(bot, message): 183 | # https://t.me/GetTGLink/4185 184 | if len(message.command) == 1: 185 | return await message.reply('Give me a user id / username') 186 | r = message.text.split(None) 187 | if len(r) > 2: 188 | reason = message.text.split(None, 2)[2] 189 | chat = message.text.split(None, 2)[1] 190 | else: 191 | chat = message.command[1] 192 | reason = "No reason Provided" 193 | try: 194 | chat = int(chat) 195 | except: 196 | pass 197 | try: 198 | k = await bot.get_users(chat) 199 | except PeerIdInvalid: 200 | return await message.reply("This is an invalid user, make sure ia have met him before.") 201 | except IndexError: 202 | return await message.reply("This might be a channel, make sure its a user.") 203 | except Exception as e: 204 | return await message.reply(f'Error - {e}') 205 | else: 206 | jar = await db.get_ban_status(k.id) 207 | if jar['is_banned']: 208 | return await message.reply(f"{k.mention} is already banned\nReason: {jar['ban_reason']}") 209 | await db.ban_user(k.id, reason) 210 | temp.BANNED_USERS.append(k.id) 211 | await message.reply(f"Successfully banned {k.mention}") 212 | 213 | 214 | 215 | @Client.on_message(filters.command('unban') & filters.user(ADMINS)) 216 | async def unban_a_user(bot, message): 217 | if len(message.command) == 1: 218 | return await message.reply('Give me a user id / username') 219 | r = message.text.split(None) 220 | if len(r) > 2: 221 | reason = message.text.split(None, 2)[2] 222 | chat = message.text.split(None, 2)[1] 223 | else: 224 | chat = message.command[1] 225 | reason = "No reason Provided" 226 | try: 227 | chat = int(chat) 228 | except: 229 | pass 230 | try: 231 | k = await bot.get_users(chat) 232 | except PeerIdInvalid: 233 | return await message.reply("This is an invalid user, make sure ia have met him before.") 234 | except IndexError: 235 | return await message.reply("Thismight be a channel, make sure its a user.") 236 | except Exception as e: 237 | return await message.reply(f'Error - {e}') 238 | else: 239 | jar = await db.get_ban_status(k.id) 240 | if not jar['is_banned']: 241 | return await message.reply(f"{k.mention} is not yet banned.") 242 | await db.remove_ban(k.id) 243 | temp.BANNED_USERS.remove(k.id) 244 | await message.reply(f"Successfully unbanned {k.mention}") 245 | 246 | 247 | 248 | @Client.on_message(filters.command('users') & filters.user(ADMINS)) 249 | async def list_users(bot, message): 250 | # https://t.me/GetTGLink/4184 251 | raju = await message.reply('Getting List Of Users') 252 | users = await db.get_all_users() 253 | out = "Users Saved In DB Are:\n\n" 254 | async for user in users: 255 | out += f"{user['name']}" 256 | if user['ban_status']['is_banned']: 257 | out += '( Banned User )' 258 | out += '\n' 259 | try: 260 | await raju.edit_text(out) 261 | except MessageTooLong: 262 | with open('users.txt', 'w+') as outfile: 263 | outfile.write(out) 264 | await message.reply_document('users.txt', caption="List Of Users") 265 | 266 | @Client.on_message(filters.command('chats') & filters.user(ADMINS)) 267 | async def list_chats(bot, message): 268 | raju = await message.reply('Getting List Of chats') 269 | chats = await db.get_all_chats() 270 | out = "Chats Saved In DB Are:\n\n" 271 | async for chat in chats: 272 | out += f"**Title:** `{chat['title']}`\n**- ID:** `{chat['id']}`" 273 | if chat['chat_status']['is_disabled']: 274 | out += '( Disabled Chat )' 275 | out += '\n' 276 | try: 277 | await raju.edit_text(out) 278 | except MessageTooLong: 279 | with open('chats.txt', 'w+') as outfile: 280 | outfile.write(out) 281 | await message.reply_document('chats.txt', caption="List Of Chats") 282 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | # @MrMNTG @MusammilN 2 | #please give credits https://github.com/MN-BOTS/ShobanaFilterBot 3 | 4 | import logging 5 | from pyrogram.errors import InputUserDeactivated, UserNotParticipant, FloodWait, UserIsBlocked, PeerIdInvalid 6 | from info import LONG_IMDB_DESCRIPTION, MAX_LIST_ELM 7 | from imdb import IMDb 8 | import asyncio 9 | from pyrogram.types import Message, InlineKeyboardButton 10 | from pyrogram import enums 11 | from typing import Union 12 | import re 13 | import os 14 | from datetime import datetime 15 | from typing import List 16 | from database.users_chats_db import db 17 | from bs4 import BeautifulSoup 18 | import requests 19 | 20 | # @MrMNTG @MusammilN 21 | #please give credits https://github.com/MN-BOTS/ShobanaFilterBot 22 | logger = logging.getLogger(__name__) 23 | logger.setLevel(logging.INFO) 24 | 25 | BTN_URL_REGEX = re.compile( 26 | r"(\[([^\[]+?)\]\((buttonurl|buttonalert):(?:/{0,2})(.+?)(:same)?\))" 27 | ) 28 | 29 | imdb = IMDb() 30 | 31 | BANNED = {} 32 | SMART_OPEN = '“' 33 | SMART_CLOSE = '”' 34 | START_CHAR = ('\'', '"', SMART_OPEN) 35 | 36 | # temp db for banned 37 | class temp(object): 38 | BANNED_USERS = [] 39 | BANNED_CHATS = [] 40 | ME = None 41 | CURRENT=int(os.environ.get("SKIP", 2)) 42 | CANCEL = False 43 | MELCOW = {} 44 | U_NAME = None 45 | B_NAME = None 46 | SETTINGS = {} 47 | 48 | # @MrMNTG @MusammilN 49 | #please give credits https://github.com/MN-BOTS/ShobanaFilterBot 50 | from pyrogram.enums import ChatMemberStatus 51 | from database.users_chats_db import db 52 | from info import REQUEST_FSUB_MODE # Import from your info.py 53 | 54 | JOIN_REQUEST_USERS = {} 55 | 56 | async def is_subscribed(user_id: int, client) -> bool: 57 | auth_channels = await db.get_auth_channels() 58 | if not auth_channels: 59 | return True # No channels to check 60 | 61 | # Check if user is joined in channels 62 | joined_all = True 63 | for channel in auth_channels: 64 | try: 65 | member = await client.get_chat_member(channel, user_id) 66 | if member.status not in [ 67 | ChatMemberStatus.MEMBER, 68 | ChatMemberStatus.ADMINISTRATOR, 69 | ChatMemberStatus.OWNER, 70 | ]: 71 | joined_all = False 72 | break 73 | except Exception: 74 | joined_all = False 75 | break 76 | 77 | if joined_all: 78 | return True 79 | 80 | # If REQUEST_FSUB_MODE is True, check join requests 81 | if REQUEST_FSUB_MODE: 82 | requested_channels = JOIN_REQUEST_USERS.get(user_id, set()) 83 | if set(auth_channels).issubset(requested_channels): 84 | return True 85 | 86 | return False 87 | 88 | async def create_invite_links(client) -> dict: 89 | links = {} 90 | auth_channels = await db.get_auth_channels() 91 | for channel in auth_channels: 92 | try: 93 | invite = await client.create_chat_invite_link( 94 | channel, 95 | creates_join_request=REQUEST_FSUB_MODE, # Only enable join requests if REQUEST_FSUB_MODE is True 96 | name="BotAuthAccess" 97 | ) 98 | links[channel] = invite.invite_link 99 | except Exception: 100 | continue 101 | return links 102 | 103 | # @MrMNTG @MusammilN 104 | #please give credits https://github.com/MN-BOTS/ShobanaFilterBot 105 | async def get_poster(query, bulk=False, id=False, file=None): 106 | if not id: 107 | # https://t.me/GetTGLink/4183 108 | query = (query.strip()).lower() 109 | title = query 110 | year = re.findall(r'[1-2]\d{3}$', query, re.IGNORECASE) 111 | if year: 112 | year = list_to_str(year[:1]) 113 | title = (query.replace(year, "")).strip() 114 | elif file is not None: 115 | year = re.findall(r'[1-2]\d{3}', file, re.IGNORECASE) 116 | if year: 117 | year = list_to_str(year[:1]) 118 | else: 119 | year = None 120 | movieid = imdb.search_movie(title.lower(), results=10) 121 | if not movieid: 122 | return None 123 | if year: 124 | filtered=list(filter(lambda k: str(k.get('year')) == str(year), movieid)) 125 | if not filtered: 126 | filtered = movieid 127 | else: 128 | filtered = movieid 129 | movieid=list(filter(lambda k: k.get('kind') in ['movie', 'tv series'], filtered)) 130 | if not movieid: 131 | movieid = filtered 132 | if bulk: 133 | return movieid 134 | movieid = movieid[0].movieID 135 | else: 136 | movieid = query 137 | movie = imdb.get_movie(movieid) 138 | if movie.get("original air date"): 139 | date = movie["original air date"] 140 | elif movie.get("year"): 141 | date = movie.get("year") 142 | else: 143 | date = "N/A" 144 | plot = "" 145 | if not LONG_IMDB_DESCRIPTION: 146 | plot = movie.get('plot') 147 | if plot and len(plot) > 0: 148 | plot = plot[0] 149 | else: 150 | plot = movie.get('plot outline') 151 | if plot and len(plot) > 800: 152 | plot = plot[0:800] + "..." 153 | 154 | return { 155 | 'title': movie.get('title'), 156 | 'votes': movie.get('votes'), 157 | "aka": list_to_str(movie.get("akas")), 158 | "seasons": movie.get("number of seasons"), 159 | "box_office": movie.get('box office'), 160 | 'localized_title': movie.get('localized title'), 161 | 'kind': movie.get("kind"), 162 | "imdb_id": f"tt{movie.get('imdbID')}", 163 | "cast": list_to_str(movie.get("cast")), 164 | "runtime": list_to_str(movie.get("runtimes")), 165 | "countries": list_to_str(movie.get("countries")), 166 | "certificates": list_to_str(movie.get("certificates")), 167 | "languages": list_to_str(movie.get("languages")), 168 | "director": list_to_str(movie.get("director")), 169 | "writer":list_to_str(movie.get("writer")), 170 | "producer":list_to_str(movie.get("producer")), 171 | "composer":list_to_str(movie.get("composer")) , 172 | "cinematographer":list_to_str(movie.get("cinematographer")), 173 | "music_team": list_to_str(movie.get("music department")), 174 | "distributors": list_to_str(movie.get("distributors")), 175 | 'release_date': date, 176 | 'year': movie.get('year'), 177 | 'genres': list_to_str(movie.get("genres")), 178 | 'poster': movie.get('full-size cover url'), 179 | 'plot': plot, 180 | 'rating': str(movie.get("rating")), 181 | 'url':f'https://www.imdb.com/title/tt{movieid}' 182 | } 183 | # https://github.com/odysseusmax/animated-lamp/blob/2ef4730eb2b5f0596ed6d03e7b05243d93e3415b/bot/utils/broadcast.py#L37 184 | 185 | async def broadcast_messages(user_id, message): 186 | try: 187 | await message.copy(chat_id=user_id) 188 | return True, "Success" 189 | except FloodWait as e: 190 | await asyncio.sleep(e.x) 191 | return await broadcast_messages(user_id, message) 192 | except InputUserDeactivated: 193 | await db.delete_user(int(user_id)) 194 | logging.info(f"{user_id}-Removed from Database, since deleted account.") 195 | return False, "Deleted" 196 | except UserIsBlocked: 197 | logging.info(f"{user_id} -Blocked the bot.") 198 | return False, "Blocked" 199 | except PeerIdInvalid: 200 | await db.delete_user(int(user_id)) 201 | logging.info(f"{user_id} - PeerIdInvalid") 202 | return False, "Error" 203 | except Exception as e: 204 | return False, "Error" 205 | 206 | async def search_gagala(text): 207 | usr_agent = { 208 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 209 | 'Chrome/61.0.3163.100 Safari/537.36' 210 | } 211 | text = text.replace(" ", '+') 212 | url = f'https://www.google.com/search?q={text}' 213 | response = requests.get(url, headers=usr_agent) 214 | response.raise_for_status() 215 | soup = BeautifulSoup(response.text, 'html.parser') 216 | titles = soup.find_all( 'h3' ) 217 | return [title.getText() for title in titles] 218 | 219 | 220 | async def get_settings(group_id): 221 | settings = temp.SETTINGS.get(group_id) 222 | if not settings: 223 | settings = await db.get_settings(group_id) 224 | temp.SETTINGS[group_id] = settings 225 | return settings 226 | 227 | async def save_group_settings(group_id, key, value): 228 | current = await get_settings(group_id) 229 | current[key] = value 230 | temp.SETTINGS[group_id] = current 231 | await db.update_settings(group_id, current) 232 | 233 | def get_size(size): 234 | """Get size in readable format""" 235 | 236 | units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB"] 237 | size = float(size) 238 | i = 0 239 | while size >= 1024.0 and i < len(units): 240 | i += 1 241 | size /= 1024.0 242 | return "%.2f %s" % (size, units[i]) 243 | 244 | def split_list(l, n): 245 | for i in range(0, len(l), n): 246 | yield l[i:i + n] 247 | 248 | def get_file_id(msg: Message): 249 | if msg.media: 250 | for message_type in ( 251 | "photo", 252 | "animation", 253 | "audio", 254 | "document", 255 | "video", 256 | "video_note", 257 | "voice", 258 | "sticker" 259 | ): 260 | obj = getattr(msg, message_type) 261 | if obj: 262 | setattr(obj, "message_type", message_type) 263 | return obj 264 | 265 | def extract_user(message: Message) -> Union[int, str]: 266 | """extracts the user from a message""" 267 | # https://github.com/SpEcHiDe/PyroGramBot/blob/f30e2cca12002121bad1982f68cd0ff9814ce027/pyrobot/helper_functions/extract_user.py#L7 268 | user_id = None 269 | user_first_name = None 270 | if message.reply_to_message: 271 | user_id = message.reply_to_message.from_user.id 272 | user_first_name = message.reply_to_message.from_user.first_name 273 | 274 | elif len(message.command) > 1: 275 | if ( 276 | len(message.entities) > 1 and 277 | message.entities[1].type == enums.MessageEntityType.TEXT_MENTION 278 | ): 279 | 280 | required_entity = message.entities[1] 281 | user_id = required_entity.user.id 282 | user_first_name = required_entity.user.first_name 283 | else: 284 | user_id = message.command[1] 285 | # don't want to make a request -_- 286 | user_first_name = user_id 287 | try: 288 | user_id = int(user_id) 289 | except ValueError: 290 | pass 291 | else: 292 | user_id = message.from_user.id 293 | user_first_name = message.from_user.first_name 294 | return (user_id, user_first_name) 295 | 296 | def list_to_str(k): 297 | if not k: 298 | return "N/A" 299 | elif len(k) == 1: 300 | return str(k[0]) 301 | elif MAX_LIST_ELM: 302 | k = k[:int(MAX_LIST_ELM)] 303 | return ' '.join(f'{elem}, ' for elem in k) 304 | else: 305 | return ' '.join(f'{elem}, ' for elem in k) 306 | 307 | def last_online(from_user): 308 | time = "" 309 | if from_user.is_bot: 310 | time += "🤖 Bot :(" 311 | elif from_user.status == enums.UserStatus.RECENTLY: 312 | time += "Recently" 313 | elif from_user.status == enums.UserStatus.LAST_WEEK: 314 | time += "Within the last week" 315 | elif from_user.status == enums.UserStatus.LAST_MONTH: 316 | time += "Within the last month" 317 | elif from_user.status == enums.UserStatus.LONG_AGO: 318 | time += "A long time ago :(" 319 | elif from_user.status == enums.UserStatus.ONLINE: 320 | time += "Currently Online" 321 | elif from_user.status == enums.UserStatus.OFFLINE: 322 | time += from_user.last_online_date.strftime("%a, %d %b %Y, %H:%M:%S") 323 | return time 324 | 325 | 326 | def split_quotes(text: str) -> List: 327 | if not any(text.startswith(char) for char in START_CHAR): 328 | return text.split(None, 1) 329 | counter = 1 # ignore first char -> is some kind of quote 330 | while counter < len(text): 331 | if text[counter] == "\\": 332 | counter += 1 333 | elif text[counter] == text[0] or (text[0] == SMART_OPEN and text[counter] == SMART_CLOSE): 334 | break 335 | counter += 1 336 | else: 337 | return text.split(None, 1) 338 | 339 | # 1 to avoid starting quote, and counter is exclusive so avoids ending 340 | key = remove_escapes(text[1:counter].strip()) 341 | # index will be in range, or `else` would have been executed and returned 342 | rest = text[counter + 1:].strip() 343 | if not key: 344 | key = text[0] + text[0] 345 | return list(filter(None, [key, rest])) 346 | 347 | def parser(text, keyword): 348 | if "buttonalert" in text: 349 | text = (text.replace("\n", "\\n").replace("\t", "\\t")) 350 | buttons = [] 351 | note_data = "" 352 | prev = 0 353 | i = 0 354 | alerts = [] 355 | for match in BTN_URL_REGEX.finditer(text): 356 | # Check if btnurl is escaped 357 | n_escapes = 0 358 | to_check = match.start(1) - 1 359 | while to_check > 0 and text[to_check] == "\\": 360 | n_escapes += 1 361 | to_check -= 1 362 | 363 | # if even, not escaped -> create button 364 | if n_escapes % 2 == 0: 365 | note_data += text[prev:match.start(1)] 366 | prev = match.end(1) 367 | if match.group(3) == "buttonalert": 368 | # create a thruple with button label, url, and newline status 369 | if bool(match.group(5)) and buttons: 370 | buttons[-1].append(InlineKeyboardButton( 371 | text=match.group(2), 372 | callback_data=f"alertmessage:{i}:{keyword}" 373 | )) 374 | else: 375 | buttons.append([InlineKeyboardButton( 376 | text=match.group(2), 377 | callback_data=f"alertmessage:{i}:{keyword}" 378 | )]) 379 | i += 1 380 | alerts.append(match.group(4)) 381 | elif bool(match.group(5)) and buttons: 382 | buttons[-1].append(InlineKeyboardButton( 383 | text=match.group(2), 384 | url=match.group(4).replace(" ", "") 385 | )) 386 | else: 387 | buttons.append([InlineKeyboardButton( 388 | text=match.group(2), 389 | url=match.group(4).replace(" ", "") 390 | )]) 391 | 392 | else: 393 | note_data += text[prev:to_check] 394 | prev = match.start(1) - 1 395 | else: 396 | note_data += text[prev:] 397 | 398 | try: 399 | return note_data, buttons, alerts 400 | except: 401 | return note_data, buttons, None 402 | 403 | def remove_escapes(text: str) -> str: 404 | res = "" 405 | is_escaped = False 406 | for counter in range(len(text)): 407 | if is_escaped: 408 | res += text[counter] 409 | is_escaped = False 410 | elif text[counter] == "\\": 411 | is_escaped = True 412 | else: 413 | res += text[counter] 414 | return res 415 | 416 | 417 | def humanbytes(size): 418 | if not size: 419 | return "" 420 | power = 2**10 421 | n = 0 422 | Dic_powerN = {0: ' ', 1: 'Ki', 2: 'Mi', 3: 'Gi', 4: 'Ti'} 423 | while size > power: 424 | size /= power 425 | n += 1 426 | return str(round(size, 2)) + " " + Dic_powerN[n] + 'B' 427 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /plugins/commands.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import random 4 | import asyncio 5 | from Script import script 6 | from pyrogram import Client, filters, enums 7 | from pyrogram.errors import ChatAdminRequired, FloodWait 8 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup 9 | from database.ia_filterdb import Media, get_file_details, unpack_new_file_id 10 | from database.users_chats_db import db 11 | from info import CHANNELS, ADMINS, LOG_CHANNEL, PICS, BATCH_FILE_CAPTION, CUSTOM_FILE_CAPTION, PROTECT_CONTENT, FILE_CHANNELS, FILE_CHANNEL_SENDING_MODE, FILE_AUTO_DELETE_SECONDS 12 | from utils import get_settings, get_size, is_subscribed, save_group_settings, temp, create_invite_links 13 | from database.connections_mdb import active_connection 14 | import re 15 | import json 16 | from pyrogram.types import Message 17 | import base64 18 | logger = logging.getLogger(__name__) 19 | 20 | BATCH_FILES = {} 21 | 22 | # Add these imports at the top of your file 23 | from datetime import datetime, timedelta 24 | import random 25 | 26 | AUTO_DELETE_SECONDS = 15 27 | 28 | # Helper function to create buttons for specific channel 29 | async def create_file_buttons(client, sent_message): 30 | buttons = [] 31 | 32 | # Create message link 33 | if sent_message.chat.username: 34 | message_link = f"https://t.me/{sent_message.chat.username}/{sent_message.id}" 35 | else: 36 | channel_id = str(sent_message.chat.id).replace('-100', '') 37 | message_link = f"https://t.me/c/{channel_id}/{sent_message.id}" 38 | 39 | # Create invite link only for this channel 40 | try: 41 | chat = await client.get_chat(sent_message.chat.id) 42 | if chat.username: 43 | invite_link = f"https://t.me/{chat.username}" 44 | else: 45 | invite_link = (await client.create_chat_invite_link( 46 | sent_message.chat.id, 47 | name=f"FileAccess-{datetime.now().timestamp()}", 48 | expire_date=datetime.now() + timedelta(minutes=10), 49 | member_limit=1 50 | )).invite_link 51 | 52 | buttons.append([InlineKeyboardButton("📢 Join Channel", url=invite_link)]) 53 | buttons.append([InlineKeyboardButton("🔗 View File", url=message_link)]) 54 | except Exception as e: 55 | logger.error(f"Error creating invite: {e}") 56 | buttons.append([InlineKeyboardButton("🔗 View File", url=message_link)]) 57 | 58 | return InlineKeyboardMarkup(buttons) 59 | 60 | # Auto-delete helpers 61 | async def auto_delete_message(client, message, delay): 62 | await asyncio.sleep(delay) 63 | try: 64 | await message.delete() 65 | except Exception as e: 66 | logger.error(f"Error deleting message: {e}") 67 | 68 | async def auto_delete_file(client, message, delay): 69 | await asyncio.sleep(delay) 70 | try: 71 | await message.delete() 72 | logger.info(f"Deleted file from channel {message.chat.id}") 73 | except Exception as e: 74 | logger.error(f"Error deleting file: {e}") 75 | 76 | async def send_file_to_user(client, user_id, file_id, protect_content_flag, file_name=None, file_size=None, file_caption=None): 77 | try: 78 | # Generate proper caption 79 | caption = None 80 | if CUSTOM_FILE_CAPTION: 81 | try: 82 | caption = CUSTOM_FILE_CAPTION.format( 83 | file_name=file_name if file_name else "", 84 | file_size=file_size if file_size else "", 85 | file_caption=file_caption if file_caption else "" 86 | ) 87 | except Exception as e: 88 | logger.error(f"Error formatting caption: {e}") 89 | caption = file_caption if file_caption else file_name 90 | else: 91 | caption = file_caption if file_caption else file_name 92 | 93 | # File sending logic with channel support 94 | if FILE_CHANNEL_SENDING_MODE and FILE_CHANNELS: 95 | channel_id = random.choice(FILE_CHANNELS) 96 | sent_message = await client.send_cached_media( 97 | chat_id=channel_id, 98 | file_id=file_id, 99 | caption=caption, 100 | protect_content=protect_content_flag 101 | ) 102 | # Schedule auto-delete for channel file 103 | asyncio.create_task(auto_delete_file(client, sent_message, FILE_AUTO_DELETE_SECONDS)) 104 | 105 | # Create channel-specific buttons 106 | reply_markup = await create_file_buttons(client, sent_message) 107 | 108 | # Notify user with auto-delete 109 | user_msg = await client.send_message( 110 | chat_id=user_id, 111 | text=f"**Your file is ready!**\n\nJoin the channel to view your file ", 112 | protect_content=True, 113 | reply_markup=reply_markup 114 | ) 115 | asyncio.create_task(auto_delete_message(client, user_msg, AUTO_DELETE_SECONDS)) 116 | else: 117 | # Fallback to direct send with caption 118 | await client.send_cached_media( 119 | chat_id=user_id, 120 | file_id=file_id, 121 | caption=caption, 122 | protect_content=protect_content_flag, 123 | ) 124 | except Exception as e: 125 | logger.error(f"File send error: {e}") 126 | # Fallback to direct send if channel send fails 127 | await client.send_cached_media( 128 | chat_id=user_id, 129 | file_id=file_id, 130 | caption=caption, 131 | protect_content=protect_content_flag, 132 | ) 133 | 134 | @Client.on_callback_query(filters.regex(r'^checksubp#') | filters.regex(r'^checksub#')) 135 | async def checksub_callback(client, callback_query): 136 | # Extract data from callback 137 | data = callback_query.data 138 | pre, file_id = data.split('#', 1) 139 | user_id = callback_query.from_user.id 140 | protect_content_flag = True if pre == 'checksubp' else False 141 | 142 | # Get file details for caption 143 | files = await get_file_details(file_id) 144 | file_details = files[0] if files else None 145 | 146 | # Check subscription status 147 | if await is_subscribed(user_id, client): 148 | try: 149 | # Use helper function to send file via channels with proper caption 150 | await send_file_to_user( 151 | client=client, 152 | user_id=user_id, 153 | file_id=file_id, 154 | protect_content_flag=protect_content_flag, 155 | file_name=file_details.file_name if file_details else None, 156 | file_size=get_size(file_details.file_size) if file_details else None, 157 | file_caption=file_details.caption if file_details else None 158 | ) 159 | await callback_query.message.delete() 160 | except Exception as e: 161 | logger.error(f"File send error in callback: {e}") 162 | await callback_query.answer("Failed to send file. Please try again later.", show_alert=True) 163 | else: 164 | # Resend subscription prompt 165 | links = await create_invite_links(client) 166 | btn = [[InlineKeyboardButton("🤖 Join Updates Channel", url=url)] for url in links.values()] 167 | btn.append([InlineKeyboardButton("🔄 Try Again", callback_data=data)]) 168 | await callback_query.edit_message_text( 169 | text="**❌ You still haven't joined all channels!**\n\nPlease join and press Try Again:", 170 | reply_markup=InlineKeyboardMarkup(btn) 171 | ) 172 | 173 | @Client.on_message(filters.command("start") & filters.incoming) 174 | async def start(client, message): 175 | if message.chat.type in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]: 176 | buttons = [ 177 | [ 178 | InlineKeyboardButton(f'ᴏᴛᴛ ᴜᴘᴅᴀᴛᴇs​', url='https://t.me/new_ott_movies3'), 179 | InlineKeyboardButton(f'ᴍᴀɪɴ ᴄʜᴀɴɴᴇʟ', url='https://t.me/mn_movies2'), 180 | InlineKeyboardButton('ʀᴇᴘᴏ', url='https://github.com/mn-bots/ShobanaFilterBot') 181 | ] 182 | ] 183 | reply_markup = InlineKeyboardMarkup(buttons) 184 | await message.reply(script.START_TXT.format(message.from_user.mention if message.from_user else message.chat.title, temp.U_NAME, temp.B_NAME), reply_markup=reply_markup) 185 | await asyncio.sleep(2) 186 | if not await db.get_chat(message.chat.id): 187 | total=await client.get_chat_members_count(message.chat.id) 188 | await client.send_message(LOG_CHANNEL, script.LOG_TEXT_G.format(message.chat.title, message.chat.id, total, "Unknown")) 189 | await db.add_chat(message.chat.id, message.chat.title) 190 | return 191 | if not await db.is_user_exist(message.from_user.id): 192 | await db.add_user(message.from_user.id, message.from_user.first_name) 193 | await client.send_message(LOG_CHANNEL, script.LOG_TEXT_P.format(message.from_user.id, message.from_user.mention)) 194 | if len(message.command) != 2: 195 | buttons = [[ 196 | InlineKeyboardButton('ᴀᴅᴅ ᴍᴇ ᴛᴏ ʏᴏᴜʀ ɢʀᴏᴜᴘs', url=f'http://t.me/{temp.U_NAME}?startgroup=true') 197 | ],[ 198 | InlineKeyboardButton('ʜᴇʟᴘ', callback_data='help'), 199 | InlineKeyboardButton('ᴀʙᴏᴜᴛ', callback_data='about') 200 | ], [ 201 | InlineKeyboardButton(f'ᴏᴛᴛ ᴜᴘᴅᴀᴛᴇs​', url='https://t.me/new_ott_movies3'), 202 | InlineKeyboardButton(f'ᴍᴀɪɴ ᴄʜᴀɴɴᴇʟ', url='https://t.me/mn_movies2') 203 | ],[ 204 | InlineKeyboardButton('ʀᴇᴘᴏ', url='https://github.com/mn-bots/ShobanaFilterBot') 205 | ]] 206 | reply_markup = InlineKeyboardMarkup(buttons) 207 | m=await message.reply_text("ShobanaFilterBot") 208 | await asyncio.sleep(1.2) 209 | await m.delete() 210 | await message.reply_photo( 211 | photo=random.choice(PICS), 212 | caption=script.START_TXT.format(message.from_user.mention, temp.U_NAME, temp.B_NAME), 213 | reply_markup=reply_markup, 214 | parse_mode=enums.ParseMode.HTML 215 | ) 216 | return 217 | if not await is_subscribed(message.from_user.id, client): 218 | links = await create_invite_links(client) 219 | btn = [[InlineKeyboardButton("🤖 Join Updates Channel", url=url)] for url in links.values()] 220 | 221 | if len(message.command) == 2: 222 | try: 223 | kk, file_id = message.command[1].split("_", 1) 224 | pre = 'checksubp' if kk == 'filep' else 'checksub' 225 | btn.append([InlineKeyboardButton("🔄 Try Again", callback_data=f"{pre}#{file_id}")]) 226 | except (IndexError, ValueError): 227 | btn.append([InlineKeyboardButton("🔄 Try Again", url=f"https://t.me/{temp.U_NAME}?start={message.command[1]}")]) 228 | 229 | await client.send_message( 230 | chat_id=message.from_user.id, 231 | text="**Please Join My Updates Channel to use this Bot!**", 232 | reply_markup=InlineKeyboardMarkup(btn), 233 | parse_mode=enums.ParseMode.MARKDOWN 234 | ) 235 | return 236 | if len(message.command) == 2 and message.command[1] in ["subscribe", "error", "okay", "help"]: 237 | buttons = [[ 238 | InlineKeyboardButton('ᴀᴅᴅ ᴍᴇ ᴛᴏ ʏᴏᴜʀ ɢʀᴏᴜᴘs', url=f'http://t.me/{temp.U_NAME}?startgroup=true') 239 | ],[ 240 | InlineKeyboardButton('ʜᴇʟᴘ', callback_data='help'), 241 | InlineKeyboardButton('ᴀʙᴏᴜᴛ', callback_data='about') 242 | ],[ 243 | InlineKeyboardButton(f'ᴏᴛᴛ ᴜᴘᴅᴀᴛᴇs​', url='https://t.me/new_ott_movies3'), 244 | InlineKeyboardButton(f'ᴍᴀɪɴ ᴄʜᴀɴɴᴇʟ', url='https://t.me/mn_movies2') 245 | ],[ 246 | InlineKeyboardButton('ʀᴇᴘᴏ', url='https://github.com/mn-bots/ShobanaFilterBot') 247 | ]] 248 | reply_markup = InlineKeyboardMarkup(buttons) 249 | await message.reply_photo( 250 | photo=random.choice(PICS), 251 | caption=script.START_TXT.format(message.from_user.mention, temp.U_NAME, temp.B_NAME), 252 | reply_markup=reply_markup, 253 | parse_mode=enums.ParseMode.HTML 254 | ) 255 | return 256 | 257 | if len(message.command) == 2 and message.command[1].startswith('mntgx'): 258 | searches = message.command[1].split("-", 1)[1] 259 | search = searches.replace('-',' ') 260 | message.text = search 261 | await auto_filter(client, message) 262 | return 263 | data = message.command[1] 264 | try: 265 | pre, file_id = data.split('_', 1) 266 | except: 267 | file_id = data 268 | pre = "" 269 | if data.split("-", 1)[0] == "BATCH": 270 | sts = await message.reply("Please wait") 271 | file_id = data.split("-", 1)[1] 272 | msgs = BATCH_FILES.get(file_id) 273 | if not msgs: 274 | file = await client.download_media(file_id) 275 | try: 276 | with open(file) as file_data: 277 | msgs=json.loads(file_data.read()) 278 | except: 279 | await sts.edit("FAILED") 280 | return await client.send_message(LOG_CHANNEL, "UNABLE TO OPEN FILE.") 281 | os.remove(file) 282 | BATCH_FILES[file_id] = msgs 283 | for msg in msgs: 284 | title = msg.get("title") 285 | size=get_size(int(msg.get("size", 0))) 286 | f_caption=msg.get("caption", "") 287 | if BATCH_FILE_CAPTION: 288 | try: 289 | f_caption=BATCH_FILE_CAPTION.format(file_name= '' if title is None else title, file_size='' if size is None else size, file_caption='' if f_caption is None else f_caption) 290 | except Exception as e: 291 | logger.exception(e) 292 | f_caption=f_caption 293 | if f_caption is None: 294 | f_caption = f"{title}" 295 | try: 296 | await client.send_cached_media( 297 | chat_id=message.from_user.id, 298 | file_id=msg.get("file_id"), 299 | caption=f_caption, 300 | protect_content=msg.get('protect', False), 301 | ) 302 | except FloodWait as e: 303 | await asyncio.sleep(e.x) 304 | logger.warning(f"Floodwait of {e.x} sec.") 305 | await client.send_cached_media( 306 | chat_id=message.from_user.id, 307 | file_id=msg.get("file_id"), 308 | caption=f_caption, 309 | protect_content=msg.get('protect', False), 310 | ) 311 | except Exception as e: 312 | logger.warning(e, exc_info=True) 313 | continue 314 | await asyncio.sleep(1) 315 | await sts.delete() 316 | return 317 | elif data.split("-", 1)[0] == "DSTORE": 318 | sts = await message.reply("Please wait") 319 | b_string = data.split("-", 1)[1] 320 | decoded = (base64.urlsafe_b64decode(b_string + "=" * (-len(b_string) % 4))).decode("ascii") 321 | try: 322 | f_msg_id, l_msg_id, f_chat_id, protect = decoded.split("_", 3) 323 | except: 324 | f_msg_id, l_msg_id, f_chat_id = decoded.split("_", 2) 325 | protect = "/pbatch" if PROTECT_CONTENT else "batch" 326 | diff = int(l_msg_id) - int(f_msg_id) 327 | async for msg in client.iter_messages(int(f_chat_id), int(l_msg_id), int(f_msg_id)): 328 | if msg.media: 329 | media = getattr(msg, msg.media.value) 330 | if BATCH_FILE_CAPTION: 331 | try: 332 | f_caption=BATCH_FILE_CAPTION.format(file_name=getattr(media, 'file_name', ''), file_size=getattr(media, 'file_size', ''), file_caption=getattr(msg, 'caption', '')) 333 | except Exception as e: 334 | logger.exception(e) 335 | f_caption = getattr(msg, 'caption', '') 336 | else: 337 | media = getattr(msg, msg.media.value) 338 | file_name = getattr(media, 'file_name', '') 339 | f_caption = getattr(msg, 'caption', file_name) 340 | try: 341 | await msg.copy(message.chat.id, caption=f_caption, protect_content=True if protect == "/pbatch" else False) 342 | except FloodWait as e: 343 | await asyncio.sleep(e.x) 344 | await msg.copy(message.chat.id, caption=f_caption, protect_content=True if protect == "/pbatch" else False) 345 | except Exception as e: 346 | logger.exception(e) 347 | continue 348 | elif msg.empty: 349 | continue 350 | else: 351 | try: 352 | await msg.copy(message.chat.id, protect_content=True if protect == "/pbatch" else False) 353 | except FloodWait as e: 354 | await asyncio.sleep(e.x) 355 | await msg.copy(message.chat.id, protect_content=True if protect == "/pbatch" else False) 356 | except Exception as e: 357 | logger.exception(e) 358 | continue 359 | await asyncio.sleep(1) 360 | return await sts.delete() 361 | 362 | 363 | files_ = await get_file_details(file_id) 364 | if not files_: 365 | pre, file_id = ((base64.urlsafe_b64decode(data + "=" * (-len(data) % 4))).decode("ascii")).split("_", 1) 366 | try: 367 | protect_content_flag = True if pre == 'filep' else False 368 | 369 | # Use helper function for consistent file sending 370 | await send_file_to_user( 371 | client=client, 372 | user_id=message.from_user.id, 373 | file_id=file_id, 374 | protect_content_flag=protect_content_flag 375 | ) 376 | return 377 | except: 378 | pass 379 | return await message.reply('No such file exist.') 380 | files = files_[0] 381 | title = files.file_name 382 | size=get_size(files.file_size) 383 | f_caption=files.caption 384 | if CUSTOM_FILE_CAPTION: 385 | try: 386 | f_caption=CUSTOM_FILE_CAPTION.format(file_name= '' if title is None else title, file_size='' if size is None else size, file_caption='' if f_caption is None else f_caption) 387 | except Exception as e: 388 | logger.exception(e) 389 | f_caption=f_caption 390 | if f_caption is None: 391 | f_caption = f"{files.file_name}" 392 | 393 | protect_content_flag = True if pre == 'filep' else False 394 | 395 | # Use helper function for consistent file sending - FIXED: Removed 'caption' parameter 396 | await send_file_to_user( 397 | client=client, 398 | user_id=message.from_user.id, 399 | file_id=file_id, 400 | protect_content_flag=protect_content_flag, 401 | file_name=title, 402 | file_size=size, 403 | file_caption=f_caption 404 | ) 405 | 406 | def is_admin(user) -> bool: 407 | return ( 408 | user.id in ADMINS or 409 | (f"@{user.username}" in ADMINS if user.username else False) 410 | ) 411 | 412 | @Client.on_message(filters.command("fsub") & filters.private) 413 | async def set_auth_channels(client, message: Message): 414 | user = message.from_user 415 | if not is_admin(user): 416 | return await message.reply("🚫 You are not authorized to use this command.") 417 | 418 | args = message.text.split()[1:] 419 | if not args: 420 | return await message.reply("Usage: /fsub (channel_id1) (channel_id2) ...") 421 | 422 | try: 423 | channels = [int(cid) for cid in args] 424 | await db.set_auth_channels(channels) 425 | await message.reply(f"✅ AUTH_CHANNELs updated:\n{channels}") 426 | except ValueError: 427 | await message.reply("❌ Invalid channel IDs. Use numeric Telegram chat IDs.") 428 | 429 | @Client.on_message(filters.command('channel') & filters.user(ADMINS)) 430 | async def channel_info(bot, message): 431 | 432 | """Send basic information of channel""" 433 | if isinstance(CHANNELS, (int, str)): 434 | channels = [CHANNELS] 435 | elif isinstance(CHANNELS, list): 436 | channels = CHANNELS 437 | else: 438 | raise ValueError("Unexpected type of CHANNELS") 439 | 440 | text = '📑 **Indexed channels/groups**\n' 441 | for channel in channels: 442 | chat = await bot.get_chat(channel) 443 | if chat.username: 444 | text += '\n@' + chat.username 445 | else: 446 | text += '\n' + chat.title or chat.first_name 447 | 448 | text += f'\n\n**Total:** {len(CHANNELS)}' 449 | 450 | if len(text) < 4096: 451 | await message.reply(text) 452 | else: 453 | file = 'Indexed channels.txt' 454 | with open(file, 'w') as f: 455 | f.write(text) 456 | await message.reply_document(file) 457 | os.remove(file) 458 | 459 | 460 | @Client.on_message(filters.command('logs') & filters.user(ADMINS)) 461 | async def log_file(bot, message): 462 | """Send log file""" 463 | try: 464 | await message.reply_document('TelegramBot.txt') 465 | except Exception as e: 466 | await message.reply(str(e)) 467 | 468 | @Client.on_message(filters.command('delete') & filters.user(ADMINS)) 469 | async def delete(bot, message): 470 | """Delete file from database""" 471 | reply = message.reply_to_message 472 | if reply and reply.media: 473 | msg = await message.reply("Processing...⏳", quote=True) 474 | else: 475 | await message.reply('Reply to file with /delete which you want to delete', quote=True) 476 | return 477 | 478 | for file_type in ("document", "video", "audio"): 479 | media = getattr(reply, file_type, None) 480 | if media is not None: 481 | break 482 | else: 483 | await msg.edit('This is not supported file format') 484 | return 485 | 486 | file_id, file_ref = unpack_new_file_id(media.file_id) 487 | 488 | result = await Media.collection.delete_one({ 489 | '_id': file_id, 490 | }) 491 | if result.deleted_count: 492 | await msg.edit('File is successfully deleted from database') 493 | else: 494 | file_name = re.sub(r"(_|\-|\.|\+)", " ", str(media.file_name)) 495 | result = await Media.collection.delete_many({ 496 | 'file_name': file_name, 497 | 'file_size': media.file_size, 498 | 'mime_type': media.mime_type 499 | }) 500 | if result.deleted_count: 501 | await msg.edit('File is successfully deleted from database') 502 | else: 503 | result = await Media.collection.delete_many({ 504 | 'file_name': media.file_name, 505 | 'file_size': media.file_size, 506 | 'mime_type': media.mime_type 507 | }) 508 | if result.deleted_count: 509 | await msg.edit('File is successfully deleted from database') 510 | else: 511 | await msg.edit('File not found in database') 512 | 513 | 514 | @Client.on_message(filters.command('deleteall') & filters.user(ADMINS)) 515 | async def delete_all_index(bot, message): 516 | await message.reply_text( 517 | 'This will delete all indexed files.\nDo you want to continue??', 518 | reply_markup=InlineKeyboardMarkup( 519 | [ 520 | [ 521 | InlineKeyboardButton( 522 | text="YES", callback_data="autofilter_delete" 523 | ) 524 | ], 525 | [ 526 | InlineKeyboardButton( 527 | text="CANCEL", callback_data="close_data" 528 | ) 529 | ], 530 | ] 531 | ), 532 | quote=True, 533 | ) 534 | 535 | 536 | @Client.on_callback_query(filters.regex(r'^autofilter_delete')) 537 | async def delete_all_index_confirm(bot, message): 538 | await Media.collection.drop() 539 | await message.answer('Piracy Is Crime') 540 | await message.message.edit('Succesfully Deleted All The Indexed Files.') 541 | 542 | 543 | @Client.on_message(filters.command('settings')) 544 | async def settings(client, message): 545 | userid = message.from_user.id if message.from_user else None 546 | if not userid: 547 | return await message.reply(f"You are anonymous admin. Use /connect {message.chat.id} in PM") 548 | chat_type = message.chat.type 549 | 550 | if chat_type == enums.ChatType.PRIVATE: 551 | grpid = await active_connection(str(userid)) 552 | if grpid is not None: 553 | grp_id = grpid 554 | try: 555 | chat = await client.get_chat(grpid) 556 | title = chat.title 557 | except: 558 | await message.reply_text("Make sure I'm present in your group!!", quote=True) 559 | return 560 | else: 561 | await message.reply_text("I'm not connected to any groups!", quote=True) 562 | return 563 | 564 | elif chat_type in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]: 565 | grp_id = message.chat.id 566 | title = message.chat.title 567 | 568 | else: 569 | return 570 | 571 | st = await client.get_chat_member(grp_id, userid) 572 | if ( 573 | st.status != enums.ChatMemberStatus.ADMINISTRATOR 574 | and st.status != enums.ChatMemberStatus.OWNER 575 | and str(userid) not in ADMINS 576 | ): 577 | return 578 | 579 | settings = await get_settings(grp_id) 580 | 581 | if settings is not None: 582 | buttons = [ 583 | [ 584 | InlineKeyboardButton( 585 | 'Filter Button', 586 | callback_data=f'setgs#button#{settings["button"]}#{grp_id}', 587 | ), 588 | InlineKeyboardButton( 589 | 'Single' if settings["button"] else 'Double', 590 | callback_data=f'setgs#button#{settings["button"]}#{grp_id}', 591 | ), 592 | ], 593 | [ 594 | InlineKeyboardButton( 595 | 'Bot PM', 596 | callback_data=f'setgs#botpm#{settings["botpm"]}#{grp_id}', 597 | ), 598 | InlineKeyboardButton( 599 | '✅ Yes' if settings["botpm"] else '❌ No', 600 | callback_data=f'setgs#botpm#{settings["botpm"]}#{grp_id}', 601 | ), 602 | ], 603 | [ 604 | InlineKeyboardButton( 605 | 'File Secure', 606 | callback_data=f'setgs#file_secure#{settings["file_secure"]}#{grp_id}', 607 | ), 608 | InlineKeyboardButton( 609 | '✅ Yes' if settings["file_secure"] else '❌ No', 610 | callback_data=f'setgs#file_secure#{settings["file_secure"]}#{grp_id}', 611 | ), 612 | ], 613 | [ 614 | InlineKeyboardButton( 615 | 'IMDB', 616 | callback_data=f'setgs#imdb#{settings["imdb"]}#{grp_id}', 617 | ), 618 | InlineKeyboardButton( 619 | '✅ Yes' if settings["imdb"] else '❌ No', 620 | callback_data=f'setgs#imdb#{settings["imdb"]}#{grp_id}', 621 | ), 622 | ], 623 | [ 624 | InlineKeyboardButton( 625 | 'Spell Check', 626 | callback_data=f'setgs#spell_check#{settings["spell_check"]}#{grp_id}', 627 | ), 628 | InlineKeyboardButton( 629 | '✅ Yes' if settings["spell_check"] else '❌ No', 630 | callback_data=f'setgs#spell_check#{settings["spell_check"]}#{grp_id}', 631 | ), 632 | ], 633 | [ 634 | InlineKeyboardButton( 635 | 'Welcome', 636 | callback_data=f'setgs#welcome#{settings["welcome"]}#{grp_id}', 637 | ), 638 | InlineKeyboardButton( 639 | '✅ Yes' if settings["welcome"] else '❌ No', 640 | callback_data=f'setgs#welcome#{settings["welcome"]}#{grp_id}', 641 | ), 642 | ], 643 | ] 644 | 645 | reply_markup = InlineKeyboardMarkup(buttons) 646 | 647 | await message.reply_text( 648 | text=f"Change Your Settings for {title} As Your Wish ⚙", 649 | reply_markup=reply_markup, 650 | disable_web_page_preview=True, 651 | parse_mode=enums.ParseMode.HTML, 652 | reply_to_message_id=message.id 653 | ) 654 | 655 | 656 | 657 | @Client.on_message(filters.command('set_template')) 658 | async def save_template(client, message): 659 | sts = await message.reply("Checking template") 660 | userid = message.from_user.id if message.from_user else None 661 | if not userid: 662 | return await message.reply(f"You are anonymous admin. Use /connect {message.chat.id} in PM") 663 | chat_type = message.chat.type 664 | 665 | if chat_type == enums.ChatType.PRIVATE: 666 | grpid = await active_connection(str(userid)) 667 | if grpid is not None: 668 | grp_id = grpid 669 | try: 670 | chat = await client.get_chat(grpid) 671 | title = chat.title 672 | except: 673 | await message.reply_text("Make sure I'm present in your group!!", quote=True) 674 | return 675 | else: 676 | await message.reply_text("I'm not connected to any groups!", quote=True) 677 | return 678 | 679 | elif chat_type in [enums.ChatType.GROUP, enums.ChatType.SUPERGROUP]: 680 | grp_id = message.chat.id 681 | title = message.chat.title 682 | 683 | else: 684 | return 685 | 686 | st = await client.get_chat_member(grp_id, userid) 687 | if ( 688 | st.status != enums.ChatMemberStatus.ADMINISTRATOR 689 | and st.status != enums.ChatMemberStatus.OWNER 690 | and str(userid) not in ADMINS 691 | ): 692 | return 693 | 694 | if len(message.command) < 2: 695 | return await sts.edit("No Input!!") 696 | template = message.text.split(" ", 1)[1] 697 | await save_group_settings(grp_id, 'template', template) 698 | await sts.edit(f"Successfully changed template for {title} to\n\n{template}") 699 | --------------------------------------------------------------------------------