├── start.sh ├── heroku.yml ├── main.py ├── Procfile ├── requirements.txt ├── Dockerfile ├── config.py ├── logging.conf ├── LICENSE ├── app.json ├── plugins ├── utils.py ├── broadcast.py ├── commands.py ├── unequify.py ├── public.py ├── test.py ├── regix.py └── settings.py ├── bot.py ├── README.md ├── translation.py └── database.py /start.sh: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | worker: Dockerfile 4 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from bot import Bot 2 | 3 | app = Bot() 4 | app.run() 5 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | devgagan1: python main.py 2 | devgagan2: cp __init__.py src/devgagan/ && cd src && python -m devgagan -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyrofork 2 | motor 3 | TgCrypto 4 | Dnspython 5 | https://devgagan.in/wp-content/uploads/2023/12/gagantelethon.zip 6 | cryptg 7 | tgcrypto 8 | Pyrogram==2.0.93 9 | psutil 10 | opencv-python-headless 11 | python-decouple 12 | requests 13 | speedtest-cli 14 | pymongo 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-slim-buster 2 | 3 | RUN apt update && apt upgrade -y 4 | RUN apt install git -y 5 | COPY requirements.txt /requirements.txt 6 | 7 | RUN cd / 8 | RUN pip3 install -U pip && pip3 install -U -r requirements.txt 9 | RUN mkdir /fwdbot 10 | WORKDIR /fwdbot 11 | COPY start.sh /start.sh 12 | CMD ["/bin/bash", "/start.sh"] 13 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | 3 | class Config: 4 | API_ID = environ.get("API_ID", "577678") 5 | API_HASH = environ.get("API_HASH", "d2c6e01uuiuiouioiuiou0fc6d7a1be") 6 | BOT_TOKEN = environ.get("BOT_TOKEN", "70955...") 7 | BOT_SESSION = environ.get("BOT_SESSION", "bot") 8 | DATABASE_URI = environ.get("DATABASE", "mongodb+srv://chhjgjkkjhkjhkjh@cluster0.xowzpr4.mongodb.net/") 9 | DATABASE_NAME = environ.get("DATABASE_NAME", "forward-bot") 10 | BOT_OWNER_ID = [int(id) for id in environ.get("BOT_OWNER_ID", '6964148334').split()] 11 | 12 | class temp(object): 13 | lock = {} 14 | CANCEL = {} 15 | forwardings = 0 16 | BANNED_USERS = [] 17 | IS_FRWD_CHAT = [] 18 | 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.log','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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2024 devgaganin (as on GitHub) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Auto-Forward-Bot-V2", 3 | "description": "Telegram's Best Smart Plugin File Forward Bot", 4 | "logo": "https://telegra.ph/file/5c3499c3b2ec282cf0c4e.jpg", 5 | "stack": "container", 6 | "keywords": [ 7 | "telegram", 8 | "best", 9 | "file", 10 | "forward", 11 | "bot" 12 | ], 13 | "success_url": "https://t.me/devggn", 14 | "website": "https://github.com/devgaganin/Auto-Forward-Bot-V2", 15 | "repository": "https://github.com/devgaganin/Auto-Forward-Bot-V2", 16 | "env": { 17 | "API_ID": { 18 | "description": "Get this value from https://my.telegram.org or @UseTGSBot", 19 | "value": "" 20 | }, 21 | "API_HASH": { 22 | "description": "Get this value from https://my.telegram.org or @UseTGSBot", 23 | "value": "" 24 | }, 25 | "BOT_TOKEN": { 26 | "description": "Your bot token from @BotFather", 27 | "value": "" 28 | }, 29 | "BOT_OWNER_ID": { 30 | "description": "Enter Your Telegram id", 31 | "value": "" 32 | }, 33 | "BANNED_USERS": { 34 | "description": "If you want to add a caption to the forwarded file, enter it here", 35 | "required": false 36 | }, 37 | "DATABASE_NAME": { 38 | "description": "Type Of filters (document , audio , photo , video , animation)", 39 | "value": "document" 40 | }, 41 | "DATABASE_URI": { 42 | "description": "pyrogram string Seccion - https://replit.com/@JijinR/PyroSessionString?v=1",", 43 | "value": "" 44 | } 45 | }, 46 | "formation": { 47 | "worker": { 48 | "quantity": 1, 49 | "size": "free" 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /plugins/utils.py: -------------------------------------------------------------------------------- 1 | import time as tm 2 | from database import db 3 | from .test import parse_buttons 4 | 5 | STATUS = {} 6 | 7 | class STS: 8 | def __init__(self, id): 9 | self.id = id 10 | self.data = STATUS 11 | 12 | def verify(self): 13 | return self.data.get(self.id) 14 | 15 | def store(self, From, to, skip, limit): 16 | self.data[self.id] = {"FROM": From, 'TO': to, 'total_files': 0, 'skip': skip, 'limit': limit, 17 | 'fetched': skip, 'filtered': 0, 'deleted': 0, 'duplicate': 0, 'total': limit, 'start': 0} 18 | self.get(full=True) 19 | return STS(self.id) 20 | 21 | def get(self, value=None, full=False): 22 | values = self.data.get(self.id) 23 | if not full: 24 | return values.get(value) 25 | for k, v in values.items(): 26 | setattr(self, k, v) 27 | return self 28 | 29 | def add(self, key=None, value=1, time=False): 30 | if time: 31 | return self.data[self.id].update({'start': tm.time()}) 32 | self.data[self.id].update({key: self.get(key) + value}) 33 | 34 | def divide(self, no, by): 35 | by = 1 if int(by) == 0 else by 36 | return int(no) / by 37 | 38 | async def get_data(self, user_id): 39 | bot = await db.get_bot(user_id) 40 | k, filters = self, await db.get_filters(user_id) 41 | size, configs = None, await db.get_configs(user_id) 42 | if configs['duplicate']: 43 | duplicate = [configs['db_uri'], self.TO] 44 | else: 45 | duplicate = False 46 | button = parse_buttons(configs['button'] if configs['button'] else '') 47 | if configs['file_size'] != 0: 48 | size = [configs['file_size'], configs['size_limit']] 49 | return bot, configs['caption'], configs['forward_tag'], {'chat_id': k.FROM, 'limit': k.limit, 'offset': k.skip, 'filters': filters, 50 | 'keywords': configs['keywords'], 'media_size': size, 'extensions': configs['extension'], 'skip_duplicate': duplicate}, configs['protect'], button 51 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | import logging.config 4 | from database import db 5 | from config import Config 6 | from pyrogram import Client, __version__ 7 | from pyrogram.raw.all import layer 8 | from pyrogram.enums import ParseMode 9 | from pyrogram.errors import FloodWait 10 | 11 | logging.config.fileConfig('logging.conf') 12 | logging.getLogger().setLevel(logging.INFO) 13 | logging.getLogger("pyrogram").setLevel(logging.ERROR) 14 | 15 | class Bot(Client): 16 | def __init__(self): 17 | super().__init__( 18 | Config.BOT_SESSION, 19 | api_hash=Config.API_HASH, 20 | api_id=Config.API_ID, 21 | plugins={ 22 | "root": "plugins" 23 | }, 24 | workers=50, 25 | bot_token=Config.BOT_TOKEN 26 | ) 27 | self.log = logging 28 | 29 | async def start(self): 30 | await super().start() 31 | me = await self.get_me() 32 | logging.info(f"{me.first_name} with for pyrogram v{__version__} (Layer {layer}) started on @{me.username}.") 33 | self.id = me.id 34 | self.username = me.username 35 | self.first_name = me.first_name 36 | self.set_parse_mode(ParseMode.DEFAULT) 37 | text = "**๏[-ิ_•ิ]๏ bot restarted !**" 38 | logging.info(text) 39 | success = failed = 0 40 | users = await db.get_all_frwd() 41 | async for user in users: 42 | chat_id = user['user_id'] 43 | try: 44 | await self.send_message(chat_id, text) 45 | success += 1 46 | except FloodWait as e: 47 | await asyncio.sleep(e.value + 1) 48 | await self.send_message(chat_id, text) 49 | success += 1 50 | except Exception: 51 | failed += 1 52 | # await self.send_message("venombotsupport", text) 53 | if (success + failed) != 0: 54 | await db.rmve_frwd(all=True) 55 | logging.info(f"Restart message status" 56 | f"success: {success}" 57 | f"failed: {failed}") 58 | 59 | async def stop(self, *args): 60 | msg = f"@{self.username} stopped. Bye." 61 | await super().stop() 62 | logging.info(msg) -------------------------------------------------------------------------------- /plugins/broadcast.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import time, datetime 3 | from database import db 4 | from config import Config 5 | from pyrogram import Client, filters 6 | from pyrogram.errors import InputUserDeactivated, FloodWait, UserIsBlocked 7 | 8 | @Client.on_message(filters.command("broadcast") & filters.user(Config.BOT_OWNER_ID) & filters.reply) 9 | async def broadcast (bot, message): 10 | users = await db.get_all_users() 11 | b_msg = message.reply_to_message 12 | sts = await message.reply_text( 13 | text='Broadcasting your messages...' 14 | ) 15 | start_time = time.time() 16 | total_users, k = await db.total_users_bots_count() 17 | done = 0 18 | blocked = 0 19 | deleted = 0 20 | failed = 0 21 | success = 0 22 | async for user in users: 23 | pti, sh = await broadcast_messages(int(user['id']), b_msg, bot.log) 24 | if pti: 25 | success += 1 26 | await asyncio.sleep(2) 27 | elif pti == False: 28 | if sh == "Blocked": 29 | blocked+=1 30 | elif sh == "Deleted": 31 | deleted += 1 32 | elif sh == "Error": 33 | failed += 1 34 | done += 1 35 | if not done % 20: 36 | await sts.edit(f"Broadcast in progress:\n\nTotal Users {total_users}\nCompleted: {done} / {total_users}\nSuccess: {success}\nBlocked: {blocked}\nDeleted: {deleted}") 37 | time_taken = datetime.timedelta(seconds=int(time.time()-start_time)) 38 | await sts.edit(f"Broadcast Completed:\nCompleted in {time_taken} seconds.\n\nTotal Users {total_users}\nCompleted: {done} / {total_users}\nSuccess: {success}\nBlocked: {blocked}\nDeleted: {deleted}") 39 | 40 | async def broadcast_messages(user_id, message, log): 41 | try: 42 | await message.copy(chat_id=user_id) 43 | return True, "Success" 44 | except FloodWait as e: 45 | await asyncio.sleep(e.x) 46 | return await broadcast_messages(user_id, message, log) 47 | except InputUserDeactivated: 48 | await db.delete_user(int(user_id)) 49 | log.info(f"{user_id}-Removed from Database, since deleted account.") 50 | return False, "Deleted" 51 | except UserIsBlocked: 52 | log.info(f"{user_id} -Blocked the bot.") 53 | return False, "Blocked" 54 | except Exception as e: 55 | return False, "Error" 56 | 57 | 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advance Content Saver Bot 2 | 3 | Advance Telegram Bot that can work as Message/Files Forwarder from Restricted or Non-Restricted Channels/Groups/Bots. It can save files even if the channel have restrictions of forwarding or else. 4 | 5 | ## What can this bot do 6 | - Forward public/private channel messages for private user login needed as forward method 7 | - Save/clone individual post by its post link 8 | - Can save multiple post link via `batch` command up to 10K 9 | 10 | ## Deployment Methods 11 | 12 | ### VPS Deployment 13 | 14 | 1. **Initial Setup**: 15 | -`config.py` files with your variables. 16 | 17 | 2. **Clone and Run**: 18 | - Clone your forked and edited repository: 19 | ```bash 20 | git clone forked_edited_repo_link 21 | ``` 22 | - Navigate to the repository directory: 23 | ```bash 24 | cd repo_name 25 | ``` 26 | - Run the bot: 27 | ```bash 28 | python main.py 29 | ``` 30 | 31 | ### Heroku Deployment 32 | 33 | 1. **Setup on Heroku**: 34 | - Go to [Heroku Dashboard](https://dashboard.heroku.com) and create a new app. 35 | - Connect your GitHub repository to Heroku. 36 | - Search and deploy the forked repository. 37 | - Back to the app view and refresh the page. 38 | 39 | 2. **Configure Dynos**: 40 | - Configure dynos for `devagagan1` and `devgagan2`. 41 | 42 | ## Additional Notes 43 | 44 | - Make sure to replace `forked_edited_repo_link` with the link to your forked and edited repository. 45 | - Replace `repo_name` with the name of your repository. 46 | - Update the `__init__.py` and `config.py` files with your bot's specific variables before deployment. 47 | - Ensure that your bot is configured correctly according to the requirements of the Telegram Bot API. 48 | 49 | ## Commands Available in Bot - [TEAM SPY](https://t.me/dev_gagan) 50 | 51 | - ```/start``` - to start the bot 52 | - ```/cancel``` - to cancel the onging /batch task 53 | - ```/stats``` - to viewing the statics of bot 54 | - `/forward or /fwd` - to start forward 55 | - `restart` - to restart the bot 56 | - `/resetall` - to reset unlink all other users / bot 57 | - `/broadcast` - send bulk message to all users who ever have started the bot 58 | - `/help` - get help about other commands 59 | 60 | ## Support 61 | 62 | [](https://instagram.com/devagagn.in) 63 | [](https://youtube.com/@dev_gagan) 64 | [](https://t.me/dev_gagan) 65 | [](https://github.com/devgaganin) 66 | [](https://devgagan.in) 67 | 68 | ## Terms of USE / Modification 69 | Visit [Terms](https://github.com/devgaganin/Save-Restricted-Content-Bot-Repo/blob/main/TERMS_OF_USE.md) and accept the guidelines. 70 | 71 | ## Contributing 72 | 73 | Contributions are welcome! Please feel free to submit issues or pull requests. 74 | 75 | ## License 76 | 77 | This project is licensed under the [MIT License](LICENSE). 78 | -------------------------------------------------------------------------------- /plugins/commands.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import asyncio 4 | from database import db, mongodb_version 5 | from config import Config, temp 6 | from platform import python_version 7 | from translation import Translation 8 | from pyrogram import Client, filters, enums, __version__ as pyrogram_version 9 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, InputMediaDocument 10 | 11 | main_buttons = [[ 12 | InlineKeyboardButton('Main Channel', url='https://t.me/dev_gagan') 13 | ],[ 14 | InlineKeyboardButton('📜 sᴜᴘᴘᴏʀᴛ ɢʀᴏᴜᴘ ', url='https://t.me/dev_gagan'), 15 | InlineKeyboardButton('🤖 ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ ', url='https://t.me/dev_gagan') 16 | ],[ 17 | InlineKeyboardButton('🙋‍♂️ ʜᴇʟᴘ', callback_data='help'), 18 | InlineKeyboardButton('💁‍♂️ ᴀʙᴏᴜᴛ ', callback_data='about') 19 | ],[ 20 | InlineKeyboardButton('⚙️ sᴇᴛᴛɪɴɢs ⚙️', callback_data='settings#main') 21 | ]] 22 | #===================Start Function===================# 23 | 24 | @Client.on_message(filters.private & filters.command(['start'])) 25 | async def start(client, message): 26 | user = message.from_user 27 | if not await db.is_user_exist(user.id): 28 | await db.add_user(user.id, user.first_name) 29 | reply_markup = InlineKeyboardMarkup(main_buttons) 30 | await client.send_message( 31 | chat_id=message.chat.id, 32 | reply_markup=InlineKeyboardMarkup(main_buttons), 33 | text=Translation.START_TXT.format(message.from_user.first_name)) 34 | 35 | #==================Restart Function==================# 36 | 37 | @Client.on_message(filters.private & filters.command(['restart']) & filters.user(Config.BOT_OWNER_ID)) 38 | async def restart(client, message): 39 | msg = await message.reply_text( 40 | text="Trying to restarting....." 41 | ) 42 | await asyncio.sleep(5) 43 | await msg.edit("Server restarted successfully ✅") 44 | os.execl(sys.executable, sys.executable, *sys.argv) 45 | 46 | #==================Callback Functions==================# 47 | 48 | @Client.on_callback_query(filters.regex(r'^help')) 49 | async def helpcb(bot, query): 50 | await query.message.edit_text( 51 | text=Translation.HELP_TXT, 52 | reply_markup=InlineKeyboardMarkup( 53 | [[ 54 | InlineKeyboardButton('ʜᴏᴡ ᴛᴏ ᴜsᴇ ᴍᴇ ❓', callback_data='how_to_use') 55 | ],[ 56 | InlineKeyboardButton('⚙️ sᴇᴛᴛɪɴɢs ', callback_data='settings#main'), 57 | InlineKeyboardButton('📜 sᴛᴀᴛᴜs ', callback_data='status') 58 | ],[ 59 | InlineKeyboardButton('↩ ʙᴀᴄᴋ', callback_data='back') 60 | ]] 61 | )) 62 | 63 | @Client.on_callback_query(filters.regex(r'^how_to_use')) 64 | async def how_to_use(bot, query): 65 | await query.message.edit_text( 66 | text=Translation.HOW_USE_TXT, 67 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton('↩ Back', callback_data='help')]]), 68 | disable_web_page_preview=True 69 | ) 70 | 71 | @Client.on_callback_query(filters.regex(r'^back')) 72 | async def back(bot, query): 73 | reply_markup = InlineKeyboardMarkup(main_buttons) 74 | await query.message.edit_text( 75 | reply_markup=reply_markup, 76 | text=Translation.START_TXT.format( 77 | query.from_user.first_name)) 78 | 79 | @Client.on_callback_query(filters.regex(r'^about')) 80 | async def about(bot, query): 81 | await query.message.edit_text( 82 | text=Translation.ABOUT_TXT.format(my_name='Public Forward',python_version=python_version(),pyrogram_version=pyrogram_version,mongodb_version=await mongodb_version()), 83 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton('↩ Back', callback_data='back')]]), 84 | disable_web_page_preview=True, 85 | parse_mode=enums.ParseMode.HTML, 86 | ) 87 | 88 | @Client.on_callback_query(filters.regex(r'^status')) 89 | async def status(bot, query): 90 | users_count, bots_count = await db.total_users_bots_count() 91 | total_channels = await db.total_channels() 92 | await query.message.edit_text( 93 | text=Translation.STATUS_TXT.format(users_count, bots_count, temp.forwardings, total_channels, temp.BANNED_USERS ), 94 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton('↩ Back', callback_data='help')]]), 95 | parse_mode=enums.ParseMode.HTML, 96 | disable_web_page_preview=True, 97 | ) 98 | -------------------------------------------------------------------------------- /plugins/unequify.py: -------------------------------------------------------------------------------- 1 | import re, asyncio 2 | from database import db 3 | from config import temp 4 | from .test import CLIENT , start_clone_bot 5 | from translation import Translation 6 | from pyrogram import Client, filters 7 | #from pyropatch.utils import unpack_new_file_id 8 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup 9 | 10 | CLIENT = CLIENT() 11 | COMPLETED_BTN = InlineKeyboardMarkup( 12 | [ 13 | [InlineKeyboardButton('⚡ Support', url='https://t.me/dev_gagan')], 14 | [InlineKeyboardButton('📢 Updates', url='https://t.me/dev_gagan')] 15 | ] 16 | ) 17 | 18 | CANCEL_BTN = InlineKeyboardMarkup([[InlineKeyboardButton('• ᴄᴀɴᴄᴇʟ', 'terminate_frwd')]]) 19 | 20 | @Client.on_message(filters.command("unequify") & filters.private) 21 | async def unequify(client, message): 22 | user_id = message.from_user.id 23 | temp.CANCEL[user_id] = False 24 | if temp.lock.get(user_id) and str(temp.lock.get(user_id))=="True": 25 | return await message.reply("**please wait until previous task complete**") 26 | _bot = await db.get_bot(user_id) 27 | if not _bot or _bot['is_bot']: 28 | return await message.reply("Need userbot to do this process. Please add a userbot using /settings") 29 | target = await client.ask(user_id, text="**Forward the last message from target chat or send last message link.**\n/cancel - `cancel this process`") 30 | if target.text.startswith("/"): 31 | return await message.reply("**process cancelled !**") 32 | elif target.text: 33 | regex = re.compile(r"(https://)?(t\.me/|telegram\.me/|telegram\.dog/)(c/)?(\d+|[a-zA-Z_0-9]+)/(\d+)$") 34 | match = regex.match(target.text.replace("?single", "")) 35 | if not match: 36 | return await message.reply('**Invalid link**') 37 | chat_id = match.group(4) 38 | last_msg_id = int(match.group(5)) 39 | if chat_id.isnumeric(): 40 | chat_id = int(("-100" + chat_id)) 41 | elif fromid.forward_from_chat.type in ['channel', 'supergroup']: 42 | last_msg_id = target.forward_from_message_id 43 | chat_id = target.forward_from_chat.username or target.forward_from_chat.id 44 | else: 45 | return await message.reply_text("**invalid !**") 46 | confirm = await client.ask(user_id, text="**send /yes to start the process and /no to cancel this process**") 47 | if confirm.text.lower() == '/no': 48 | return await confirm.reply("**process cancelled !**") 49 | sts = await confirm.reply("`processing..`") 50 | try: 51 | bot = await start_clone_bot(CLIENT.client(_bot)) 52 | except Exception as e: 53 | return await sts.edit(e) 54 | try: 55 | k = await bot.send_message(chat_id, text="testing") 56 | await k.delete() 57 | except: 58 | await sts.edit(f"**please make your [userbot](t.me/{_bot['username']}) admin in target chat with full permissions**") 59 | return await bot.stop() 60 | MESSAGES = [] 61 | DUPLICATE = [] 62 | total=deleted=0 63 | temp.lock[user_id] = True 64 | try: 65 | await sts.edit(Translation.DUPLICATE_TEXT.format(total, deleted, "ᴘʀᴏɢʀᴇssɪɴɢ"), reply_markup=CANCEL_BTN) 66 | async for message in bot.search_messages(chat_id=chat_id, filter="document"): 67 | if temp.CANCEL.get(user_id) == True: 68 | await sts.edit(Translation.DUPLICATE_TEXT.format(total, deleted, "ᴄᴀɴᴄᴇʟʟᴇᴅ"), reply_markup=COMPLETED_BTN) 69 | return await bot.stop() 70 | file = message.document 71 | file_id = unpack_new_file_id(file.file_id) 72 | if file_id in MESSAGES: 73 | DUPLICATE.append(message.id) 74 | else: 75 | MESSAGES.append(file_id) 76 | total += 1 77 | if total %10000 == 0: 78 | await sts.edit(Translation.DUPLICATE_TEXT.format(total, deleted, "ᴘʀᴏɢʀᴇssɪɴɢ"), reply_markup=CANCEL_BTN) 79 | if len(DUPLICATE) >= 100: 80 | await bot.delete_messages(chat_id, DUPLICATE) 81 | deleted += 100 82 | await sts.edit(Translation.DUPLICATE_TEXT.format(total, deleted, "ᴘʀᴏɢʀᴇssɪɴɢ"), reply_markup=CANCEL_BTN) 83 | DUPLICATE = [] 84 | if DUPLICATE: 85 | await bot.delete_messages(chat_id, DUPLICATE) 86 | deleted += len(DUPLICATE) 87 | except Exception as e: 88 | temp.lock[user_id] = False 89 | await sts.edit(f"**ERROR**\n`{e}`") 90 | return await bot.stop() 91 | temp.lock[user_id] = False 92 | await sts.edit(Translation.DUPLICATE_TEXT.format(total, deleted, "ᴄᴏᴍᴘʟᴇᴛᴇᴅ"), reply_markup=COMPLETED_BTN) 93 | await bot.stop() 94 | 95 | -------------------------------------------------------------------------------- /plugins/public.py: -------------------------------------------------------------------------------- 1 | import re 2 | import asyncio 3 | from .utils import STS 4 | from database import db 5 | from config import temp 6 | from translation import Translation 7 | from pyrogram import Client, filters, enums 8 | from pyrogram.errors import FloodWait 9 | from pyrogram.errors.exceptions.not_acceptable_406 import ChannelPrivate as PrivateChat 10 | from pyrogram.errors.exceptions.bad_request_400 import ChannelInvalid, ChatAdminRequired, UsernameInvalid, UsernameNotModified, ChannelPrivate 11 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove 12 | 13 | #===================Run Function===================# 14 | 15 | @Client.on_message(filters.private & filters.command(["fwd", "forward"])) 16 | async def run(bot, message): 17 | buttons = [] 18 | btn_data = {} 19 | user_id = message.from_user.id 20 | _bot = await db.get_bot(user_id) 21 | if not _bot: 22 | return await message.reply("You didn't added any bot. Please add a bot using /settings !") 23 | channels = await db.get_user_channels(user_id) 24 | if not channels: 25 | return await message.reply_text("please set a to channel in /settings before forwarding") 26 | if len(channels) > 1: 27 | for channel in channels: 28 | buttons.append([KeyboardButton(f"{channel['title']}")]) 29 | btn_data[channel['title']] = channel['chat_id'] 30 | buttons.append([KeyboardButton("cancel")]) 31 | _toid = await bot.ask(message.chat.id, Translation.TO_MSG.format(_bot['name'], _bot['username']), reply_markup=ReplyKeyboardMarkup(buttons, one_time_keyboard=True, resize_keyboard=True)) 32 | if _toid.text.startswith(('/', 'cancel')): 33 | return await message.reply_text(Translation.CANCEL, reply_markup=ReplyKeyboardRemove()) 34 | to_title = _toid.text 35 | toid = btn_data.get(to_title) 36 | if not toid: 37 | return await message.reply_text("wrong channel choosen !", reply_markup=ReplyKeyboardRemove()) 38 | else: 39 | toid = channels[0]['chat_id'] 40 | to_title = channels[0]['title'] 41 | fromid = await bot.ask(message.chat.id, Translation.FROM_MSG, reply_markup=ReplyKeyboardRemove()) 42 | if fromid.text and fromid.text.startswith('/'): 43 | await message.reply(Translation.CANCEL) 44 | return 45 | if fromid.text and not fromid.forward_date: 46 | regex = re.compile(r"(https://)?(t\.me/|telegram\.me/|telegram\.dog/)(c/)?(\d+|[a-zA-Z_0-9]+)/(\d+)$") 47 | match = regex.match(fromid.text.replace("?single", "")) 48 | if not match: 49 | return await message.reply('Invalid link') 50 | chat_id = match.group(4) 51 | last_msg_id = int(match.group(5)) 52 | if chat_id.isnumeric(): 53 | chat_id = int(("-100" + chat_id)) 54 | elif fromid.forward_from_chat.type in [enums.ChatType.CHANNEL]: 55 | last_msg_id = fromid.forward_from_message_id 56 | chat_id = fromid.forward_from_chat.username or fromid.forward_from_chat.id 57 | if last_msg_id == None: 58 | return await message.reply_text("**This may be a forwarded message from a group and sended by anonymous admin. instead of this please send last message link from group**") 59 | else: 60 | await message.reply_text("**invalid !**") 61 | return 62 | try: 63 | title = (await bot.get_chat(chat_id)).title 64 | # except ChannelInvalid: 65 | #return await fromid.reply("**Given source chat is copyrighted channel/group. you can't forward messages from there**") 66 | except (PrivateChat, ChannelPrivate, ChannelInvalid): 67 | title = "private" if fromid.text else fromid.forward_from_chat.title 68 | except (UsernameInvalid, UsernameNotModified): 69 | return await message.reply('Invalid Link specified.') 70 | except Exception as e: 71 | return await message.reply(f'Errors - {e}') 72 | skipno = await bot.ask(message.chat.id, Translation.SKIP_MSG) 73 | if skipno.text.startswith('/'): 74 | await message.reply(Translation.CANCEL) 75 | return 76 | forward_id = f"{user_id}-{skipno.id}" 77 | buttons = [[ 78 | InlineKeyboardButton('Yes', callback_data=f"start_public_{forward_id}"), 79 | InlineKeyboardButton('No', callback_data="close_btn") 80 | ]] 81 | reply_markup = InlineKeyboardMarkup(buttons) 82 | await message.reply_text( 83 | text=Translation.DOUBLE_CHECK.format(botname=_bot['name'], botuname=_bot['username'], from_chat=title, to_chat=to_title, skip=skipno.text), 84 | disable_web_page_preview=True, 85 | reply_markup=reply_markup 86 | ) 87 | STS(forward_id).store(chat_id, toid, int(skipno.text), int(last_msg_id)) 88 | -------------------------------------------------------------------------------- /translation.py: -------------------------------------------------------------------------------- 1 | import os 2 | from config import Config 3 | 4 | class Translation(object): 5 | START_TXT = """ʜᴇʟʟᴏ {} 6 | 7 | ɪ'ᴍ ᴀ ᴘᴏᴡᴇʀғᴜʟʟ ᴀᴜᴛᴏ ғᴏʀᴡᴀʀᴅ ʙᴏᴛ 8 | 9 | ɪ ᴄᴀɴ ғᴏʀᴡᴀʀᴅ ᴀʟʟ ᴍᴇssᴀɢᴇ ғʀᴏᴍ ᴏɴᴇ ᴄʜᴀɴɴᴇʟ ᴛᴏ ᴀɴᴏᴛʜᴇʀ ᴄʜᴀɴɴᴇʟ ➜ ᴡɪᴛʜ ᴍᴏʀᴇ ғᴇᴀᴛᴜʀᴇs. 10 | ᴄʟɪᴄᴋ ʜᴇʟᴘ ʙᴜᴛᴛᴏɴ ᴛᴏ ᴋɴᴏᴡ ᴍᴏʀᴇ ᴀʙᴏᴜᴛ ᴍᴇ""" 11 | 12 | 13 | HELP_TXT = """🔆 HELP 14 | 15 | **📚 Available commands:** 16 | ⏣ __/start - check I'm alive__ 17 | ⏣ __/forward - forward messages__ 18 | ⏣ __/unequify - delete duplicate messages in channels__ 19 | ⏣ __/settings - configure your settings__ 20 | ⏣ __/reset - reset your settings__ 21 | 22 | 💢 Features: 23 | ► __Forward message from public channel to your channel without admin permission. if the channel is private need admin permission__ 24 | ► __Forward message from private channel to your channel by using userbot(user must be member in there)__ 25 | ► __custom caption__ 26 | ► __custom button__ 27 | ► __support restricted chats__ 28 | ► __skip duplicate messages__ 29 | ► __filter type of messages__ 30 | ► __skip messages based on extensions & keywords & size__ 31 | """ 32 | 33 | HOW_USE_TXT = """⚠️ Before Forwarding: 34 | ► __add a bot or userbot__ 35 | ► __add atleast one to channel__ `(your bot/userbot must be admin in there)` 36 | ► __You can add chats or bots by using /settings__ 37 | ► __if the **From Channel** is private your userbot must be member in there or your bot must need admin permission in there also__ 38 | ► __Then use /forward to forward messages__""" 39 | 40 | ABOUT_TXT = """╭──────❰ 🤖 Bot Details ❱──────〄 41 | │ 42 | │ 🤖 Mʏ Nᴀᴍᴇ : Dev Gagan Botᴛ 43 | │ 👨‍💻 ᴅᴇᴠᴘʟᴏᴇʀ : Team SPY 44 | │ 🤖 ᴜᴘᴅᴀᴛᴇ : devgagan 45 | │ 📡 ʜᴏsᴛ ᴏɴ : Dev Gagan Host 46 | │ 🗣️ ʟᴀɴɢᴜᴀɢᴇ : ᴘʏᴛʜᴏɴ 3 47 | {python_version} 48 | │ 📚 ʟɪʙʀᴀʀʏ : ᴘʏʀᴏɢʀᴀᴍ 49 | ╰────────────────────⍟""" 50 | 51 | STATUS_TXT = """╭──────❪ 🤖 Bot Status ❫─────⍟ 52 | │ 53 | ├👨 ᴜsᴇʀs : {} 54 | │ 55 | ├🤖 ʙᴏᴛs : {} 56 | │ 57 | ├📣 ᴄʜᴀɴɴᴇʟ : {} 58 | ╰───────────────────⍟""" 59 | 60 | FROM_MSG = "❪ SET SOURCE CHAT ❫\n\nForward the last message or last message link of source chat.\n/cancel - cancel this process" 61 | TO_MSG = "❪ CHOOSE TARGET CHAT ❫\n\nChoose your target chat from the given buttons.\n/cancel - Cancel this process" 62 | SKIP_MSG = "❪ SET MESSAGE SKIPING NUMBER ❫\n\nSkip the message as much as you enter the number and the rest of the message will be forwarded\nDefault Skip Number = 0\neg: You enter 0 = 0 message skiped\n You enter 5 = 5 message skiped\n/cancel - cancel this process" 63 | CANCEL = "Process Cancelled Succefully !" 64 | BOT_DETAILS = "📄 BOT DETAILS\n\n➣ NAME: {}\n➣ BOT ID: {}\n➣ USERNAME: @{}" 65 | USER_DETAILS = "📄 USERBOT DETAILS\n\n➣ NAME: {}\n➣ USER ID: {}\n➣ USERNAME: @{}" 66 | 67 | TEXT = """╭────❰ Forwarded Status ❱────❍ 68 | ┃ 69 | ┣⊸🕵 ғᴇᴄʜᴇᴅ ᴍsɢ : {} 70 | ┣⊸✅ sᴜᴄᴄᴇғᴜʟʟʏ ғᴡᴅ : {} 71 | ┣⊸👥 ᴅᴜᴘʟɪᴄᴀᴛᴇ ᴍsɢ : {} 72 | ┣⊸🗑️ ᴅᴇʟᴇᴛᴇᴅ ᴍsɢ : {} 73 | ┣⊸🪆 sᴋɪᴘᴘᴇᴅ ᴍsɢ : {} 74 | ┣⊸📊 sᴛᴀᴛᴜs : {} 75 | ┣⊸⏳ ᴘʀᴏɢʀᴇss : {} % 76 | ┣⊸⏰ ᴇᴛᴀ : {} 77 | ┃ 78 | ╰────⌊ {} ⌉───❍""" 79 | 80 | TEXT1 = """╭─❰ Forwarded Status ❱─❍ 81 | ┃ 82 | ┣⊸🕵𝙁𝙚𝙘𝙝𝙚𝙙 𝙈𝙨𝙜 : {} 83 | ┣⊸✅𝙎𝙪𝙘𝙘𝙚𝙛𝙪𝙡𝙮 𝙁𝙬𝙙 : {} 84 | ┣⊸👥𝘿𝙪𝙥𝙡𝙞𝙘𝙖𝙩𝙚 𝙈𝙨𝙜: {} 85 | ┣⊸🗑𝘿𝙚𝙡𝙚𝙩𝙚𝙙 𝙈𝙨𝙜: {} 86 | ┣⊸🪆𝙎𝙠𝙞𝙥𝙥𝙚𝙙 : {} 87 | ┣⊸📊𝙎𝙩𝙖𝙩𝙨 : {} 88 | ┣⊸⏳𝙋𝙧𝙤𝙜𝙧𝙚𝙨𝙨 : {} 89 | ┣⊸𝙀𝙏𝘼 : {} 90 | ┃ 91 | ╰─⌊ {} ⌉─❍""" 92 | 93 | DUPLICATE_TEXT = """ 94 | ╔════❰ ᴜɴᴇǫᴜɪғʏ sᴛᴀᴛᴜs ❱═❍⊱❁۪۪ 95 | ║╭━━━━━━━━━━━━━━━➣ 96 | ║┣⪼ ғᴇᴛᴄʜᴇᴅ ғɪʟᴇs: {} 97 | ║┃ 98 | ║┣⪼ ᴅᴜᴘʟɪᴄᴀᴛᴇ ᴅᴇʟᴇᴛᴇᴅ: {} 99 | ║╰━━━━━━━━━━━━━━━➣ 100 | ╚════❰ {} ❱══❍⊱❁۪۪ 101 | """ 102 | DOUBLE_CHECK = """DOUBLE CHECKING ⚠️ 103 | Before forwarding the messages Click the Yes button only after checking the following 104 | 105 | ★ YOUR BOT: [{botname}](t.me/{botuname}) 106 | ★ FROM CHANNEL: `{from_chat}` 107 | ★ TO CHANNEL: `{to_chat}` 108 | ★ SKIP MESSAGES: `{skip}` 109 | 110 | ° [{botname}](t.me/{botuname}) must be admin in **TARGET CHAT** (`{to_chat}`) 111 | ° If the **SOURCE CHAT** is private your userbot must be member or your bot must be admin in there also 112 | 113 | If the above is checked then the yes button can be clicked""" 114 | -------------------------------------------------------------------------------- /database.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | from config import Config 3 | import motor.motor_asyncio 4 | from pymongo import MongoClient 5 | 6 | async def mongodb_version(): 7 | x = MongoClient(Config.DATABASE_URI) 8 | mongodb_version = x.server_info()['version'] 9 | return mongodb_version 10 | 11 | class Database: 12 | 13 | def __init__(self, uri, database_name): 14 | self._client = motor.motor_asyncio.AsyncIOMotorClient(uri) 15 | self.db = self._client[database_name] 16 | self.bot = self.db.bots 17 | self.col = self.db.users 18 | self.nfy = self.db.notify 19 | self.chl = self.db.channels 20 | 21 | def new_user(self, id, name): 22 | return dict( 23 | id = id, 24 | name = name, 25 | ban_status=dict( 26 | is_banned=False, 27 | ban_reason="", 28 | ), 29 | ) 30 | 31 | async def add_user(self, id, name): 32 | user = self.new_user(id, name) 33 | await self.col.insert_one(user) 34 | 35 | async def is_user_exist(self, id): 36 | user = await self.col.find_one({'id':int(id)}) 37 | return bool(user) 38 | 39 | async def total_users_bots_count(self): 40 | bcount = await self.bot.count_documents({}) 41 | count = await self.col.count_documents({}) 42 | return count, bcount 43 | 44 | async def total_channels(self): 45 | count = await self.chl.count_documents({}) 46 | return count 47 | 48 | async def remove_ban(self, id): 49 | ban_status = dict( 50 | is_banned=False, 51 | ban_reason='' 52 | ) 53 | await self.col.update_one({'id': id}, {'$set': {'ban_status': ban_status}}) 54 | 55 | async def ban_user(self, user_id, ban_reason="No Reason"): 56 | ban_status = dict( 57 | is_banned=True, 58 | ban_reason=ban_reason 59 | ) 60 | await self.col.update_one({'id': user_id}, {'$set': {'ban_status': ban_status}}) 61 | 62 | async def get_ban_status(self, id): 63 | default = dict( 64 | is_banned=False, 65 | ban_reason='' 66 | ) 67 | user = await self.col.find_one({'id':int(id)}) 68 | if not user: 69 | return default 70 | return user.get('ban_status', default) 71 | 72 | async def get_all_users(self): 73 | return self.col.find({}) 74 | 75 | async def delete_user(self, user_id): 76 | await self.col.delete_many({'id': int(user_id)}) 77 | 78 | async def get_banned(self): 79 | users = self.col.find({'ban_status.is_banned': True}) 80 | b_users = [user['id'] async for user in users] 81 | return b_users 82 | 83 | async def update_configs(self, id, configs): 84 | await self.col.update_one({'id': int(id)}, {'$set': {'configs': configs}}) 85 | 86 | async def get_configs(self, id): 87 | default = { 88 | 'caption': None, 89 | 'duplicate': True, 90 | 'forward_tag': False, 91 | 'file_size': 0, 92 | 'size_limit': None, 93 | 'extension': None, 94 | 'keywords': None, 95 | 'protect': None, 96 | 'button': None, 97 | 'db_uri': None, 98 | 'filters': { 99 | 'poll': True, 100 | 'text': True, 101 | 'audio': True, 102 | 'voice': True, 103 | 'video': True, 104 | 'photo': True, 105 | 'document': True, 106 | 'animation': True, 107 | 'sticker': True 108 | } 109 | } 110 | user = await self.col.find_one({'id':int(id)}) 111 | if user: 112 | return user.get('configs', default) 113 | return default 114 | 115 | async def add_bot(self, datas): 116 | if not await self.is_bot_exist(datas['user_id']): 117 | await self.bot.insert_one(datas) 118 | 119 | async def remove_bot(self, user_id): 120 | await self.bot.delete_many({'user_id': int(user_id)}) 121 | 122 | async def get_bot(self, user_id: int): 123 | bot = await self.bot.find_one({'user_id': user_id}) 124 | return bot if bot else None 125 | 126 | async def is_bot_exist(self, user_id): 127 | bot = await self.bot.find_one({'user_id': user_id}) 128 | return bool(bot) 129 | 130 | async def in_channel(self, user_id: int, chat_id: int) -> bool: 131 | channel = await self.chl.find_one({"user_id": int(user_id), "chat_id": int(chat_id)}) 132 | return bool(channel) 133 | 134 | async def add_channel(self, user_id: int, chat_id: int, title, username): 135 | channel = await self.in_channel(user_id, chat_id) 136 | if channel: 137 | return False 138 | return await self.chl.insert_one({"user_id": user_id, "chat_id": chat_id, "title": title, "username": username}) 139 | 140 | async def remove_channel(self, user_id: int, chat_id: int): 141 | channel = await self.in_channel(user_id, chat_id ) 142 | if not channel: 143 | return False 144 | return await self.chl.delete_many({"user_id": int(user_id), "chat_id": int(chat_id)}) 145 | 146 | async def get_channel_details(self, user_id: int, chat_id: int): 147 | return await self.chl.find_one({"user_id": int(user_id), "chat_id": int(chat_id)}) 148 | 149 | async def get_user_channels(self, user_id: int): 150 | channels = self.chl.find({"user_id": int(user_id)}) 151 | return [channel async for channel in channels] 152 | 153 | async def get_filters(self, user_id): 154 | filters = [] 155 | filter = (await self.get_configs(user_id))['filters'] 156 | for k, v in filter.items(): 157 | if v == False: 158 | filters.append(str(k)) 159 | return filters 160 | 161 | async def add_frwd(self, user_id): 162 | return await self.nfy.insert_one({'user_id': int(user_id)}) 163 | 164 | async def rmve_frwd(self, user_id=0, all=False): 165 | data = {} if all else {'user_id': int(user_id)} 166 | return await self.nfy.delete_many(data) 167 | 168 | async def get_all_frwd(self): 169 | return self.nfy.find({}) 170 | 171 | db = Database(Config.DATABASE_URI, Config.DATABASE_NAME) 172 | -------------------------------------------------------------------------------- /plugins/test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import sys 4 | import typing 5 | import asyncio 6 | import logging 7 | from database import db 8 | from config import Config, temp 9 | from pyrogram import Client, filters 10 | from pyrogram.raw.all import layer 11 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message 12 | from pyrogram.errors.exceptions.bad_request_400 import AccessTokenExpired, AccessTokenInvalid 13 | from pyrogram.errors import FloodWait 14 | from config import Config 15 | from translation import Translation 16 | 17 | from typing import Union, Optional, AsyncGenerator 18 | 19 | logger = logging.getLogger(__name__) 20 | logger.setLevel(logging.INFO) 21 | 22 | BTN_URL_REGEX = re.compile(r"(\[([^\[]+?)]\[buttonurl:/{0,2}(.+?)(:same)?])") 23 | BOT_TOKEN_TEXT = "1) create a bot using @BotFather\n2) Then you will get a message with bot token\n3) Forward that message to me" 24 | SESSION_STRING_SIZE = 351 25 | 26 | async def start_clone_bot(FwdBot, data=None): 27 | await FwdBot.start() 28 | # 29 | async def iter_messages( 30 | self, 31 | chat_id: Union[int, str], 32 | limit: int, 33 | offset: int = 0, 34 | search: str = None, 35 | filter: "types.TypeMessagesFilter" = None, 36 | ) -> Optional[AsyncGenerator["types.Message", None]]: 37 | """Iterate through a chat sequentially. 38 | This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.get_messages` in a loop, thus saving 39 | you from the hassle of setting up boilerplate code. It is useful for getting the whole chat messages with a 40 | single call. 41 | Parameters: 42 | chat_id (``int`` | ``str``): 43 | Unique identifier (int) or username (str) of the target chat. 44 | For your personal cloud (Saved Messages) you can simply use "me" or "self". 45 | For a contact that exists in your Telegram address book you can use his phone number (str). 46 | 47 | limit (``int``): 48 | Identifier of the last message to be returned. 49 | 50 | offset (``int``, *optional*): 51 | Identifier of the first message to be returned. 52 | Defaults to 0. 53 | Returns: 54 | ``Generator``: A generator yielding :obj:`~pyrogram.types.Message` objects. 55 | Example: 56 | .. code-block:: python 57 | for message in app.iter_messages("pyrogram", 1, 15000): 58 | print(message.text) 59 | """ 60 | current = offset 61 | while True: 62 | new_diff = min(200, limit - current) 63 | if new_diff <= 0: 64 | return 65 | messages = await self.get_messages(chat_id, list(range(current, current+new_diff+1))) 66 | for message in messages: 67 | yield message 68 | current += 1 69 | # 70 | FwdBot.iter_messages = iter_messages 71 | return FwdBot 72 | 73 | class CLIENT: 74 | def __init__(self): 75 | self.api_id = Config.API_ID 76 | self.api_hash = Config.API_HASH 77 | 78 | def client(self, data, user=None): 79 | if user == None and data.get('is_bot') == False: 80 | return Client("USERBOT", self.api_id, self.api_hash, session_string=data.get('session')) 81 | elif user == True: 82 | return Client("USERBOT", self.api_id, self.api_hash, session_string=data) 83 | elif user != False: 84 | data = data.get('token') 85 | return Client("BOT", self.api_id, self.api_hash, bot_token=data, in_memory=True) 86 | 87 | async def add_bot(self, bot, message): 88 | user_id = int(message.from_user.id) 89 | msg = await bot.ask(chat_id=user_id, text=BOT_TOKEN_TEXT) 90 | if msg.text=='/cancel': 91 | return await msg.reply('process cancelled !') 92 | elif not msg.forward_date: 93 | return await msg.reply_text("This is not a forward message") 94 | elif str(msg.forward_from.id) != "93372553": 95 | return await msg.reply_text("This message was not forward from bot father") 96 | bot_token = re.findall(r'\d[0-9]{8,10}:[0-9A-Za-z_-]{35}', msg.text, re.IGNORECASE) 97 | bot_token = bot_token[0] if bot_token else None 98 | if not bot_token: 99 | return await msg.reply_text("There is no bot token in that message") 100 | try: 101 | _client = await start_clone_bot(self.client(bot_token, False), True) 102 | except Exception as e: 103 | await msg.reply_text(f"BOT ERROR: `{e}`") 104 | _bot = _client.me 105 | details = { 106 | 'id': _bot.id, 107 | 'is_bot': True, 108 | 'user_id': user_id, 109 | 'name': _bot.first_name, 110 | 'token': bot_token, 111 | 'username': _bot.username 112 | } 113 | await db.add_bot(details) 114 | return True 115 | 116 | async def add_session(self, bot, message): 117 | user_id = int(message.from_user.id) 118 | text = "⚠️ DISCLAIMER ⚠️\n\nyou can use your session for forward message from private chat to another chat.\nPlease add your pyrogram session with your own risk. Their is a chance to ban your account. My developer is not responsible if your account may get banned." 119 | await bot.send_message(user_id, text=text) 120 | msg = await bot.ask(chat_id=user_id, text="send your pyrogram session.\nGet it from trusted sources.\n\n/cancel - cancel the process") 121 | if msg.text=='/cancel': 122 | return await msg.reply('process cancelled !') 123 | elif len(msg.text) < SESSION_STRING_SIZE: 124 | return await msg.reply('invalid session sring') 125 | try: 126 | client = await start_clone_bot(self.client(msg.text, True), True) 127 | except Exception as e: 128 | await msg.reply_text(f"USER BOT ERROR: `{e}`") 129 | user = client.me 130 | details = { 131 | 'id': user.id, 132 | 'is_bot': False, 133 | 'user_id': user_id, 134 | 'name': user.first_name, 135 | 'session': msg.text, 136 | 'username': user.username 137 | } 138 | await db.add_bot(details) 139 | return True 140 | 141 | @Client.on_message(filters.private & filters.command('reset')) 142 | async def forward_tag(bot, m): 143 | default = await db.get_configs("01") 144 | temp.CONFIGS[m.from_user.id] = default 145 | await db.update_configs(m.from_user.id, default) 146 | await m.reply("successfully settings reseted ✔️") 147 | 148 | @Client.on_message(filters.command('resetall') & filters.user(Config.BOT_OWNER_ID)) 149 | async def resetall(bot, message): 150 | users = await db.get_all_users() 151 | sts = await message.reply("**processing**") 152 | TEXT = "total: {}\nsuccess: {}\nfailed: {}\nexcept: {}" 153 | total = success = failed = already = 0 154 | ERRORS = [] 155 | async for user in users: 156 | user_id = user['id'] 157 | default = await get_configs(user_id) 158 | default['db_uri'] = None 159 | total += 1 160 | if total %10 == 0: 161 | await sts.edit(TEXT.format(total, success, failed, already)) 162 | try: 163 | await db.update_configs(user_id, default) 164 | success += 1 165 | except Exception as e: 166 | ERRORS.append(e) 167 | failed += 1 168 | if ERRORS: 169 | await message.reply(ERRORS[:100]) 170 | await sts.edit("completed\n" + TEXT.format(total, success, failed, already)) 171 | 172 | async def get_configs(user_id): 173 | #configs = temp.CONFIGS.get(user_id) 174 | #if not configs: 175 | configs = await db.get_configs(user_id) 176 | #temp.CONFIGS[user_id] = configs 177 | return configs 178 | 179 | async def update_configs(user_id, key, value): 180 | current = await db.get_configs(user_id) 181 | if key in ['caption', 'duplicate', 'db_uri', 'forward_tag', 'protect', 'file_size', 'size_limit', 'extension', 'keywords', 'button']: 182 | current[key] = value 183 | else: 184 | current['filters'][key] = value 185 | # temp.CONFIGS[user_id] = value 186 | await db.update_configs(user_id, current) 187 | 188 | def parse_buttons(text, markup=True): 189 | buttons = [] 190 | for match in BTN_URL_REGEX.finditer(text): 191 | n_escapes = 0 192 | to_check = match.start(1) - 1 193 | while to_check > 0 and text[to_check] == "\\": 194 | n_escapes += 1 195 | to_check -= 1 196 | 197 | if n_escapes % 2 == 0: 198 | if bool(match.group(4)) and buttons: 199 | buttons[-1].append(InlineKeyboardButton( 200 | text=match.group(2), 201 | url=match.group(3).replace(" ", ""))) 202 | else: 203 | buttons.append([InlineKeyboardButton( 204 | text=match.group(2), 205 | url=match.group(3).replace(" ", ""))]) 206 | if markup and buttons: 207 | buttons = InlineKeyboardMarkup(buttons) 208 | return buttons if buttons else None 209 | -------------------------------------------------------------------------------- /plugins/regix.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import math 4 | import time 5 | import asyncio 6 | import logging 7 | from .utils import STS 8 | from database import db 9 | from .test import CLIENT , start_clone_bot 10 | from config import Config, temp 11 | from translation import Translation 12 | from pyrogram import Client, filters 13 | #from pyropatch.utils import unpack_new_file_id 14 | from pyrogram.errors import FloodWait, MessageNotModified, RPCError 15 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery, Message 16 | 17 | CLIENT = CLIENT() 18 | logger = logging.getLogger(__name__) 19 | logger.setLevel(logging.INFO) 20 | TEXT = Translation.TEXT 21 | 22 | @Client.on_callback_query(filters.regex(r'^start_public')) 23 | async def pub_(bot, message): 24 | user = message.from_user.id 25 | temp.CANCEL[user] = False 26 | frwd_id = message.data.split("_")[2] 27 | if temp.lock.get(user) and str(temp.lock.get(user))=="True": 28 | return await message.answer("please wait until previous task complete", show_alert=True) 29 | sts = STS(frwd_id) 30 | if not sts.verify(): 31 | await message.answer("your are clicking on my old button", show_alert=True) 32 | return await message.message.delete() 33 | i = sts.get(full=True) 34 | if i.TO in temp.IS_FRWD_CHAT: 35 | return await message.answer("In Target chat a task is progressing. please wait until task complete", show_alert=True) 36 | m = await msg_edit(message.message, "verifying your data's, please wait.") 37 | _bot, caption, forward_tag, data, protect, button = await sts.get_data(user) 38 | if not _bot: 39 | return await msg_edit(m, "You didn't added any bot. Please add a bot using /settings !", wait=True) 40 | try: 41 | client = await start_clone_bot(CLIENT.client(_bot)) 42 | except Exception as e: 43 | return await m.edit(e) 44 | await msg_edit(m, "processing..") 45 | try: 46 | await client.get_messages(sts.get("FROM"), sts.get("limit")) 47 | except: 48 | await msg_edit(m, f"**Source chat may be a private channel / group. Use userbot (user must be member over there) or if Make Your [Bot](t.me/{_bot['username']}) an admin over there**", retry_btn(frwd_id), True) 49 | return await stop(client, user) 50 | try: 51 | k = await client.send_message(i.TO, "Testing") 52 | await k.delete() 53 | except: 54 | await msg_edit(m, f"**Please Make Your [UserBot / Bot](t.me/{_bot['username']}) Admin In Target Channel With Full Permissions**", retry_btn(frwd_id), True) 55 | return await stop(client, user) 56 | temp.forwardings += 1 57 | await db.add_frwd(user) 58 | await send(client, user, "ғᴏʀᴡᴀʀᴅɪɴɢ sᴛᴀʀᴛᴇᴅ Dev Gagan") 59 | sts.add(time=True) 60 | sleep = 1 if _bot['is_bot'] else 10 61 | await msg_edit(m, "Processing...") 62 | temp.IS_FRWD_CHAT.append(i.TO) 63 | temp.lock[user] = locked = True 64 | if locked: 65 | try: 66 | MSG = [] 67 | pling=0 68 | await edit(m, 'Progressing', 10, sts) 69 | print(f"Starting Forwarding Process... From :{sts.get('FROM')} To: {sts.get('TO')} Totel: {sts.get('limit')} stats : {sts.get('skip')})") 70 | async for message in client.iter_messages( 71 | client, 72 | chat_id=sts.get('FROM'), 73 | limit=int(sts.get('limit')), 74 | offset=int(sts.get('skip')) if sts.get('skip') else 0 75 | ): 76 | if await is_cancelled(client, user, m, sts): 77 | return 78 | if pling %20 == 0: 79 | await edit(m, 'Progressing', 10, sts) 80 | pling += 1 81 | sts.add('fetched') 82 | if message == "DUPLICATE": 83 | sts.add('duplicate') 84 | continue 85 | elif message == "FILTERED": 86 | sts.add('filtered') 87 | continue 88 | if message.empty or message.service: 89 | sts.add('deleted') 90 | continue 91 | if forward_tag: 92 | MSG.append(message.id) 93 | notcompleted = len(MSG) 94 | completed = sts.get('total') - sts.get('fetched') 95 | if ( notcompleted >= 100 96 | or completed <= 100): 97 | await forward(client, MSG, m, sts, protect) 98 | sts.add('total_files', notcompleted) 99 | await asyncio.sleep(10) 100 | MSG = [] 101 | else: 102 | new_caption = custom_caption(message, caption) 103 | details = {"msg_id": message.id, "media": media(message), "caption": new_caption, 'button': button, "protect": protect} 104 | await copy(client, details, m, sts) 105 | sts.add('total_files') 106 | await asyncio.sleep(sleep) 107 | except Exception as e: 108 | await msg_edit(m, f'ERROR:\n{e}', wait=True) 109 | temp.IS_FRWD_CHAT.remove(sts.TO) 110 | return await stop(client, user) 111 | temp.IS_FRWD_CHAT.remove(sts.TO) 112 | await send(client, user, "🎉 ғᴏʀᴡᴀᴅɪɴɢ ᴄᴏᴍᴘʟᴇᴛᴇᴅ 🥀 SUPPORT🥀") 113 | await edit(m, 'Completed', "completed", sts) 114 | await stop(client, user) 115 | 116 | async def copy(bot, msg, m, sts): 117 | try: 118 | if msg.get("media") and msg.get("caption"): 119 | await bot.send_cached_media( 120 | chat_id=sts.get('TO'), 121 | file_id=msg.get("media"), 122 | caption=msg.get("caption"), 123 | reply_markup=msg.get('button'), 124 | protect_content=msg.get("protect")) 125 | else: 126 | await bot.copy_message( 127 | chat_id=sts.get('TO'), 128 | from_chat_id=sts.get('FROM'), 129 | caption=msg.get("caption"), 130 | message_id=msg.get("msg_id"), 131 | reply_markup=msg.get('button'), 132 | protect_content=msg.get("protect")) 133 | except FloodWait as e: 134 | await edit(m, 'Progressing', e.value, sts) 135 | await asyncio.sleep(e.value) 136 | await edit(m, 'Progressing', 10, sts) 137 | await copy(bot, msg, m, sts) 138 | except Exception as e: 139 | print(e) 140 | sts.add('deleted') 141 | 142 | async def forward(bot, msg, m, sts, protect): 143 | try: 144 | await bot.forward_messages( 145 | chat_id=sts.get('TO'), 146 | from_chat_id=sts.get('FROM'), 147 | protect_content=protect, 148 | message_ids=msg) 149 | except FloodWait as e: 150 | await edit(m, 'Progressing', e.value, sts) 151 | await asyncio.sleep(e.value) 152 | await edit(m, 'Progressing', 10, sts) 153 | await forward(bot, msg, m, sts, protect) 154 | 155 | PROGRESS = """ 156 | 📈 Percetage: {0} % 157 | 158 | ♻️ Feched: {1} 159 | 160 | ♻️ Fowarded: {2} 161 | 162 | ♻️ Remaining: {3} 163 | 164 | ♻️ Stataus: {4} 165 | 166 | ⏳️ ETA: {5} 167 | """ 168 | 169 | async def msg_edit(msg, text, button=None, wait=None): 170 | try: 171 | return await msg.edit(text, reply_markup=button) 172 | except MessageNotModified: 173 | pass 174 | except FloodWait as e: 175 | if wait: 176 | await asyncio.sleep(e.value) 177 | return await msg_edit(msg, text, button, wait) 178 | 179 | async def edit(msg, title, status, sts): 180 | i = sts.get(full=True) 181 | status = 'Forwarding' if status == 10 else f"Sleeping {status} s" if str(status).isnumeric() else status 182 | percentage = "{:.0f}".format(float(i.fetched)*100/float(i.total)) 183 | 184 | now = time.time() 185 | diff = int(now - i.start) 186 | speed = sts.divide(i.fetched, diff) 187 | elapsed_time = round(diff) * 1000 188 | time_to_completion = round(sts.divide(i.total - i.fetched, int(speed))) * 1000 189 | estimated_total_time = elapsed_time + time_to_completion 190 | progress = "◉{0}{1}".format( 191 | ''.join(["◉" for i in range(math.floor(int(percentage) / 10))]), 192 | ''.join(["◎" for i in range(10 - math.floor(int(percentage) / 10))])) 193 | button = [[InlineKeyboardButton(title, f'fwrdstatus#{status}#{estimated_total_time}#{percentage}#{i.id}')]] 194 | estimated_total_time = TimeFormatter(milliseconds=estimated_total_time) 195 | estimated_total_time = estimated_total_time if estimated_total_time != '' else '0 s' 196 | 197 | text = TEXT.format(i.fetched, i.total_files, i.duplicate, i.deleted, i.skip, status, percentage, estimated_total_time, progress) 198 | if status in ["cancelled", "completed"]: 199 | button.append( 200 | [InlineKeyboardButton('Support', url='https://t.me/dev_gagan'), 201 | InlineKeyboardButton('Updates', url='https://t.me/dev_gagan')] 202 | ) 203 | else: 204 | button.append([InlineKeyboardButton('• ᴄᴀɴᴄᴇʟ', 'terminate_frwd')]) 205 | await msg_edit(msg, text, InlineKeyboardMarkup(button)) 206 | 207 | async def is_cancelled(client, user, msg, sts): 208 | if temp.CANCEL.get(user)==True: 209 | temp.IS_FRWD_CHAT.remove(sts.TO) 210 | await edit(msg, "Cancelled", "completed", sts) 211 | await send(client, user, "❌ Forwarding Process Cancelled") 212 | await stop(client, user) 213 | return True 214 | return False 215 | 216 | async def stop(client, user): 217 | try: 218 | await client.stop() 219 | except: 220 | pass 221 | await db.rmve_frwd(user) 222 | temp.forwardings -= 1 223 | temp.lock[user] = False 224 | 225 | async def send(bot, user, text): 226 | try: 227 | await bot.send_message(user, text=text) 228 | except: 229 | pass 230 | 231 | def custom_caption(msg, caption): 232 | if msg.media: 233 | if (msg.video or msg.document or msg.audio or msg.photo): 234 | media = getattr(msg, msg.media.value, None) 235 | if media: 236 | file_name = getattr(media, 'file_name', '') 237 | file_size = getattr(media, 'file_size', '') 238 | fcaption = getattr(msg, 'caption', '') 239 | if fcaption: 240 | fcaption = fcaption.html 241 | if caption: 242 | return caption.format(filename=file_name, size=get_size(file_size), caption=fcaption) 243 | return fcaption 244 | return None 245 | 246 | def get_size(size): 247 | units = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB"] 248 | size = float(size) 249 | i = 0 250 | while size >= 1024.0 and i < len(units): 251 | i += 1 252 | size /= 1024.0 253 | return "%.2f %s" % (size, units[i]) 254 | 255 | def media(msg): 256 | if msg.media: 257 | media = getattr(msg, msg.media.value, None) 258 | if media: 259 | return getattr(media, 'file_id', None) 260 | return None 261 | 262 | def TimeFormatter(milliseconds: int) -> str: 263 | seconds, milliseconds = divmod(int(milliseconds), 1000) 264 | minutes, seconds = divmod(seconds, 60) 265 | hours, minutes = divmod(minutes, 60) 266 | days, hours = divmod(hours, 24) 267 | tmp = ((str(days) + "d, ") if days else "") + \ 268 | ((str(hours) + "h, ") if hours else "") + \ 269 | ((str(minutes) + "m, ") if minutes else "") + \ 270 | ((str(seconds) + "s, ") if seconds else "") + \ 271 | ((str(milliseconds) + "ms, ") if milliseconds else "") 272 | return tmp[:-2] 273 | 274 | def retry_btn(id): 275 | return InlineKeyboardMarkup([[InlineKeyboardButton('♻️ RETRY ♻️', f"start_public_{id}")]]) 276 | 277 | @Client.on_callback_query(filters.regex(r'^terminate_frwd$')) 278 | async def terminate_frwding(bot, m): 279 | user_id = m.from_user.id 280 | temp.lock[user_id] = False 281 | temp.CANCEL[user_id] = True 282 | await m.answer("Forwarding cancelled !", show_alert=True) 283 | 284 | @Client.on_callback_query(filters.regex(r'^fwrdstatus')) 285 | async def status_msg(bot, msg): 286 | _, status, est_time, percentage, frwd_id = msg.data.split("#") 287 | sts = STS(frwd_id) 288 | if not sts.verify(): 289 | fetched, forwarded, remaining = 0 290 | else: 291 | fetched, forwarded = sts.get('fetched'), sts.get('total_files') 292 | remaining = fetched - forwarded 293 | est_time = TimeFormatter(milliseconds=est_time) 294 | est_time = est_time if (est_time != '' or status not in ['completed', 'cancelled']) else '0 s' 295 | return await msg.answer(PROGRESS.format(percentage, fetched, forwarded, remaining, status, est_time), show_alert=True) 296 | 297 | @Client.on_callback_query(filters.regex(r'^close_btn$')) 298 | async def close(bot, update): 299 | await update.answer() 300 | await update.message.delete() 301 | -------------------------------------------------------------------------------- /plugins/settings.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from database import db 3 | from translation import Translation 4 | from pyrogram import Client, filters 5 | from .test import get_configs, update_configs, CLIENT, parse_buttons 6 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup 7 | 8 | CLIENT = CLIENT() 9 | 10 | @Client.on_message(filters.command('settings')) 11 | async def settings(client, message): 12 | await message.delete() 13 | await message.reply_text( 14 | "change your settings as your wish", 15 | reply_markup=main_buttons() 16 | ) 17 | 18 | @Client.on_callback_query(filters.regex(r'^settings')) 19 | async def settings_query(bot, query): 20 | user_id = query.from_user.id 21 | i, type = query.data.split("#") 22 | buttons = [[InlineKeyboardButton('↩ Back', callback_data="settings#main")]] 23 | 24 | if type=="main": 25 | await query.message.edit_text( 26 | "change your settings as your wish", 27 | reply_markup=main_buttons()) 28 | 29 | elif type=="bots": 30 | buttons = [] 31 | _bot = await db.get_bot(user_id) 32 | if _bot is not None: 33 | buttons.append([InlineKeyboardButton(_bot['name'], 34 | callback_data=f"settings#editbot")]) 35 | else: 36 | buttons.append([InlineKeyboardButton('✚ Add bot ✚', 37 | callback_data="settings#addbot")]) 38 | buttons.append([InlineKeyboardButton('✚ Add User bot ✚', 39 | callback_data="settings#adduserbot")]) 40 | buttons.append([InlineKeyboardButton('↩ Back', 41 | callback_data="settings#main")]) 42 | await query.message.edit_text( 43 | "My Bots\n\nYou can manage your bots in here", 44 | reply_markup=InlineKeyboardMarkup(buttons)) 45 | 46 | elif type=="addbot": 47 | await query.message.delete() 48 | bot = await CLIENT.add_bot(bot, query) 49 | if bot != True: return 50 | await query.message.reply_text( 51 | "bot token successfully added to db", 52 | reply_markup=InlineKeyboardMarkup(buttons)) 53 | 54 | elif type=="adduserbot": 55 | await query.message.delete() 56 | user = await CLIENT.add_session(bot, query) 57 | if user != True: return 58 | await query.message.reply_text( 59 | "session successfully added to db", 60 | reply_markup=InlineKeyboardMarkup(buttons)) 61 | 62 | elif type=="channels": 63 | buttons = [] 64 | channels = await db.get_user_channels(user_id) 65 | for channel in channels: 66 | buttons.append([InlineKeyboardButton(f"{channel['title']}", 67 | callback_data=f"settings#editchannels_{channel['chat_id']}")]) 68 | buttons.append([InlineKeyboardButton('✚ Add Channel ✚', 69 | callback_data="settings#addchannel")]) 70 | buttons.append([InlineKeyboardButton('↩ Back', 71 | callback_data="settings#main")]) 72 | await query.message.edit_text( 73 | "My Channels\n\nyou can manage your target chats in here", 74 | reply_markup=InlineKeyboardMarkup(buttons)) 75 | 76 | elif type=="addchannel": 77 | await query.message.delete() 78 | try: 79 | text = await bot.send_message(user_id, "❪ SET TARGET CHAT ❫\n\nForward a message from Your target chat\n/cancel - cancel this process") 80 | chat_ids = await bot.listen(chat_id=user_id, timeout=300) 81 | if chat_ids.text=="/cancel": 82 | await chat_ids.delete() 83 | return await text.edit_text( 84 | "process canceled", 85 | reply_markup=InlineKeyboardMarkup(buttons)) 86 | elif not chat_ids.forward_date: 87 | await chat_ids.delete() 88 | return await text.edit_text("**This is not a forward message**") 89 | else: 90 | chat_id = chat_ids.forward_from_chat.id 91 | title = chat_ids.forward_from_chat.title 92 | username = chat_ids.forward_from_chat.username 93 | username = "@" + username if username else "private" 94 | chat = await db.add_channel(user_id, chat_id, title, username) 95 | await chat_ids.delete() 96 | await text.edit_text( 97 | "Successfully updated" if chat else "This channel already added", 98 | reply_markup=InlineKeyboardMarkup(buttons)) 99 | except asyncio.exceptions.TimeoutError: 100 | await text.edit_text('Process has been automatically cancelled', reply_markup=InlineKeyboardMarkup(buttons)) 101 | 102 | elif type=="editbot": 103 | bot = await db.get_bot(user_id) 104 | TEXT = Translation.BOT_DETAILS if bot['is_bot'] else Translation.USER_DETAILS 105 | buttons = [[InlineKeyboardButton('❌ Remove ❌', callback_data=f"settings#removebot") 106 | ], 107 | [InlineKeyboardButton('↩ Back', callback_data="settings#bots")]] 108 | await query.message.edit_text( 109 | TEXT.format(bot['name'], bot['id'], bot['username']), 110 | reply_markup=InlineKeyboardMarkup(buttons)) 111 | 112 | elif type=="removebot": 113 | await db.remove_bot(user_id) 114 | await query.message.edit_text( 115 | "successfully updated", 116 | reply_markup=InlineKeyboardMarkup(buttons)) 117 | 118 | elif type.startswith("editchannels"): 119 | chat_id = type.split('_')[1] 120 | chat = await db.get_channel_details(user_id, chat_id) 121 | buttons = [[InlineKeyboardButton('❌ Remove ❌', callback_data=f"settings#removechannel_{chat_id}") 122 | ], 123 | [InlineKeyboardButton('↩ Back', callback_data="settings#channels")]] 124 | await query.message.edit_text( 125 | f"📄 CHANNEL DETAILS\n\n- TITLE: {chat['title']}\n- CHANNEL ID: {chat['chat_id']}\n- USERNAME: {chat['username']}", 126 | reply_markup=InlineKeyboardMarkup(buttons)) 127 | 128 | elif type.startswith("removechannel"): 129 | chat_id = type.split('_')[1] 130 | await db.remove_channel(user_id, chat_id) 131 | await query.message.edit_text( 132 | "successfully updated", 133 | reply_markup=InlineKeyboardMarkup(buttons)) 134 | 135 | elif type=="caption": 136 | buttons = [] 137 | data = await get_configs(user_id) 138 | caption = data['caption'] 139 | if caption is None: 140 | buttons.append([InlineKeyboardButton('✚ Add Caption ✚', 141 | callback_data="settings#addcaption")]) 142 | else: 143 | buttons.append([InlineKeyboardButton('See Caption', 144 | callback_data="settings#seecaption")]) 145 | buttons[-1].append(InlineKeyboardButton('🗑️ Delete Caption', 146 | callback_data="settings#deletecaption")) 147 | buttons.append([InlineKeyboardButton('↩ Back', 148 | callback_data="settings#main")]) 149 | await query.message.edit_text( 150 | "CUSTOM CAPTION\n\nYou can set a custom caption to videos and documents. Normaly use its default caption\n\nAVAILABLE FILLINGS:\n- {filename} : Filename\n- {size} : File size\n- {caption} : default caption", 151 | reply_markup=InlineKeyboardMarkup(buttons)) 152 | 153 | elif type=="seecaption": 154 | data = await get_configs(user_id) 155 | buttons = [[InlineKeyboardButton('🖋️ Edit Caption', 156 | callback_data="settings#addcaption") 157 | ],[ 158 | InlineKeyboardButton('↩ Back', 159 | callback_data="settings#caption")]] 160 | await query.message.edit_text( 161 | f"YOUR CUSTOM CAPTION\n\n{data['caption']}", 162 | reply_markup=InlineKeyboardMarkup(buttons)) 163 | 164 | elif type=="deletecaption": 165 | await update_configs(user_id, 'caption', None) 166 | await query.message.edit_text( 167 | "successfully updated", 168 | reply_markup=InlineKeyboardMarkup(buttons)) 169 | 170 | elif type=="addcaption": 171 | await query.message.delete() 172 | try: 173 | text = await bot.send_message(query.message.chat.id, "Send your custom caption\n/cancel - cancel this process") 174 | caption = await bot.listen(chat_id=user_id, timeout=300) 175 | if caption.text=="/cancel": 176 | await caption.delete() 177 | return await text.edit_text( 178 | "process canceled !", 179 | reply_markup=InlineKeyboardMarkup(buttons)) 180 | try: 181 | caption.text.format(filename='', size='', caption='') 182 | except KeyError as e: 183 | await caption.delete() 184 | return await text.edit_text( 185 | f"wrong filling {e} used in your caption. change it", 186 | reply_markup=InlineKeyboardMarkup(buttons)) 187 | await update_configs(user_id, 'caption', caption.text) 188 | await caption.delete() 189 | await text.edit_text( 190 | "successfully updated", 191 | reply_markup=InlineKeyboardMarkup(buttons)) 192 | except asyncio.exceptions.TimeoutError: 193 | await text.edit_text('Process has been automatically cancelled', reply_markup=InlineKeyboardMarkup(buttons)) 194 | 195 | elif type=="button": 196 | buttons = [] 197 | button = (await get_configs(user_id))['button'] 198 | if button is None: 199 | buttons.append([InlineKeyboardButton('✚ Add Button ✚', 200 | callback_data="settings#addbutton")]) 201 | else: 202 | buttons.append([InlineKeyboardButton('👀 See Button', 203 | callback_data="settings#seebutton")]) 204 | buttons[-1].append(InlineKeyboardButton('🗑️ Remove Button ', 205 | callback_data="settings#deletebutton")) 206 | buttons.append([InlineKeyboardButton('↩ Back', 207 | callback_data="settings#main")]) 208 | await query.message.edit_text( 209 | "CUSTOM BUTTON\n\nYou can set a inline button to messages.\n\nFORMAT:\n`[Forward bot][buttonurl:https://t.me/devgaganbot]`\n", 210 | reply_markup=InlineKeyboardMarkup(buttons)) 211 | 212 | elif type=="addbutton": 213 | await query.message.delete() 214 | try: 215 | txt = await bot.send_message(user_id, text="**Send your custom button.\n\nFORMAT:**\n`[forward bot][buttonurl:https://t.me/devgaganbot]`\n") 216 | ask = await bot.listen(chat_id=user_id, timeout=300) 217 | button = parse_buttons(ask.text.html) 218 | if not button: 219 | await ask.delete() 220 | return await txt.edit_text("**INVALID BUTTON**") 221 | await update_configs(user_id, 'button', ask.text.html) 222 | await ask.delete() 223 | await txt.edit_text("**Successfully button added**", 224 | reply_markup=InlineKeyboardMarkup(buttons)) 225 | except asyncio.exceptions.TimeoutError: 226 | await txt.edit_text('Process has been automatically cancelled', reply_markup=InlineKeyboardMarkup(buttons)) 227 | 228 | elif type=="seebutton": 229 | button = (await get_configs(user_id))['button'] 230 | button = parse_buttons(button, markup=False) 231 | button.append([InlineKeyboardButton("↩ Back", "settings#button")]) 232 | await query.message.edit_text( 233 | "**YOUR CUSTOM BUTTON**", 234 | reply_markup=InlineKeyboardMarkup(button)) 235 | 236 | elif type=="deletebutton": 237 | await update_configs(user_id, 'button', None) 238 | await query.message.edit_text( 239 | "**Successfully button deleted**", 240 | reply_markup=InlineKeyboardMarkup(buttons)) 241 | 242 | elif type=="database": 243 | buttons = [] 244 | db_uri = (await get_configs(user_id))['db_uri'] 245 | if db_uri is None: 246 | buttons.append([InlineKeyboardButton('✚ Add Url ✚', 247 | callback_data="settings#addurl")]) 248 | else: 249 | buttons.append([InlineKeyboardButton('👀 See Url', 250 | callback_data="settings#seeurl")]) 251 | buttons[-1].append(InlineKeyboardButton('🗑️ Remove Url ', 252 | callback_data="settings#deleteurl")) 253 | buttons.append([InlineKeyboardButton('↩ Back', 254 | callback_data="settings#main")]) 255 | await query.message.edit_text( 256 | "DATABASE\n\nDatabase is required for store your duplicate messages permenant. other wise stored duplicate media may be disappeared when after bot restart.", 257 | reply_markup=InlineKeyboardMarkup(buttons)) 258 | 259 | elif type=="addurl": 260 | await query.message.delete() 261 | uri = await bot.ask(user_id, "please send your mongodb url.\n\nget your Mongodb url from [here](https://mongodb.com)", disable_web_page_preview=True) 262 | if uri.text=="/cancel": 263 | return await uri.reply_text( 264 | "process canceled !", 265 | reply_markup=InlineKeyboardMarkup(buttons)) 266 | if not uri.text.startswith("mongodb+srv://") and not uri.text.endswith("majority"): 267 | return await uri.reply("Invalid Mongodb Url", 268 | reply_markup=InlineKeyboardMarkup(buttons)) 269 | await update_configs(user_id, 'db_uri', uri.text) 270 | await uri.reply("**Successfully database url added**", 271 | reply_markup=InlineKeyboardMarkup(buttons)) 272 | 273 | elif type=="seeurl": 274 | db_uri = (await get_configs(user_id))['db_uri'] 275 | await query.answer(f"DATABASE URL: {db_uri}", show_alert=True) 276 | 277 | elif type=="deleteurl": 278 | await update_configs(user_id, 'db_uri', None) 279 | await query.message.edit_text( 280 | "**Successfully your database url deleted**", 281 | reply_markup=InlineKeyboardMarkup(buttons)) 282 | 283 | elif type=="filters": 284 | await query.message.edit_text( 285 | "💠 CUSTOM FILTERS 💠\n\n**configure the type of messages which you want forward**", 286 | reply_markup=await filters_buttons(user_id)) 287 | 288 | elif type=="nextfilters": 289 | await query.edit_message_reply_markup( 290 | reply_markup=await next_filters_buttons(user_id)) 291 | 292 | elif type.startswith("updatefilter"): 293 | i, key, value = type.split('-') 294 | if value=="True": 295 | await update_configs(user_id, key, False) 296 | else: 297 | await update_configs(user_id, key, True) 298 | if key in ['poll', 'protect']: 299 | return await query.edit_message_reply_markup( 300 | reply_markup=await next_filters_buttons(user_id)) 301 | await query.edit_message_reply_markup( 302 | reply_markup=await filters_buttons(user_id)) 303 | 304 | elif type.startswith("file_size"): 305 | settings = await get_configs(user_id) 306 | size = settings.get('file_size', 0) 307 | i, limit = size_limit(settings['size_limit']) 308 | await query.message.edit_text( 309 | f'SIZE LIMIT\n\nyou can set file size limit to forward\n\nStatus: files with {limit} `{size} MB` will forward', 310 | reply_markup=size_button(size)) 311 | 312 | elif type.startswith("update_size"): 313 | size = int(query.data.split('-')[1]) 314 | if 0 < size > 2000: 315 | return await query.answer("size limit exceeded", show_alert=True) 316 | await update_configs(user_id, 'file_size', size) 317 | i, limit = size_limit((await get_configs(user_id))['size_limit']) 318 | await query.message.edit_text( 319 | f'SIZE LIMIT\n\nyou can set file size limit to forward\n\nStatus: files with {limit} `{size} MB` will forward', 320 | reply_markup=size_button(size)) 321 | 322 | elif type.startswith('update_limit'): 323 | i, limit, size = type.split('-') 324 | limit, sts = size_limit(limit) 325 | await update_configs(user_id, 'size_limit', limit) 326 | await query.message.edit_text( 327 | f'SIZE LIMIT\n\nyou can set file size limit to forward\n\nStatus: files with {sts} `{size} MB` will forward', 328 | reply_markup=size_button(int(size))) 329 | 330 | elif type == "add_extension": 331 | await query.message.delete() 332 | ext = await bot.ask(user_id, text="**please send your extensions (seperete by space)**") 333 | if ext.text == '/cancel': 334 | return await ext.reply_text( 335 | "process canceled", 336 | reply_markup=InlineKeyboardMarkup(buttons)) 337 | extensions = ext.text.split(" ") 338 | extension = (await get_configs(user_id))['extension'] 339 | if extension: 340 | for extn in extensions: 341 | extension.append(extn) 342 | else: 343 | extension = extensions 344 | await update_configs(user_id, 'extension', extension) 345 | await ext.reply_text( 346 | f"**successfully updated**", 347 | reply_markup=InlineKeyboardMarkup(buttons)) 348 | 349 | elif type == "get_extension": 350 | extensions = (await get_configs(user_id))['extension'] 351 | btn = extract_btn(extensions) 352 | btn.append([InlineKeyboardButton('✚ ADD ✚', 'settings#add_extension')]) 353 | btn.append([InlineKeyboardButton('Remove all', 'settings#rmve_all_extension')]) 354 | btn.append([InlineKeyboardButton('↩ Back', 'settings#main')]) 355 | await query.message.edit_text( 356 | text='EXTENSIONS\n\n**Files with these extiontions will not forward**', 357 | reply_markup=InlineKeyboardMarkup(btn)) 358 | 359 | elif type == "rmve_all_extension": 360 | await update_configs(user_id, 'extension', None) 361 | await query.message.edit_text(text="**successfully deleted**", 362 | reply_markup=InlineKeyboardMarkup(buttons)) 363 | elif type == "add_keyword": 364 | await query.message.delete() 365 | ask = await bot.ask(user_id, text="**please send the keywords (seperete by space)**") 366 | if ask.text == '/cancel': 367 | return await ask.reply_text( 368 | "process canceled", 369 | reply_markup=InlineKeyboardMarkup(buttons)) 370 | keywords = ask.text.split(" ") 371 | keyword = (await get_configs(user_id))['keywords'] 372 | if keyword: 373 | for word in keywords: 374 | keyword.append(word) 375 | else: 376 | keyword = keywords 377 | await update_configs(user_id, 'keywords', keyword) 378 | await ask.reply_text( 379 | f"**successfully updated**", 380 | reply_markup=InlineKeyboardMarkup(buttons)) 381 | 382 | elif type == "get_keyword": 383 | keywords = (await get_configs(user_id))['keywords'] 384 | btn = extract_btn(keywords) 385 | btn.append([InlineKeyboardButton('✚ ADD ✚', 'settings#add_keyword')]) 386 | btn.append([InlineKeyboardButton('Remove all', 'settings#rmve_all_keyword')]) 387 | btn.append([InlineKeyboardButton('↩ Back', 'settings#main')]) 388 | await query.message.edit_text( 389 | text='KEYWORDS\n\n**File with these keywords in file name will forwad**', 390 | reply_markup=InlineKeyboardMarkup(btn)) 391 | 392 | elif type == "rmve_all_keyword": 393 | await update_configs(user_id, 'keywords', None) 394 | await query.message.edit_text(text="**successfully deleted**", 395 | reply_markup=InlineKeyboardMarkup(buttons)) 396 | elif type.startswith("alert"): 397 | alert = type.split('_')[1] 398 | await query.answer(alert, show_alert=True) 399 | 400 | def main_buttons(): 401 | buttons = [[ 402 | InlineKeyboardButton('🤖 Bᴏᴛs', 403 | callback_data=f'settings#bots'), 404 | InlineKeyboardButton('🏷 Cʜᴀɴɴᴇʟs', 405 | callback_data=f'settings#channels') 406 | ],[ 407 | InlineKeyboardButton('🖋️ Cᴀᴘᴛɪᴏɴ', 408 | callback_data=f'settings#caption'), 409 | InlineKeyboardButton('🗃 MᴏɴɢᴏDB', 410 | callback_data=f'settings#database') 411 | ],[ 412 | InlineKeyboardButton('🕵‍♀ Fɪʟᴛᴇʀs 🕵‍♀', 413 | callback_data=f'settings#filters'), 414 | InlineKeyboardButton('⏹ Bᴜᴛᴛᴏɴ', 415 | callback_data=f'settings#button') 416 | ],[ 417 | InlineKeyboardButton('Exᴛʀᴀ Sᴇᴛᴛɪɴɢs 🧪', 418 | callback_data='settings#nextfilters') 419 | ],[ 420 | InlineKeyboardButton('⫷ Bᴀᴄᴋ', callback_data='back') 421 | ]] 422 | return InlineKeyboardMarkup(buttons) 423 | 424 | def size_limit(limit): 425 | if str(limit) == "None": 426 | return None, "" 427 | elif str(limit) == "True": 428 | return True, "more than" 429 | else: 430 | return False, "less than" 431 | 432 | def extract_btn(datas): 433 | i = 0 434 | btn = [] 435 | if datas: 436 | for data in datas: 437 | if i >= 5: 438 | i = 0 439 | if i == 0: 440 | btn.append([InlineKeyboardButton(data, f'settings#alert_{data}')]) 441 | i += 1 442 | continue 443 | elif i > 0: 444 | btn[-1].append(InlineKeyboardButton(data, f'settings#alert_{data}')) 445 | i += 1 446 | return btn 447 | 448 | def size_button(size): 449 | buttons = [[ 450 | InlineKeyboardButton('+', 451 | callback_data=f'settings#update_limit-True-{size}'), 452 | InlineKeyboardButton('=', 453 | callback_data=f'settings#update_limit-None-{size}'), 454 | InlineKeyboardButton('-', 455 | callback_data=f'settings#update_limit-False-{size}') 456 | ],[ 457 | InlineKeyboardButton('+1', 458 | callback_data=f'settings#update_size-{size + 1}'), 459 | InlineKeyboardButton('-1', 460 | callback_data=f'settings#update_size_-{size - 1}') 461 | ],[ 462 | InlineKeyboardButton('+5', 463 | callback_data=f'settings#update_size-{size + 5}'), 464 | InlineKeyboardButton('-5', 465 | callback_data=f'settings#update_size_-{size - 5}') 466 | ],[ 467 | InlineKeyboardButton('+10', 468 | callback_data=f'settings#update_size-{size + 10}'), 469 | InlineKeyboardButton('-10', 470 | callback_data=f'settings#update_size_-{size - 10}') 471 | ],[ 472 | InlineKeyboardButton('+50', 473 | callback_data=f'settings#update_size-{size + 50}'), 474 | InlineKeyboardButton('-50', 475 | callback_data=f'settings#update_size_-{size - 50}') 476 | ],[ 477 | InlineKeyboardButton('+100', 478 | callback_data=f'settings#update_size-{size + 100}'), 479 | InlineKeyboardButton('-100', 480 | callback_data=f'settings#update_size_-{size - 100}') 481 | ],[ 482 | InlineKeyboardButton('↩ Back', 483 | callback_data="settings#main") 484 | ]] 485 | return InlineKeyboardMarkup(buttons) 486 | 487 | async def filters_buttons(user_id): 488 | filter = await get_configs(user_id) 489 | filters = filter['filters'] 490 | buttons = [[ 491 | InlineKeyboardButton('🏷️ Forward tag', 492 | callback_data=f'settings_#updatefilter-forward_tag-{filter["forward_tag"]}'), 493 | InlineKeyboardButton('✅' if filter['forward_tag'] else '❌', 494 | callback_data=f'settings#updatefilter-forward_tag-{filter["forward_tag"]}') 495 | ],[ 496 | InlineKeyboardButton('🖍️ Texts', 497 | callback_data=f'settings_#updatefilter-text-{filters["text"]}'), 498 | InlineKeyboardButton('✅' if filters['text'] else '❌', 499 | callback_data=f'settings#updatefilter-text-{filters["text"]}') 500 | ],[ 501 | InlineKeyboardButton('📁 Documents', 502 | callback_data=f'settings_#updatefilter-document-{filters["document"]}'), 503 | InlineKeyboardButton('✅' if filters['document'] else '❌', 504 | callback_data=f'settings#updatefilter-document-{filters["document"]}') 505 | ],[ 506 | InlineKeyboardButton('🎞️ Videos', 507 | callback_data=f'settings_#updatefilter-video-{filters["video"]}'), 508 | InlineKeyboardButton('✅' if filters['video'] else '❌', 509 | callback_data=f'settings#updatefilter-video-{filters["video"]}') 510 | ],[ 511 | InlineKeyboardButton('📷 Photos', 512 | callback_data=f'settings_#updatefilter-photo-{filters["photo"]}'), 513 | InlineKeyboardButton('✅' if filters['photo'] else '❌', 514 | callback_data=f'settings#updatefilter-photo-{filters["photo"]}') 515 | ],[ 516 | InlineKeyboardButton('🎧 Audios', 517 | callback_data=f'settings_#updatefilter-audio-{filters["audio"]}'), 518 | InlineKeyboardButton('✅' if filters['audio'] else '❌', 519 | callback_data=f'settings#updatefilter-audio-{filters["audio"]}') 520 | ],[ 521 | InlineKeyboardButton('🎤 Voices', 522 | callback_data=f'settings_#updatefilter-voice-{filters["voice"]}'), 523 | InlineKeyboardButton('✅' if filters['voice'] else '❌', 524 | callback_data=f'settings#updatefilter-voice-{filters["voice"]}') 525 | ],[ 526 | InlineKeyboardButton('🎭 Animations', 527 | callback_data=f'settings_#updatefilter-animation-{filters["animation"]}'), 528 | InlineKeyboardButton('✅' if filters['animation'] else '❌', 529 | callback_data=f'settings#updatefilter-animation-{filters["animation"]}') 530 | ],[ 531 | InlineKeyboardButton('🃏 Stickers', 532 | callback_data=f'settings_#updatefilter-sticker-{filters["sticker"]}'), 533 | InlineKeyboardButton('✅' if filters['sticker'] else '❌', 534 | callback_data=f'settings#updatefilter-sticker-{filters["sticker"]}') 535 | ],[ 536 | InlineKeyboardButton('▶️ Skip duplicate', 537 | callback_data=f'settings_#updatefilter-duplicate-{filter["duplicate"]}'), 538 | InlineKeyboardButton('✅' if filter['duplicate'] else '❌', 539 | callback_data=f'settings#updatefilter-duplicate-{filter["duplicate"]}') 540 | ],[ 541 | InlineKeyboardButton('⫷ back', 542 | callback_data="settings#main") 543 | ]] 544 | return InlineKeyboardMarkup(buttons) 545 | 546 | async def next_filters_buttons(user_id): 547 | filter = await get_configs(user_id) 548 | filters = filter['filters'] 549 | buttons = [[ 550 | InlineKeyboardButton('📊 Poll', 551 | callback_data=f'settings_#updatefilter-poll-{filters["poll"]}'), 552 | InlineKeyboardButton('✅' if filters['poll'] else '❌', 553 | callback_data=f'settings#updatefilter-poll-{filters["poll"]}') 554 | ],[ 555 | InlineKeyboardButton('🔒 Secure message', 556 | callback_data=f'settings_#updatefilter-protect-{filter["protect"]}'), 557 | InlineKeyboardButton('✅' if filter['protect'] else '❌', 558 | callback_data=f'settings#updatefilter-protect-{filter["protect"]}') 559 | ],[ 560 | InlineKeyboardButton('🛑 size limit', 561 | callback_data='settings#file_size') 562 | ],[ 563 | InlineKeyboardButton('💾 Extension', 564 | callback_data='settings#get_extension') 565 | ],[ 566 | InlineKeyboardButton('♦️ keywords ♦️', 567 | callback_data='settings#get_keyword') 568 | ],[ 569 | InlineKeyboardButton('⫷ back', 570 | callback_data="settings#main") 571 | ]] 572 | return InlineKeyboardMarkup(buttons) 573 | 574 | --------------------------------------------------------------------------------