├── 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 | [](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 |
--------------------------------------------------------------------------------