├── Procfile ├── requirements.txt ├── handlers ├── database │ ├── access_db.py │ ├── add_user.py │ └── database.py ├── forwarder_handler.py ├── send_mesage_handler.py └── forcesub_handler.py ├── configs.py ├── README.md ├── app.json └── main.py /Procfile: -------------------------------------------------------------------------------- 1 | worker: python3 main.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyrogram==1.4.16 2 | TgCrypto 3 | motor 4 | dnspython 5 | aiofiles 6 | -------------------------------------------------------------------------------- /handlers/database/access_db.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from configs import Config 4 | from handlers.database.database import Database 5 | 6 | db = Database(Config.MONGODB_URI, "Discovery-Project-0") 7 | -------------------------------------------------------------------------------- /handlers/database/add_user.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from handlers.database.access_db import db 4 | from pyrogram.types import Message 5 | 6 | 7 | async def AddUserToDatabase(cmd: Message): 8 | if not await db.is_user_exist(cmd.from_user.id): 9 | await db.add_user(cmd.from_user.id) 10 | -------------------------------------------------------------------------------- /handlers/forwarder_handler.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import asyncio 4 | from pyrogram.types import Message 5 | from pyrogram.errors import FloodWait 6 | from configs import Config 7 | 8 | 9 | async def forwardMessage(file: Message): 10 | try: 11 | data = await file.forward(chat_id=Config.DB_CHANNEL_ID) 12 | return data 13 | except FloodWait as e: 14 | print(f"Sleep of {e.x}s caused by FloodWait") 15 | await asyncio.sleep(e.x) 16 | await forwardMessage(file) 17 | -------------------------------------------------------------------------------- /configs.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import os 4 | 5 | 6 | class Config(object): 7 | API_ID = int(os.environ.get("API_ID", 12345)) 8 | API_HASH = os.environ.get("API_HASH", None) 9 | STRING_SESSION = os.environ.get("STRING_SESSION", "") 10 | DB_CHANNEL_ID = int(os.environ.get("DB_CHANNEL_ID", -100)) 11 | FORCE_SUB_CHANNEL = os.environ.get("FORCE_SUB_CHANNEL", None) 12 | MONGODB_URI = os.environ.get("MONGODB_URI", "") 13 | BOT_TOKEN = os.environ.get("BOT_TOKEN", "") 14 | DEFAULT_BLOCKED_EXTENSIONS = "srt txt jpg jpeg png torrent html aio pdf" 15 | BLOCKED_EXTENSIONS = list(set(x for x in os.environ.get("BLOCKED_EXTENSIONS", DEFAULT_BLOCKED_EXTENSIONS).split())) 16 | -------------------------------------------------------------------------------- /handlers/send_mesage_handler.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import asyncio 4 | from pyrogram import Client 5 | from pyrogram.errors import FloodWait 6 | 7 | 8 | async def sendMessage(bot: Client, text: str, message_id: int, chat_id: int): 9 | """ 10 | Custom Send Message Function with FloodWait Error Handler & Website Preview Disabled. You can Send Message with a Reply to Group Message. 11 | 12 | :param bot: Pass Bot Client. 13 | :param text: Pass Text for Reply. 14 | :param message_id: Pass Message ID for Reply. 15 | :param chat_id: Pass Group Chat ID. 16 | """ 17 | 18 | try: 19 | await bot.send_message( 20 | chat_id=chat_id, 21 | text=text, 22 | reply_to_message_id=message_id, 23 | disable_web_page_preview=True 24 | ) 25 | except FloodWait as e: 26 | print(f"Sleep of {e.x}s caused by FloodWait") 27 | await asyncio.sleep(e.x) 28 | await sendMessage(bot, text, message_id, chat_id) 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Save-Group 2 | I specially made this for my [Discovery Cloud](https://t.me/joinchat/O9WIjhCGHLo0YmQ0) group. Check in Group to understand how this works. 3 | 4 | This will just save your group files to a Channel & will provide a link to retrieve your file. 5 | 6 | ## Deploy to Heroku 7 | Press Below Button to Deploy as Lazy Deploy: 8 | 9 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 10 | 11 | Using Heroku CLI 12 | ```shell 13 | git clone https://github.com/AbirHasan2005/Save-Group 14 | cd Save-Group 15 | ``` 16 | Now setup Your Configs in `config.py` File using `nano` or something else. 17 | ```shell 18 | heroku login 19 | heroku create app_name 20 | heroku git:remote -a app_name 21 | git commit -am "Pushing to Heroku" 22 | git push heroku main:master 23 | ``` 24 | 25 | ### Support Group 26 | 27 | 28 | ### Follow on 29 |

30 | 31 |

32 |

33 | 34 |

35 |

36 | 37 |

38 |

39 | 40 |

41 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Telegram Group Saver", 3 | "description": "A Telegram to make your leech/mirror group copyright free by @AbirHasan2005", 4 | "keywords": [ 5 | "telegram", 6 | "group", 7 | "files", 8 | "store", 9 | "userbot", 10 | "bot", 11 | "save" 12 | ], 13 | "repository": "https://github.com/AbirHasan2005/Save-Group", 14 | "success_url": "https://t.me/joinchat/O9WIjhCGHLo0YmQ0", 15 | "website": "https://github.com/AbirHasan2005/Save-Group", 16 | "env": { 17 | "API_ID": { 18 | "description": "Get this value from https://my.telegram.org or @TeleORG_Bot", 19 | "value": "" 20 | }, 21 | "API_HASH": { 22 | "description": "Get this value from https://my.telegram.org or @TeleORG_Bot", 23 | "value": "" 24 | }, 25 | "STRING_SESSION": { 26 | "description": "Pyrogram user session string, get it from @StringSessionGen_Bot", 27 | "value": "" 28 | }, 29 | "FORCE_SUB_CHANNEL": { 30 | "description": "Your channel username that you want to forcesub", 31 | "value": "", 32 | "required": false 33 | }, 34 | "MONGODB_URI": { 35 | "description": "Mongo database url get it from https://www.mongodb.com", 36 | "value": "" 37 | }, 38 | "BOT_TOKEN": { 39 | "description": "telegram bot token, get it from @botfather ", 40 | "value": "" 41 | }, 42 | "BLOCKED_EXTENSIONS": { 43 | "description": "blocked files extension that will not get deleted.", 44 | "value": "srt txt jpg jpeg png torrent html aio pdf" 45 | }, 46 | "DB_CHANNEL_ID": { 47 | "description": "Your private channel id for database. In that channel bot will save files.", 48 | "value": "" 49 | } 50 | }, 51 | "buildpacks": [ 52 | { 53 | "url": "heroku/python" 54 | } 55 | ], 56 | "formation": { 57 | "worker": { 58 | "quantity": 1, 59 | "size": "free" 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /handlers/database/database.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import datetime 4 | import motor.motor_asyncio 5 | 6 | 7 | class Database: 8 | 9 | def __init__(self, uri, database_name): 10 | self._client = motor.motor_asyncio.AsyncIOMotorClient(uri) 11 | self.db = self._client[database_name] 12 | self.col = self.db.users 13 | 14 | def new_user(self, id): 15 | return dict( 16 | id=id, 17 | join_date=datetime.date.today().isoformat(), 18 | joined_channel=None, 19 | group_id=None, 20 | group_message_id=None 21 | ) 22 | 23 | async def add_user(self, id): 24 | user = self.new_user(id) 25 | await self.col.insert_one(user) 26 | 27 | async def is_user_exist(self, id): 28 | user = await self.col.find_one({'id': int(id)}) 29 | return True if user else False 30 | 31 | async def total_users_count(self): 32 | count = await self.col.count_documents({}) 33 | return count 34 | 35 | async def get_all_users(self): 36 | all_users = self.col.find({}) 37 | return all_users 38 | 39 | async def delete_user(self, user_id): 40 | await self.col.delete_many({'id': int(user_id)}) 41 | 42 | async def request_(self, id): 43 | user = await self.col.find_one({'id': int(id)}) 44 | return user 45 | 46 | async def set_joined_channel(self, id, joined_channel): 47 | await self.col.update_one({'id': int(id)}, {'$set': {'joined_channel': joined_channel}}) 48 | 49 | async def get_joined_channel(self, id): 50 | user = await self.request_(int(id)) 51 | return user.get('joined_channel', None) if (user is not None) else None 52 | 53 | async def set_group_id(self, id, group_id): 54 | await self.col.update_one({'id': int(id)}, {'$set': {'group_id': group_id}}) 55 | 56 | async def get_group_id(self, id): 57 | user = await self.request_(int(id)) 58 | return user.get('group_id', None) if (user is not None) else None 59 | 60 | async def set_group_message_id(self, id, group_message_id): 61 | await self.col.update_one({'id': int(id)}, {'$set': {'group_message_id': group_message_id}}) 62 | 63 | async def get_group_message_id(self, id): 64 | user = await self.request_(int(id)) 65 | return user.get('group_message_id', None) if (user is not None) else None 66 | -------------------------------------------------------------------------------- /handlers/forcesub_handler.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import asyncio 4 | from configs import Config 5 | from pyrogram import Client 6 | from handlers.database.access_db import db 7 | from pyrogram.errors import FloodWait, UserNotParticipant 8 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 9 | 10 | 11 | async def ForceSub(bot: Client, cmd: Message): 12 | try: 13 | user = await bot.get_chat_member(chat_id=(int(Config.FORCE_SUB_CHANNEL) if Config.FORCE_SUB_CHANNEL.startswith("-100") else Config.FORCE_SUB_CHANNEL), user_id=cmd.from_user.id) 14 | if user.status == "kicked": 15 | await bot.send_message( 16 | chat_id=cmd.chat.id, 17 | text="Sorry Unkil, You are Banned! You will be Kicked from This Group within 15 Seconds.\n" 18 | "Contact: [Support Group](https://t.me/linux_repo).", 19 | disable_web_page_preview=True, 20 | reply_to_message_id=cmd.message_id 21 | ) 22 | await asyncio.sleep(15) 23 | return 404 24 | except UserNotParticipant: 25 | try: 26 | invite_link = await bot.create_chat_invite_link(chat_id=(int(Config.FORCE_SUB_CHANNEL) if Config.FORCE_SUB_CHANNEL.startswith("-100") else Config.FORCE_SUB_CHANNEL)) 27 | except FloodWait as e: 28 | print(f"Sleep of {e.x}s caused by FloodWait") 29 | await asyncio.sleep(e.x) 30 | return 200 31 | except Exception as err: 32 | print(f"Unable to do Force Subscribe to {Config.FORCE_SUB_CHANNEL}\n\nError: {err}") 33 | return 200 34 | send_ = await bot.send_message( 35 | chat_id=cmd.chat.id, 36 | text=f"Hello {cmd.from_user.mention},\n\n" 37 | f"**Please [Join Channel]({invite_link.invite_link}) to Send Messages to This Group!**\n\n" 38 | "Till you join channel you will be muted in this group!", 39 | reply_to_message_id=cmd.message_id, 40 | disable_web_page_preview=True, 41 | reply_markup=InlineKeyboardMarkup( 42 | [ 43 | [InlineKeyboardButton("🤖 Join Channel 🤖", url=invite_link.invite_link)] 44 | ] 45 | ) 46 | ) 47 | await db.set_group_message_id(cmd.from_user.id, group_message_id=send_.message_id) 48 | return 400 49 | except FloodWait as e: 50 | print(f"Sleep of {e.x}s caused by FloodWait") 51 | await asyncio.sleep(e.x) 52 | return 200 53 | except Exception as err: 54 | print(f"Unable to do Force Subscribe to {Config.FORCE_SUB_CHANNEL}\n\nError: {err}") 55 | return 200 56 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import asyncio 4 | from pyrogram import Client, filters, idle 5 | from pyrogram.errors import UserNotParticipant 6 | from pyrogram.types import Message, ChatPermissions 7 | 8 | from configs import Config 9 | from handlers.database.access_db import db 10 | from handlers.forcesub_handler import ForceSub 11 | from handlers.forwarder_handler import forwardMessage 12 | from handlers.send_mesage_handler import sendMessage 13 | from handlers.database.add_user import AddUserToDatabase 14 | 15 | User = Client( 16 | session_name=Config.STRING_SESSION, 17 | api_id=Config.API_ID, 18 | api_hash=Config.API_HASH 19 | ) 20 | Bot = Client( 21 | session_name="Abir-Save-Group", 22 | api_id=Config.API_ID, 23 | api_hash=Config.API_HASH, 24 | bot_token=Config.BOT_TOKEN 25 | ) 26 | 27 | 28 | @User.on_message(filters.group & (filters.document | filters.video) & ~filters.edited) 29 | async def files_handler(bot: Client, cmd: Message): 30 | media = cmd.document or cmd.video 31 | if media.file_name.rsplit(".", 1)[-1] in Config.BLOCKED_EXTENSIONS: 32 | return 33 | if media.file_size < 5242880: 34 | return 35 | if (Config.FORCE_SUB_CHANNEL is not None) and (cmd.from_user.is_bot is False): 36 | await AddUserToDatabase(cmd) 37 | Fsub = await ForceSub(Bot, cmd) 38 | if Fsub == 400: 39 | await db.set_joined_channel(cmd.from_user.id, joined_channel=False) 40 | await db.set_group_id(cmd.from_user.id, group_id=cmd.chat.id) 41 | try: 42 | await bot.restrict_chat_member( 43 | chat_id=cmd.chat.id, 44 | user_id=cmd.from_user.id, 45 | permissions=ChatPermissions(can_send_messages=False) 46 | ) 47 | except: 48 | pass 49 | return 50 | elif Fsub == 404: 51 | try: 52 | await bot.kick_chat_member(chat_id=cmd.chat.id, user_id=cmd.from_user.id) 53 | except: 54 | pass 55 | else: 56 | await db.delete_user(cmd.from_user.id) 57 | forward = await forwardMessage(cmd) 58 | if cmd.from_user.is_bot: 59 | text = "This File will be deleted in 10 minutes.\n\n" \ 60 | "But,\n" \ 61 | "File Stored in Database!\n" \ 62 | f"**File Name:** `{media.file_name}`\n\n" \ 63 | f"[👉 Get File Now 👈](https://t.me/{(await Bot.get_me()).username}?start=AbirHasan2005_{str(forward.message_id)})" 64 | else: 65 | text = f"{cmd.from_user.mention} Unkil,\n" \ 66 | "This File will be deleted in 10 minutes.\n\n" \ 67 | "But,\n" \ 68 | "Your File stored in Database!\n\n" \ 69 | f"**File Name:** `{media.file_name}`\n\n" \ 70 | f"[👉 Get Your File Now 👈](https://t.me/{(await Bot.get_me()).username}?start=AbirHasan2005_{str(forward.message_id)})" 71 | await sendMessage( 72 | bot=bot, 73 | message_id=cmd.message_id, 74 | chat_id=cmd.chat.id, 75 | text=text 76 | ) 77 | await asyncio.sleep(600) 78 | try: 79 | await cmd.delete(True) 80 | except Exception as err: 81 | print(f"Unable to Delete Media Message!\nError: {err}\n\nMessage ID: {cmd.message_id}") 82 | 83 | 84 | @User.on_message(filters.private & (filters.text | filters.sticker) & ~filters.edited) 85 | async def text_handler(_, cmd: Message): 86 | await cmd.reply_text( 87 | "Hi Unkil!\n" 88 | "I am Group Files Store Userbot.\n\n" 89 | "Add me to Group I will Save Group Files & Delete Them After 10 Minutes. Also I will Reply with Revive Link.\n" 90 | "**Demo Group:** [Discovery Cloud](https://t.me/joinchat/O9WIjhCGHLo0YmQ0)", 91 | disable_web_page_preview=True 92 | ) 93 | 94 | 95 | @User.on_message(filters.group & filters.text & ~filters.edited) 96 | async def Fsub_handler(bot: Client, event: Message): 97 | if (Config.FORCE_SUB_CHANNEL is not None) and (event.from_user.is_bot is False): 98 | await AddUserToDatabase(event) 99 | Fsub = await ForceSub(Bot, event) 100 | if Fsub == 400: 101 | await db.set_joined_channel(event.from_user.id, joined_channel=False) 102 | await db.set_group_id(event.from_user.id, group_id=event.chat.id) 103 | try: 104 | await bot.restrict_chat_member( 105 | chat_id=event.chat.id, 106 | user_id=event.from_user.id, 107 | permissions=ChatPermissions(can_send_messages=False) 108 | ) 109 | except: 110 | pass 111 | elif Fsub == 404: 112 | try: 113 | await bot.kick_chat_member(chat_id=event.chat.id, user_id=event.from_user.id) 114 | except: 115 | pass 116 | else: 117 | await db.delete_user(event.from_user.id) 118 | 119 | 120 | @Bot.on_message(filters.private & filters.command("start") & ~filters.edited) 121 | async def start_handler(bot: Client, event: Message): 122 | __data = event.text.split("_")[-1] 123 | if __data == "/start": 124 | await sendMessage(bot, "Go Away Unkil", event.message_id, event.chat.id) 125 | else: 126 | file_id = int(__data) 127 | try: 128 | await bot.forward_messages(chat_id=event.chat.id, from_chat_id=Config.DB_CHANNEL_ID, message_ids=file_id) 129 | except: 130 | await sendMessage(bot, "Unable to Get Message!\n\nReport at @DevsZone !!", event.message_id, event.chat.id) 131 | 132 | 133 | @Bot.on_chat_member_updated() 134 | async def handle_Fsub_Join(bot: Client, event: Message): 135 | """ 136 | Auto Unmute Member after joining channel. 137 | 138 | :param bot: pyrogram.Client 139 | :param event: pyrogram.types.Message 140 | """ 141 | 142 | if Config.FORCE_SUB_CHANNEL: 143 | try: 144 | user_ = await bot.get_chat_member(event.chat.id, event.from_user.id) 145 | if user_.is_member is False: 146 | return 147 | except UserNotParticipant: 148 | return 149 | group_id = await db.get_group_id(event.from_user.id) 150 | group_message_id = await db.get_group_message_id(event.from_user.id) 151 | if group_id: 152 | try: 153 | await bot.unban_chat_member(chat_id=int(group_id), user_id=event.from_user.id) 154 | try: 155 | await bot.delete_messages(chat_id=int(group_id), message_ids=group_message_id, revoke=True) 156 | except Exception as err: 157 | print(f"Unable to Delete Message!\nError: {err}") 158 | await db.delete_user(user_id=event.from_user.id) 159 | except Exception as e: 160 | print(f"Skipping FSub ...\nError: {e}") 161 | 162 | # Start User Client 163 | User.start() 164 | print("Userbot Started!") 165 | # Start Bot Client 166 | Bot.start() 167 | print("Bot Started!") 168 | # Loop Clients till Disconnects 169 | idle() 170 | # Stop User Client 171 | User.stop() 172 | print("\n") 173 | print("Userbot Stopped!") 174 | # Stop Bot Client 175 | Bot.stop() 176 | print("Bot Stopped!") 177 | --------------------------------------------------------------------------------