├── .gitattributes ├── .gitignore ├── Dockerfile ├── FileStream ├── __init__.py ├── __main__.py ├── bot │ ├── __init__.py │ ├── clients.py │ └── plugins │ │ ├── admin.py │ │ ├── callback.py │ │ ├── start.py │ │ └── stream.py ├── config.py ├── server │ ├── __init__.py │ ├── exceptions.py │ └── stream_routes.py ├── template │ ├── dl.html │ └── play.html └── utils │ ├── __init__.py │ ├── bot_utils.py │ ├── broadcast_helper.py │ ├── custom_dl.py │ ├── database.py │ ├── file_properties.py │ ├── human_readable.py │ ├── render_template.py │ ├── time_format.py │ └── translation.py ├── Procfile ├── README.md ├── app.json └── requirements.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # IPython 77 | profile_default/ 78 | ipython_config.py 79 | 80 | # pyenv 81 | .python-version 82 | 83 | # celery beat schedule file 84 | celerybeat-schedule 85 | 86 | # SageMath parsed files 87 | *.sage.py 88 | 89 | # Environments 90 | .env 91 | .venv 92 | env/ 93 | venv/ 94 | ENV/ 95 | env.bak/ 96 | venv.bak/ 97 | 98 | # Spyder project settings 99 | .spyderproject 100 | .spyproject 101 | 102 | # Rope project settings 103 | .ropeproject 104 | 105 | # mkdocs documentation 106 | /site 107 | 108 | # mypy 109 | .mypy_cache/ 110 | .dmypy.json 111 | dmypy.json 112 | 113 | # Pyre type checker 114 | .pyre/ 115 | 116 | #session files 117 | *.session 118 | *.session-journal 119 | 120 | .env 121 | test.py 122 | /test 123 | 124 | streambot.log.* 125 | .idea/workspace.xml 126 | *.xml 127 | *.xml 128 | .idea/workspace.xml 129 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11 2 | 3 | WORKDIR /app 4 | COPY . /app 5 | 6 | RUN pip install --upgrade pip 7 | RUN pip install -r requirements.txt 8 | 9 | COPY . . 10 | 11 | CMD ["python", "-m", "FileStream"] -------------------------------------------------------------------------------- /FileStream/__init__.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | __version__ = "1.1.0" 4 | StartTime = time.time() 5 | 6 | -------------------------------------------------------------------------------- /FileStream/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import asyncio 3 | import logging 4 | import traceback 5 | import logging.handlers as handlers 6 | from FileStream.config import Telegram, Server 7 | from aiohttp import web 8 | from pyrogram import idle 9 | 10 | from FileStream.bot import FileStream 11 | from FileStream.server import web_server 12 | from FileStream.bot.clients import initialize_clients 13 | 14 | logging.basicConfig( 15 | level=logging.INFO, 16 | datefmt="%d/%m/%Y %H:%M:%S", 17 | format='[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s', 18 | handlers=[logging.StreamHandler(stream=sys.stdout), 19 | handlers.RotatingFileHandler("streambot.log", mode="a", maxBytes=104857600, backupCount=2, encoding="utf-8")],) 20 | 21 | logging.getLogger("aiohttp").setLevel(logging.ERROR) 22 | logging.getLogger("pyrogram").setLevel(logging.ERROR) 23 | logging.getLogger("aiohttp.web").setLevel(logging.ERROR) 24 | 25 | server = web.AppRunner(web_server()) 26 | 27 | loop = asyncio.get_event_loop() 28 | 29 | async def start_services(): 30 | print() 31 | if Telegram.SECONDARY: 32 | print("------------------ Starting as Secondary Server ------------------") 33 | else: 34 | print("------------------- Starting as Primary Server -------------------") 35 | print() 36 | print("-------------------- Initializing Telegram Bot --------------------") 37 | 38 | 39 | await FileStream.start() 40 | bot_info = await FileStream.get_me() 41 | FileStream.id = bot_info.id 42 | FileStream.username = bot_info.username 43 | FileStream.fname=bot_info.first_name 44 | print("------------------------------ DONE ------------------------------") 45 | print() 46 | print("---------------------- Initializing Clients ----------------------") 47 | await initialize_clients() 48 | print("------------------------------ DONE ------------------------------") 49 | print() 50 | print("--------------------- Initializing Web Server ---------------------") 51 | await server.setup() 52 | await web.TCPSite(server, Server.BIND_ADDRESS, Server.PORT).start() 53 | print("------------------------------ DONE ------------------------------") 54 | print() 55 | print("------------------------- Service Started -------------------------") 56 | print(" bot =>> {}".format(bot_info.first_name)) 57 | if bot_info.dc_id: 58 | print(" DC ID =>> {}".format(str(bot_info.dc_id))) 59 | print(" URL =>> {}".format(Server.URL)) 60 | print("------------------------------------------------------------------") 61 | await idle() 62 | 63 | async def cleanup(): 64 | await server.cleanup() 65 | await FileStream.stop() 66 | 67 | if __name__ == "__main__": 68 | try: 69 | loop.run_until_complete(start_services()) 70 | except KeyboardInterrupt: 71 | pass 72 | except Exception as err: 73 | logging.error(traceback.format_exc()) 74 | finally: 75 | loop.run_until_complete(cleanup()) 76 | loop.stop() 77 | print("------------------------ Stopped Services ------------------------") -------------------------------------------------------------------------------- /FileStream/bot/__init__.py: -------------------------------------------------------------------------------- 1 | from ..config import Telegram 2 | from pyrogram import Client 3 | 4 | if Telegram.SECONDARY: 5 | plugins=None 6 | no_updates=True 7 | else: 8 | plugins={"root": "FileStream/bot/plugins"} 9 | no_updates=None 10 | 11 | FileStream = Client( 12 | name="FileStream", 13 | api_id=Telegram.API_ID, 14 | api_hash=Telegram.API_HASH, 15 | workdir="FileStream", 16 | plugins=plugins, 17 | bot_token=Telegram.BOT_TOKEN, 18 | sleep_threshold=Telegram.SLEEP_THRESHOLD, 19 | workers=Telegram.WORKERS, 20 | no_updates=no_updates 21 | ) 22 | 23 | multi_clients = {} 24 | work_loads = {} 25 | 26 | -------------------------------------------------------------------------------- /FileStream/bot/clients.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | from os import environ 4 | from ..config import Telegram 5 | from pyrogram import Client 6 | from . import multi_clients, work_loads, FileStream 7 | 8 | 9 | async def initialize_clients(): 10 | all_tokens = dict( 11 | (c + 1, t) 12 | for c, (_, t) in enumerate( 13 | filter( 14 | lambda n: n[0].startswith("MULTI_TOKEN"), sorted(environ.items()) 15 | ) 16 | ) 17 | ) 18 | if not all_tokens: 19 | multi_clients[0] = FileStream 20 | work_loads[0] = 0 21 | print("No additional clients found, using default client") 22 | return 23 | 24 | async def start_client(client_id, token): 25 | try: 26 | if len(token) >= 100: 27 | session_string=token 28 | bot_token=None 29 | print(f'Starting Client - {client_id} Using Session String') 30 | else: 31 | session_string=None 32 | bot_token=token 33 | print(f'Starting Client - {client_id} Using Bot Token') 34 | if client_id == len(all_tokens): 35 | await asyncio.sleep(2) 36 | print("This will take some time, please wait...") 37 | client = await Client( 38 | name=str(client_id), 39 | api_id=Telegram.API_ID, 40 | api_hash=Telegram.API_HASH, 41 | bot_token=bot_token, 42 | sleep_threshold=Telegram.SLEEP_THRESHOLD, 43 | no_updates=True, 44 | session_string=session_string, 45 | in_memory=True, 46 | ).start() 47 | client.id = (await client.get_me()).id 48 | work_loads[client_id] = 0 49 | return client_id, client 50 | except Exception: 51 | logging.error(f"Failed starting Client - {client_id} Error:", exc_info=True) 52 | 53 | clients = await asyncio.gather(*[start_client(i, token) for i, token in all_tokens.items()]) 54 | multi_clients.update(dict(clients)) 55 | if len(multi_clients) != 1: 56 | Telegram.MULTI_CLIENT = True 57 | print("Multi-Client Mode Enabled") 58 | else: 59 | print("No additional clients were initialized, using default client") 60 | -------------------------------------------------------------------------------- /FileStream/bot/plugins/admin.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import string 4 | import random 5 | import asyncio 6 | import aiofiles 7 | import datetime 8 | 9 | from FileStream.utils.broadcast_helper import send_msg 10 | from FileStream.utils.database import Database 11 | from FileStream.bot import FileStream 12 | from FileStream.server.exceptions import FIleNotFound 13 | from FileStream.config import Telegram, Server 14 | from pyrogram import filters, Client 15 | from pyrogram.types import Message 16 | from pyrogram.enums.parse_mode import ParseMode 17 | 18 | db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME) 19 | broadcast_ids = {} 20 | 21 | 22 | @FileStream.on_message(filters.command("status") & filters.private & filters.user(Telegram.OWNER_ID)) 23 | async def sts(c: Client, m: Message): 24 | await m.reply_text(text=f"""**Total Users in DB:** `{await db.total_users_count()}` 25 | **Banned Users in DB:** `{await db.total_banned_users_count()}` 26 | **Total Links Generated: ** `{await db.total_files()}`""" 27 | , parse_mode=ParseMode.MARKDOWN, quote=True) 28 | 29 | 30 | @FileStream.on_message(filters.command("ban") & filters.private & filters.user(Telegram.OWNER_ID)) 31 | async def sts(b, m: Message): 32 | id = m.text.split("/ban ")[-1] 33 | if not await db.is_user_banned(int(id)): 34 | try: 35 | await db.ban_user(int(id)) 36 | await db.delete_user(int(id)) 37 | await m.reply_text(text=f"`{id}`** is Banned** ", parse_mode=ParseMode.MARKDOWN, quote=True) 38 | if not str(id).startswith('-100'): 39 | await b.send_message( 40 | chat_id=id, 41 | text="**Your Banned to Use The Bot**", 42 | parse_mode=ParseMode.MARKDOWN, 43 | disable_web_page_preview=True 44 | ) 45 | except Exception as e: 46 | await m.reply_text(text=f"**something went wrong: {e}** ", parse_mode=ParseMode.MARKDOWN, quote=True) 47 | else: 48 | await m.reply_text(text=f"`{id}`** is Already Banned** ", parse_mode=ParseMode.MARKDOWN, quote=True) 49 | 50 | 51 | @FileStream.on_message(filters.command("unban") & filters.private & filters.user(Telegram.OWNER_ID)) 52 | async def sts(b, m: Message): 53 | id = m.text.split("/unban ")[-1] 54 | if await db.is_user_banned(int(id)): 55 | try: 56 | await db.unban_user(int(id)) 57 | await m.reply_text(text=f"`{id}`** is Unbanned** ", parse_mode=ParseMode.MARKDOWN, quote=True) 58 | if not str(id).startswith('-100'): 59 | await b.send_message( 60 | chat_id=id, 61 | text="**Your Unbanned now Use can use The Bot**", 62 | parse_mode=ParseMode.MARKDOWN, 63 | disable_web_page_preview=True 64 | ) 65 | except Exception as e: 66 | await m.reply_text(text=f"** something went wrong: {e}**", parse_mode=ParseMode.MARKDOWN, quote=True) 67 | else: 68 | await m.reply_text(text=f"`{id}`** is not Banned** ", parse_mode=ParseMode.MARKDOWN, quote=True) 69 | 70 | 71 | @FileStream.on_message(filters.command("broadcast") & filters.private & filters.user(Telegram.OWNER_ID) & filters.reply) 72 | async def broadcast_(c, m): 73 | all_users = await db.get_all_users() 74 | broadcast_msg = m.reply_to_message 75 | while True: 76 | broadcast_id = ''.join([random.choice(string.ascii_letters) for i in range(3)]) 77 | if not broadcast_ids.get(broadcast_id): 78 | break 79 | out = await m.reply_text( 80 | text=f"Broadcast initiated! You will be notified with log file when all the users are notified." 81 | ) 82 | start_time = time.time() 83 | total_users = await db.total_users_count() 84 | done = 0 85 | failed = 0 86 | success = 0 87 | broadcast_ids[broadcast_id] = dict( 88 | total=total_users, 89 | current=done, 90 | failed=failed, 91 | success=success 92 | ) 93 | async with aiofiles.open('broadcast.txt', 'w') as broadcast_log_file: 94 | async for user in all_users: 95 | sts, msg = await send_msg( 96 | user_id=int(user['id']), 97 | message=broadcast_msg 98 | ) 99 | if msg is not None: 100 | await broadcast_log_file.write(msg) 101 | if sts == 200: 102 | success += 1 103 | else: 104 | failed += 1 105 | if sts == 400: 106 | await db.delete_user(user['id']) 107 | done += 1 108 | if broadcast_ids.get(broadcast_id) is None: 109 | break 110 | else: 111 | broadcast_ids[broadcast_id].update( 112 | dict( 113 | current=done, 114 | failed=failed, 115 | success=success 116 | ) 117 | ) 118 | try: 119 | await out.edit_text(f"Broadcast Status\n\ncurrent: {done}\nfailed:{failed}\nsuccess: {success}") 120 | except: 121 | pass 122 | if broadcast_ids.get(broadcast_id): 123 | broadcast_ids.pop(broadcast_id) 124 | completed_in = datetime.timedelta(seconds=int(time.time() - start_time)) 125 | await asyncio.sleep(3) 126 | await out.delete() 127 | if failed == 0: 128 | await m.reply_text( 129 | text=f"broadcast completed in `{completed_in}`\n\nTotal users {total_users}.\nTotal done {done}, {success} success and {failed} failed.", 130 | quote=True 131 | ) 132 | else: 133 | await m.reply_document( 134 | document='broadcast.txt', 135 | caption=f"broadcast completed in `{completed_in}`\n\nTotal users {total_users}.\nTotal done {done}, {success} success and {failed} failed.", 136 | quote=True 137 | ) 138 | os.remove('broadcast.txt') 139 | 140 | 141 | @FileStream.on_message(filters.command("del") & filters.private & filters.user(Telegram.OWNER_ID)) 142 | async def sts(c: Client, m: Message): 143 | file_id = m.text.split(" ")[-1] 144 | try: 145 | file_info = await db.get_file(file_id) 146 | except FIleNotFound: 147 | await m.reply_text( 148 | text=f"**ꜰɪʟᴇ ᴀʟʀᴇᴀᴅʏ ᴅᴇʟᴇᴛᴇᴅ**", 149 | quote=True 150 | ) 151 | return 152 | await db.delete_one_file(file_info['_id']) 153 | await db.count_links(file_info['user_id'], "-") 154 | await m.reply_text( 155 | text=f"**Fɪʟᴇ Dᴇʟᴇᴛᴇᴅ Sᴜᴄᴄᴇssғᴜʟʟʏ !** ", 156 | quote=True 157 | ) 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /FileStream/bot/plugins/callback.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import math 3 | from FileStream import __version__ 4 | from FileStream.bot import FileStream 5 | from FileStream.config import Telegram, Server 6 | from FileStream.utils.translation import LANG, BUTTON 7 | from FileStream.utils.bot_utils import gen_link 8 | from FileStream.utils.database import Database 9 | from FileStream.utils.human_readable import humanbytes 10 | from FileStream.server.exceptions import FIleNotFound 11 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery 12 | from pyrogram.file_id import FileId, FileType, PHOTO_TYPES 13 | from pyrogram.enums.parse_mode import ParseMode 14 | db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME) 15 | 16 | #---------------------[ START CMD ]---------------------# 17 | @FileStream.on_callback_query() 18 | async def cb_data(bot, update: CallbackQuery): 19 | usr_cmd = update.data.split("_") 20 | if usr_cmd[0] == "home": 21 | await update.message.edit_text( 22 | text=LANG.START_TEXT.format(update.from_user.mention, FileStream.username), 23 | disable_web_page_preview=True, 24 | reply_markup=BUTTON.START_BUTTONS 25 | ) 26 | elif usr_cmd[0] == "help": 27 | await update.message.edit_text( 28 | text=LANG.HELP_TEXT.format(Telegram.OWNER_ID), 29 | disable_web_page_preview=True, 30 | reply_markup=BUTTON.HELP_BUTTONS 31 | ) 32 | elif usr_cmd[0] == "about": 33 | await update.message.edit_text( 34 | text=LANG.ABOUT_TEXT.format(FileStream.fname, __version__), 35 | disable_web_page_preview=True, 36 | reply_markup=BUTTON.ABOUT_BUTTONS 37 | ) 38 | 39 | #---------------------[ MY FILES CMD ]---------------------# 40 | 41 | elif usr_cmd[0] == "N/A": 42 | await update.answer("N/A", True) 43 | elif usr_cmd[0] == "close": 44 | await update.message.delete() 45 | elif usr_cmd[0] == "msgdelete": 46 | await update.message.edit_caption( 47 | caption= "**Cᴏɴғɪʀᴍ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛʜᴇ Fɪʟᴇ**\n\n", 48 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ʏᴇs", callback_data=f"msgdelyes_{usr_cmd[1]}_{usr_cmd[2]}"), InlineKeyboardButton("ɴᴏ", callback_data=f"myfile_{usr_cmd[1]}_{usr_cmd[2]}")]]) 49 | ) 50 | elif usr_cmd[0] == "msgdelyes": 51 | await delete_user_file(usr_cmd[1], int(usr_cmd[2]), update) 52 | return 53 | elif usr_cmd[0] == "msgdelpvt": 54 | await update.message.edit_caption( 55 | caption= "**Cᴏɴғɪʀᴍ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛʜᴇ Fɪʟᴇ**\n\n", 56 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ʏᴇs", callback_data=f"msgdelpvtyes_{usr_cmd[1]}"), InlineKeyboardButton("ɴᴏ", callback_data=f"mainstream_{usr_cmd[1]}")]]) 57 | ) 58 | elif usr_cmd[0] == "msgdelpvtyes": 59 | await delete_user_filex(usr_cmd[1], update) 60 | return 61 | 62 | elif usr_cmd[0] == "mainstream": 63 | _id = usr_cmd[1] 64 | reply_markup, stream_text = await gen_link(_id=_id) 65 | await update.message.edit_text( 66 | text=stream_text, 67 | parse_mode=ParseMode.HTML, 68 | disable_web_page_preview=True, 69 | reply_markup=reply_markup, 70 | ) 71 | 72 | elif usr_cmd[0] == "userfiles": 73 | file_list, total_files = await gen_file_list_button(int(usr_cmd[1]), update.from_user.id) 74 | await update.message.edit_caption( 75 | caption="Total files: {}".format(total_files), 76 | reply_markup=InlineKeyboardMarkup(file_list) 77 | ) 78 | elif usr_cmd[0] == "myfile": 79 | await gen_file_menu(usr_cmd[1], usr_cmd[2], update) 80 | return 81 | elif usr_cmd[0] == "sendfile": 82 | myfile = await db.get_file(usr_cmd[1]) 83 | file_name = myfile['file_name'] 84 | await update.answer(f"Sending File {file_name}") 85 | await update.message.reply_cached_media(myfile['file_id'], caption=f'**{file_name}**') 86 | else: 87 | await update.message.delete() 88 | 89 | 90 | 91 | #---------------------[ MY FILES FUNC ]---------------------# 92 | 93 | async def gen_file_list_button(file_list_no: int, user_id: int): 94 | 95 | file_range=[file_list_no*10-10+1, file_list_no*10] 96 | user_files, total_files=await db.find_files(user_id, file_range) 97 | 98 | file_list=[] 99 | async for x in user_files: 100 | file_list.append([InlineKeyboardButton(x["file_name"], callback_data=f"myfile_{x['_id']}_{file_list_no}")]) 101 | if total_files > 10: 102 | file_list.append( 103 | [InlineKeyboardButton("◄", callback_data="{}".format("userfiles_"+str(file_list_no-1) if file_list_no > 1 else 'N/A')), 104 | InlineKeyboardButton(f"{file_list_no}/{math.ceil(total_files/10)}", callback_data="N/A"), 105 | InlineKeyboardButton("►", callback_data="{}".format("userfiles_"+str(file_list_no+1) if total_files > file_list_no*10 else 'N/A'))] 106 | ) 107 | if not file_list: 108 | file_list.append( 109 | [InlineKeyboardButton("ᴇᴍᴘᴛʏ", callback_data="N/A")]) 110 | file_list.append([InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data="close")]) 111 | return file_list, total_files 112 | 113 | async def gen_file_menu(_id, file_list_no, update: CallbackQuery): 114 | try: 115 | myfile_info=await db.get_file(_id) 116 | except FIleNotFound: 117 | await update.answer("File Not Found") 118 | return 119 | 120 | file_id=FileId.decode(myfile_info['file_id']) 121 | 122 | if file_id.file_type in PHOTO_TYPES: 123 | file_type = "Image" 124 | elif file_id.file_type == FileType.VOICE: 125 | file_type = "Voice" 126 | elif file_id.file_type in (FileType.VIDEO, FileType.ANIMATION, FileType.VIDEO_NOTE): 127 | file_type = "Video" 128 | elif file_id.file_type == FileType.DOCUMENT: 129 | file_type = "Document" 130 | elif file_id.file_type == FileType.STICKER: 131 | file_type = "Sticker" 132 | elif file_id.file_type == FileType.AUDIO: 133 | file_type = "Audio" 134 | else: 135 | file_type = "Unknown" 136 | 137 | page_link = f"{Server.URL}watch/{myfile_info['_id']}" 138 | stream_link = f"{Server.URL}dl/{myfile_info['_id']}" 139 | if "video" in file_type.lower(): 140 | MYFILES_BUTTONS = InlineKeyboardMarkup( 141 | [ 142 | [InlineKeyboardButton("sᴛʀᴇᴀᴍ", url=page_link), InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)], 143 | [InlineKeyboardButton("ɢᴇᴛ ғɪʟᴇ", callback_data=f"sendfile_{myfile_info['_id']}"), 144 | InlineKeyboardButton("ʀᴇᴠᴏᴋᴇ ғɪʟᴇ", callback_data=f"msgdelete_{myfile_info['_id']}_{file_list_no}")], 145 | [InlineKeyboardButton("ʙᴀᴄᴋ", callback_data="userfiles_{}".format(file_list_no))] 146 | ] 147 | ) 148 | else: 149 | MYFILES_BUTTONS = InlineKeyboardMarkup( 150 | [ 151 | [InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)], 152 | [InlineKeyboardButton("ɢᴇᴛ ғɪʟᴇ", callback_data=f"sendfile_{myfile_info['_id']}"), 153 | InlineKeyboardButton("ʀᴇᴠᴏᴋᴇ ғɪʟᴇ", callback_data=f"msgdelete_{myfile_info['_id']}_{file_list_no}")], 154 | [InlineKeyboardButton("ʙᴀᴄᴋ", callback_data="userfiles_{}".format(file_list_no))] 155 | ] 156 | ) 157 | 158 | TiMe = myfile_info['time'] 159 | if type(TiMe) == float: 160 | date = datetime.datetime.fromtimestamp(TiMe) 161 | await update.edit_message_caption( 162 | caption="**File Name :** `{}`\n**File Size :** `{}`\n**File Type :** `{}`\n**Created On :** `{}`".format(myfile_info['file_name'], 163 | humanbytes(int(myfile_info['file_size'])), 164 | file_type, 165 | TiMe if isinstance(TiMe,str) else date.date()), 166 | reply_markup=MYFILES_BUTTONS ) 167 | 168 | 169 | async def delete_user_file(_id, file_list_no: int, update:CallbackQuery): 170 | 171 | try: 172 | myfile_info=await db.get_file(_id) 173 | except FIleNotFound: 174 | await update.answer("File Already Deleted") 175 | return 176 | 177 | await db.delete_one_file(myfile_info['_id']) 178 | await db.count_links(update.from_user.id, "-") 179 | await update.message.edit_caption( 180 | caption= "**Fɪʟᴇ Dᴇʟᴇᴛᴇᴅ Sᴜᴄᴄᴇssғᴜʟʟʏ !**" + update.message.caption.replace("Cᴏɴғɪʀᴍ ʏᴏᴜ ᴡᴀɴᴛ ᴛᴏ ᴅᴇʟᴇᴛᴇ ᴛʜᴇ Fɪʟᴇ", ""), 181 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ʙᴀᴄᴋ", callback_data=f"userfiles_1")]]) 182 | ) 183 | 184 | async def delete_user_filex(_id, update:CallbackQuery): 185 | 186 | try: 187 | myfile_info=await db.get_file(_id) 188 | except FIleNotFound: 189 | await update.answer("File Already Deleted") 190 | return 191 | 192 | await db.delete_one_file(myfile_info['_id']) 193 | await db.count_links(update.from_user.id, "-") 194 | await update.message.edit_caption( 195 | caption= "**Fɪʟᴇ Dᴇʟᴇᴛᴇᴅ Sᴜᴄᴄᴇssғᴜʟʟʏ !**\n\n", 196 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data=f"close")]]) 197 | ) 198 | 199 | -------------------------------------------------------------------------------- /FileStream/bot/plugins/start.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import math 3 | from FileStream import __version__ 4 | from FileStream.bot import FileStream 5 | from FileStream.server.exceptions import FIleNotFound 6 | from FileStream.utils.bot_utils import gen_linkx, verify_user 7 | from FileStream.config import Telegram 8 | from FileStream.utils.database import Database 9 | from FileStream.utils.translation import LANG, BUTTON 10 | from pyrogram import filters, Client 11 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message 12 | from pyrogram.enums.parse_mode import ParseMode 13 | import asyncio 14 | 15 | db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME) 16 | 17 | @FileStream.on_message(filters.command('start') & filters.private) 18 | async def start(bot: Client, message: Message): 19 | if not await verify_user(bot, message): 20 | return 21 | usr_cmd = message.text.split("_")[-1] 22 | 23 | if usr_cmd == "/start": 24 | if Telegram.START_PIC: 25 | await message.reply_photo( 26 | photo=Telegram.START_PIC, 27 | caption=LANG.START_TEXT.format(message.from_user.mention, FileStream.username), 28 | parse_mode=ParseMode.HTML, 29 | reply_markup=BUTTON.START_BUTTONS 30 | ) 31 | else: 32 | await message.reply_text( 33 | text=LANG.START_TEXT.format(message.from_user.mention, FileStream.username), 34 | parse_mode=ParseMode.HTML, 35 | disable_web_page_preview=True, 36 | reply_markup=BUTTON.START_BUTTONS 37 | ) 38 | else: 39 | if "stream_" in message.text: 40 | try: 41 | file_check = await db.get_file(usr_cmd) 42 | file_id = str(file_check['_id']) 43 | if file_id == usr_cmd: 44 | reply_markup, stream_text = await gen_linkx(m=message, _id=file_id, 45 | name=[FileStream.username, FileStream.fname]) 46 | await message.reply_text( 47 | text=stream_text, 48 | parse_mode=ParseMode.HTML, 49 | disable_web_page_preview=True, 50 | reply_markup=reply_markup, 51 | quote=True 52 | ) 53 | 54 | except FIleNotFound as e: 55 | await message.reply_text("File Not Found") 56 | except Exception as e: 57 | await message.reply_text("Something Went Wrong") 58 | logging.error(e) 59 | 60 | elif "file_" in message.text: 61 | try: 62 | file_check = await db.get_file(usr_cmd) 63 | db_id = str(file_check['_id']) 64 | file_id = file_check['file_id'] 65 | file_name = file_check['file_name'] 66 | if db_id == usr_cmd: 67 | filex = await message.reply_cached_media(file_id=file_id, caption=f'**{file_name}**') 68 | await asyncio.sleep(3600) 69 | try: 70 | await filex.delete() 71 | await message.delete() 72 | except Exception: 73 | pass 74 | 75 | except FIleNotFound as e: 76 | await message.reply_text("**File Not Found**") 77 | except Exception as e: 78 | await message.reply_text("Something Went Wrong") 79 | logging.error(e) 80 | 81 | else: 82 | await message.reply_text(f"**Invalid Command**") 83 | 84 | @FileStream.on_message(filters.private & filters.command(["about"])) 85 | async def start(bot, message): 86 | if not await verify_user(bot, message): 87 | return 88 | if Telegram.START_PIC: 89 | await message.reply_photo( 90 | photo=Telegram.START_PIC, 91 | caption=LANG.ABOUT_TEXT.format(FileStream.fname, __version__), 92 | parse_mode=ParseMode.HTML, 93 | reply_markup=BUTTON.ABOUT_BUTTONS 94 | ) 95 | else: 96 | await message.reply_text( 97 | text=LANG.ABOUT_TEXT.format(FileStream.fname, __version__), 98 | disable_web_page_preview=True, 99 | reply_markup=BUTTON.ABOUT_BUTTONS 100 | ) 101 | 102 | @FileStream.on_message((filters.command('help')) & filters.private) 103 | async def help_handler(bot, message): 104 | if not await verify_user(bot, message): 105 | return 106 | if Telegram.START_PIC: 107 | await message.reply_photo( 108 | photo=Telegram.START_PIC, 109 | caption=LANG.HELP_TEXT.format(Telegram.OWNER_ID), 110 | parse_mode=ParseMode.HTML, 111 | reply_markup=BUTTON.HELP_BUTTONS 112 | ) 113 | else: 114 | await message.reply_text( 115 | text=LANG.HELP_TEXT.format(Telegram.OWNER_ID), 116 | parse_mode=ParseMode.HTML, 117 | disable_web_page_preview=True, 118 | reply_markup=BUTTON.HELP_BUTTONS 119 | ) 120 | 121 | # --------------------------------------------------------------------------------------------------- 122 | 123 | @FileStream.on_message(filters.command('files') & filters.private) 124 | async def my_files(bot: Client, message: Message): 125 | if not await verify_user(bot, message): 126 | return 127 | user_files, total_files = await db.find_files(message.from_user.id, [1, 10]) 128 | 129 | file_list = [] 130 | async for x in user_files: 131 | file_list.append([InlineKeyboardButton(x["file_name"], callback_data=f"myfile_{x['_id']}_{1}")]) 132 | if total_files > 10: 133 | file_list.append( 134 | [ 135 | InlineKeyboardButton("◄", callback_data="N/A"), 136 | InlineKeyboardButton(f"1/{math.ceil(total_files / 10)}", callback_data="N/A"), 137 | InlineKeyboardButton("►", callback_data="userfiles_2") 138 | ], 139 | ) 140 | if not file_list: 141 | file_list.append( 142 | [InlineKeyboardButton("ᴇᴍᴘᴛʏ", callback_data="N/A")], 143 | ) 144 | file_list.append([InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data="close")]) 145 | await message.reply_photo(photo=Telegram.FILE_PIC, 146 | caption="Total files: {}".format(total_files), 147 | reply_markup=InlineKeyboardMarkup(file_list)) 148 | 149 | 150 | -------------------------------------------------------------------------------- /FileStream/bot/plugins/stream.py: -------------------------------------------------------------------------------- 1 | 2 | import asyncio 3 | from FileStream.bot import FileStream, multi_clients 4 | from FileStream.utils.bot_utils import is_user_banned, is_user_exist, is_user_joined, gen_link, is_channel_banned, is_channel_exist, is_user_authorized 5 | from FileStream.utils.database import Database 6 | from FileStream.utils.file_properties import get_file_ids, get_file_info 7 | from FileStream.config import Telegram 8 | from pyrogram import filters, Client 9 | from pyrogram.errors import FloodWait 10 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 11 | from pyrogram.enums.parse_mode import ParseMode 12 | db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME) 13 | 14 | @FileStream.on_message( 15 | filters.private 16 | & ( 17 | filters.document 18 | | filters.video 19 | | filters.video_note 20 | | filters.audio 21 | | filters.voice 22 | | filters.animation 23 | | filters.photo 24 | ), 25 | group=4, 26 | ) 27 | async def private_receive_handler(bot: Client, message: Message): 28 | if not await is_user_authorized(message): 29 | return 30 | if await is_user_banned(message): 31 | return 32 | 33 | await is_user_exist(bot, message) 34 | if Telegram.FORCE_SUB: 35 | if not await is_user_joined(bot, message): 36 | return 37 | try: 38 | inserted_id = await db.add_file(get_file_info(message)) 39 | await get_file_ids(False, inserted_id, multi_clients, message) 40 | reply_markup, stream_text = await gen_link(_id=inserted_id) 41 | await message.reply_text( 42 | text=stream_text, 43 | parse_mode=ParseMode.HTML, 44 | disable_web_page_preview=True, 45 | reply_markup=reply_markup, 46 | quote=True 47 | ) 48 | except FloodWait as e: 49 | print(f"Sleeping for {str(e.value)}s") 50 | await asyncio.sleep(e.value) 51 | await bot.send_message(chat_id=Telegram.ULOG_CHANNEL, 52 | text=f"Gᴏᴛ FʟᴏᴏᴅWᴀɪᴛ ᴏғ {str(e.value)}s ғʀᴏᴍ [{message.from_user.first_name}](tg://user?id={message.from_user.id})\n\n**ᴜsᴇʀ ɪᴅ :** `{str(message.from_user.id)}`", 53 | disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN) 54 | 55 | 56 | @FileStream.on_message( 57 | filters.channel 58 | & ~filters.forwarded 59 | & ~filters.media_group 60 | & ( 61 | filters.document 62 | | filters.video 63 | | filters.video_note 64 | | filters.audio 65 | | filters.voice 66 | | filters.photo 67 | ) 68 | ) 69 | async def channel_receive_handler(bot: Client, message: Message): 70 | if await is_channel_banned(bot, message): 71 | return 72 | await is_channel_exist(bot, message) 73 | 74 | try: 75 | inserted_id = await db.add_file(get_file_info(message)) 76 | await get_file_ids(False, inserted_id, multi_clients, message) 77 | reply_markup, stream_link = await gen_link(_id=inserted_id) 78 | await bot.edit_message_reply_markup( 79 | chat_id=message.chat.id, 80 | message_id=message.id, 81 | reply_markup=InlineKeyboardMarkup( 82 | [[InlineKeyboardButton("Dᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ 📥", 83 | url=f"https://t.me/{FileStream.username}?start=stream_{str(inserted_id)}")]]) 84 | ) 85 | 86 | except FloodWait as w: 87 | print(f"Sleeping for {str(w.x)}s") 88 | await asyncio.sleep(w.x) 89 | await bot.send_message(chat_id=Telegram.ULOG_CHANNEL, 90 | text=f"ɢᴏᴛ ғʟᴏᴏᴅᴡᴀɪᴛ ᴏғ {str(w.x)}s ғʀᴏᴍ {message.chat.title}\n\n**ᴄʜᴀɴɴᴇʟ ɪᴅ :** `{str(message.chat.id)}`", 91 | disable_web_page_preview=True) 92 | except Exception as e: 93 | await bot.send_message(chat_id=Telegram.ULOG_CHANNEL, text=f"**#EʀʀᴏʀTʀᴀᴄᴋᴇʙᴀᴄᴋ:** `{e}`", 94 | disable_web_page_preview=True) 95 | print(f"Cᴀɴ'ᴛ Eᴅɪᴛ Bʀᴏᴀᴅᴄᴀsᴛ Mᴇssᴀɢᴇ!\nEʀʀᴏʀ: **Gɪᴠᴇ ᴍᴇ ᴇᴅɪᴛ ᴘᴇʀᴍɪssɪᴏɴ ɪɴ ᴜᴘᴅᴀᴛᴇs ᴀɴᴅ ʙɪɴ Cʜᴀɴɴᴇʟ!{e}**") 96 | 97 | -------------------------------------------------------------------------------- /FileStream/config.py: -------------------------------------------------------------------------------- 1 | from os import environ as env 2 | from dotenv import load_dotenv 3 | 4 | load_dotenv() 5 | 6 | class Telegram: 7 | API_ID = int(env.get("API_ID")) 8 | API_HASH = str(env.get("API_HASH")) 9 | BOT_TOKEN = str(env.get("BOT_TOKEN")) 10 | OWNER_ID = int(env.get('OWNER_ID', '7978482443')) 11 | WORKERS = int(env.get("WORKERS", "6")) # 6 workers = 6 commands at once 12 | DATABASE_URL = str(env.get('DATABASE_URL')) 13 | UPDATES_CHANNEL = str(env.get('UPDATES_CHANNEL', "Telegram")) 14 | SESSION_NAME = str(env.get('SESSION_NAME', 'FileStream')) 15 | FORCE_SUB_ID = env.get('FORCE_SUB_ID', None) 16 | FORCE_SUB = env.get('FORCE_UPDATES_CHANNEL', False) 17 | FORCE_SUB = True if str(FORCE_SUB).lower() == "true" else False 18 | SLEEP_THRESHOLD = int(env.get("SLEEP_THRESHOLD", "60")) 19 | FILE_PIC = env.get('FILE_PIC', "https://graph.org/file/5bb9935be0229adf98b73.jpg") 20 | START_PIC = env.get('START_PIC', "https://graph.org/file/290af25276fa34fa8f0aa.jpg") 21 | VERIFY_PIC = env.get('VERIFY_PIC', "https://graph.org/file/736e21cc0efa4d8c2a0e4.jpg") 22 | MULTI_CLIENT = False 23 | FLOG_CHANNEL = int(env.get("FLOG_CHANNEL", None)) # Logs channel for file logs 24 | ULOG_CHANNEL = int(env.get("ULOG_CHANNEL", None)) # Logs channel for user logs 25 | MODE = env.get("MODE", "primary") 26 | SECONDARY = True if MODE.lower() == "secondary" else False 27 | AUTH_USERS = list(set(int(x) for x in str(env.get("AUTH_USERS", "")).split())) 28 | 29 | class Server: 30 | PORT = int(env.get("PORT", 8080)) 31 | BIND_ADDRESS = str(env.get("BIND_ADDRESS", "0.0.0.0")) 32 | PING_INTERVAL = int(env.get("PING_INTERVAL", "1200")) 33 | HAS_SSL = str(env.get("HAS_SSL", "0").lower()) in ("1", "true", "t", "yes", "y") 34 | NO_PORT = str(env.get("NO_PORT", "0").lower()) in ("1", "true", "t", "yes", "y") 35 | FQDN = str(env.get("FQDN", BIND_ADDRESS)) 36 | URL = "http{}://{}{}/".format( 37 | "s" if HAS_SSL else "", FQDN, "" if NO_PORT else ":" + str(PORT) 38 | ) 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /FileStream/server/__init__.py: -------------------------------------------------------------------------------- 1 | from aiohttp import web 2 | from .stream_routes import routes 3 | 4 | def web_server(): 5 | web_app = web.Application(client_max_size=30000000) 6 | web_app.add_routes(routes) 7 | return web_app 8 | -------------------------------------------------------------------------------- /FileStream/server/exceptions.py: -------------------------------------------------------------------------------- 1 | class InvalidHash(Exception): 2 | message = "Invalid hash" 3 | 4 | class FIleNotFound(Exception): 5 | message = "File not found" -------------------------------------------------------------------------------- /FileStream/server/stream_routes.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | import logging 4 | import mimetypes 5 | import traceback 6 | from aiohttp import web 7 | from aiohttp.http_exceptions import BadStatusLine 8 | from FileStream.bot import multi_clients, work_loads, FileStream 9 | from FileStream.config import Telegram, Server 10 | from FileStream.server.exceptions import FIleNotFound, InvalidHash 11 | from FileStream import utils, StartTime, __version__ 12 | from FileStream.utils.render_template import render_page 13 | 14 | routes = web.RouteTableDef() 15 | 16 | @routes.get("/status", allow_head=True) 17 | async def root_route_handler(_): 18 | return web.json_response( 19 | { 20 | "server_status": "running", 21 | "uptime": utils.get_readable_time(time.time() - StartTime), 22 | "telegram_bot": "@" + FileStream.username, 23 | "connected_bots": len(multi_clients), 24 | "loads": dict( 25 | ("bot" + str(c + 1), l) 26 | for c, (_, l) in enumerate( 27 | sorted(work_loads.items(), key=lambda x: x[1], reverse=True) 28 | ) 29 | ), 30 | "version": __version__, 31 | } 32 | ) 33 | 34 | @routes.get("/watch/{path}", allow_head=True) 35 | async def stream_handler(request: web.Request): 36 | try: 37 | path = request.match_info["path"] 38 | return web.Response(text=await render_page(path), content_type='text/html') 39 | except InvalidHash as e: 40 | raise web.HTTPForbidden(text=e.message) 41 | except FIleNotFound as e: 42 | raise web.HTTPNotFound(text=e.message) 43 | except (AttributeError, BadStatusLine, ConnectionResetError): 44 | pass 45 | 46 | 47 | @routes.get("/dl/{path}", allow_head=True) 48 | async def stream_handler(request: web.Request): 49 | try: 50 | path = request.match_info["path"] 51 | return await media_streamer(request, path) 52 | except InvalidHash as e: 53 | raise web.HTTPForbidden(text=e.message) 54 | except FIleNotFound as e: 55 | raise web.HTTPNotFound(text=e.message) 56 | except (AttributeError, BadStatusLine, ConnectionResetError): 57 | pass 58 | except Exception as e: 59 | traceback.print_exc() 60 | logging.critical(e.with_traceback(None)) 61 | logging.debug(traceback.format_exc()) 62 | raise web.HTTPInternalServerError(text=str(e)) 63 | 64 | class_cache = {} 65 | 66 | async def media_streamer(request: web.Request, db_id: str): 67 | range_header = request.headers.get("Range", 0) 68 | 69 | index = min(work_loads, key=work_loads.get) 70 | faster_client = multi_clients[index] 71 | 72 | if Telegram.MULTI_CLIENT: 73 | logging.info(f"Client {index} is now serving {request.headers.get('X-FORWARDED-FOR',request.remote)}") 74 | 75 | if faster_client in class_cache: 76 | tg_connect = class_cache[faster_client] 77 | logging.debug(f"Using cached ByteStreamer object for client {index}") 78 | else: 79 | logging.debug(f"Creating new ByteStreamer object for client {index}") 80 | tg_connect = utils.ByteStreamer(faster_client) 81 | class_cache[faster_client] = tg_connect 82 | logging.debug("before calling get_file_properties") 83 | file_id = await tg_connect.get_file_properties(db_id, multi_clients) 84 | logging.debug("after calling get_file_properties") 85 | 86 | file_size = file_id.file_size 87 | 88 | if range_header: 89 | from_bytes, until_bytes = range_header.replace("bytes=", "").split("-") 90 | from_bytes = int(from_bytes) 91 | until_bytes = int(until_bytes) if until_bytes else file_size - 1 92 | else: 93 | from_bytes = request.http_range.start or 0 94 | until_bytes = (request.http_range.stop or file_size) - 1 95 | 96 | if (until_bytes > file_size) or (from_bytes < 0) or (until_bytes < from_bytes): 97 | return web.Response( 98 | status=416, 99 | body="416: Range not satisfiable", 100 | headers={"Content-Range": f"bytes */{file_size}"}, 101 | ) 102 | 103 | chunk_size = 1024 * 1024 104 | until_bytes = min(until_bytes, file_size - 1) 105 | 106 | offset = from_bytes - (from_bytes % chunk_size) 107 | first_part_cut = from_bytes - offset 108 | last_part_cut = until_bytes % chunk_size + 1 109 | 110 | req_length = until_bytes - from_bytes + 1 111 | part_count = math.ceil(until_bytes / chunk_size) - math.floor(offset / chunk_size) 112 | body = tg_connect.yield_file( 113 | file_id, index, offset, first_part_cut, last_part_cut, part_count, chunk_size 114 | ) 115 | 116 | mime_type = file_id.mime_type 117 | file_name = utils.get_name(file_id) 118 | disposition = "attachment" 119 | 120 | if not mime_type: 121 | mime_type = mimetypes.guess_type(file_name)[0] or "application/octet-stream" 122 | 123 | # if "video/" in mime_type or "audio/" in mime_type: 124 | # disposition = "inline" 125 | 126 | return web.Response( 127 | status=206 if range_header else 200, 128 | body=body, 129 | headers={ 130 | "Content-Type": f"{mime_type}", 131 | "Content-Range": f"bytes {from_bytes}-{until_bytes}/{file_size}", 132 | "Content-Length": str(req_length), 133 | "Content-Disposition": f'{disposition}; filename="{file_name}"', 134 | "Accept-Ranges": "bytes", 135 | }, 136 | ) 137 | -------------------------------------------------------------------------------- /FileStream/template/dl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{file_name}} 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 | {{file_name}} 19 |
20 |
21 | 22 |
23 | 24 | 30 | 31 |
32 | 43 | 44 | -------------------------------------------------------------------------------- /FileStream/template/play.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | FileStreamBot | {{file_name}} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 33 |
34 |
35 | WELCOME 36 | CHANNELS 37 | CONTACT 38 |
39 |
40 | 41 |
42 |
43 |
44 | 46 |
47 |
48 |

File Name:

49 |

{{file_name}}


50 |

File Size:

51 |

{{file_size}}

52 |
53 |
54 | 57 | 60 | 63 | 66 | 69 |
70 | 71 |
72 |
73 |
74 |
75 | 76 |
77 |

WELCOME TO OUR FILE STREAM BOT

78 |

79 | This is a Telegram Bot to Stream Movies and Series directly on 80 | Telegram. You can also 81 | download them if you want. This bot is developed by Avi 83 |

If you like this bot, then don't 84 | forget to share it with your friends and family. 85 |

86 |
87 | 88 |
89 |

JOIN OUR TELEGRAM CHANNELS

90 | 104 |
105 | 106 |
107 |

Report Bugs and Contact us on Telegram Below

108 | 113 |
114 | 115 |
116 |
117 |
118 |
119 | 151 |
152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /FileStream/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .time_format import get_readable_time 2 | from .file_properties import get_name, get_file_ids 3 | from .custom_dl import ByteStreamer -------------------------------------------------------------------------------- /FileStream/utils/bot_utils.py: -------------------------------------------------------------------------------- 1 | from pyrogram.errors import UserNotParticipant, FloodWait 2 | from pyrogram.enums.parse_mode import ParseMode 3 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message 4 | from FileStream.utils.translation import LANG 5 | from FileStream.utils.database import Database 6 | from FileStream.utils.human_readable import humanbytes 7 | from FileStream.config import Telegram, Server 8 | from FileStream.bot import FileStream 9 | import asyncio 10 | from typing import ( 11 | Union 12 | ) 13 | 14 | 15 | db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME) 16 | 17 | async def get_invite_link(bot, chat_id: Union[str, int]): 18 | try: 19 | invite_link = await bot.create_chat_invite_link(chat_id=chat_id) 20 | return invite_link 21 | except FloodWait as e: 22 | print(f"Sleep of {e.value}s caused by FloodWait ...") 23 | await asyncio.sleep(e.value) 24 | return await get_invite_link(bot, chat_id) 25 | 26 | async def is_user_joined(bot, message: Message): 27 | if Telegram.FORCE_SUB_ID and Telegram.FORCE_SUB_ID.startswith("-100"): 28 | channel_chat_id = int(Telegram.FORCE_SUB_ID) # When id startswith with -100 29 | elif Telegram.FORCE_SUB_ID and (not Telegram.FORCE_SUB_ID.startswith("-100")): 30 | channel_chat_id = Telegram.FORCE_SUB_ID # When id not startswith -100 31 | else: 32 | return 200 33 | try: 34 | user = await bot.get_chat_member(chat_id=channel_chat_id, user_id=message.from_user.id) 35 | if user.status == "BANNED": 36 | await message.reply_text( 37 | text=LANG.BAN_TEXT.format(Telegram.OWNER_ID), 38 | parse_mode=ParseMode.MARKDOWN, 39 | disable_web_page_preview=True 40 | ) 41 | return False 42 | except UserNotParticipant: 43 | invite_link = await get_invite_link(bot, chat_id=channel_chat_id) 44 | if Telegram.VERIFY_PIC: 45 | ver = await message.reply_photo( 46 | photo=Telegram.VERIFY_PIC, 47 | caption="Jᴏɪɴ ᴍʏ ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ ᴛᴏ ᴜsᴇ ᴍᴇ 🔐", 48 | parse_mode=ParseMode.HTML, 49 | reply_markup=InlineKeyboardMarkup( 50 | [[ 51 | InlineKeyboardButton("❆ Jᴏɪɴ Oᴜʀ Cʜᴀɴɴᴇʟ ❆", url=invite_link.invite_link) 52 | ]] 53 | ) 54 | ) 55 | else: 56 | ver = await message.reply_text( 57 | text = "Jᴏɪɴ ᴍʏ ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ ᴛᴏ ᴜsᴇ ᴍᴇ 🔐", 58 | reply_markup=InlineKeyboardMarkup( 59 | [[ 60 | InlineKeyboardButton("❆ Jᴏɪɴ Oᴜʀ Cʜᴀɴɴᴇʟ ❆", url=invite_link.invite_link) 61 | ]] 62 | ), 63 | parse_mode=ParseMode.HTML 64 | ) 65 | await asyncio.sleep(30) 66 | try: 67 | await ver.delete() 68 | await message.delete() 69 | except Exception: 70 | pass 71 | return False 72 | except Exception: 73 | await message.reply_text( 74 | text = f"Sᴏᴍᴇᴛʜɪɴɢ ᴡʀᴏɴɢ ᴄᴏɴᴛᴀᴄᴛ ᴍʏ ᴅᴇᴠᴇʟᴏᴘᴇʀ [ ᴄʟɪᴄᴋ ʜᴇʀᴇ ]", 75 | parse_mode=ParseMode.HTML, 76 | disable_web_page_preview=True) 77 | return False 78 | return True 79 | 80 | #---------------------[ PRIVATE GEN LINK + CALLBACK ]---------------------# 81 | 82 | async def gen_link(_id): 83 | file_info = await db.get_file(_id) 84 | file_name = file_info['file_name'] 85 | file_size = humanbytes(file_info['file_size']) 86 | mime_type = file_info['mime_type'] 87 | 88 | page_link = f"{Server.URL}watch/{_id}" 89 | stream_link = f"{Server.URL}dl/{_id}" 90 | file_link = f"https://t.me/{FileStream.username}?start=file_{_id}" 91 | 92 | if "video" in mime_type: 93 | stream_text = LANG.STREAM_TEXT.format(file_name, file_size, stream_link, page_link, file_link) 94 | reply_markup = InlineKeyboardMarkup( 95 | [ 96 | [InlineKeyboardButton("sᴛʀᴇᴀᴍ", url=page_link), InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)], 97 | [InlineKeyboardButton("ɢᴇᴛ ғɪʟᴇ", url=file_link), InlineKeyboardButton("ʀᴇᴠᴏᴋᴇ ғɪʟᴇ", callback_data=f"msgdelpvt_{_id}")], 98 | [InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data="close")] 99 | ] 100 | ) 101 | else: 102 | stream_text = LANG.STREAM_TEXT_X.format(file_name, file_size, stream_link, file_link) 103 | reply_markup = InlineKeyboardMarkup( 104 | [ 105 | [InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)], 106 | [InlineKeyboardButton("ɢᴇᴛ ғɪʟᴇ", url=file_link), InlineKeyboardButton("ʀᴇᴠᴏᴋᴇ ғɪʟᴇ", callback_data=f"msgdelpvt_{_id}")], 107 | [InlineKeyboardButton("ᴄʟᴏsᴇ", callback_data="close")] 108 | ] 109 | ) 110 | return reply_markup, stream_text 111 | 112 | #---------------------[ GEN STREAM LINKS FOR CHANNEL ]---------------------# 113 | 114 | async def gen_linkx(m:Message , _id, name: list): 115 | file_info = await db.get_file(_id) 116 | file_name = file_info['file_name'] 117 | mime_type = file_info['mime_type'] 118 | file_size = humanbytes(file_info['file_size']) 119 | 120 | page_link = f"{Server.URL}watch/{_id}" 121 | stream_link = f"{Server.URL}dl/{_id}" 122 | file_link = f"https://t.me/{FileStream.username}?start=file_{_id}" 123 | 124 | if "video" in mime_type: 125 | stream_text= LANG.STREAM_TEXT_X.format(file_name, file_size, stream_link, page_link) 126 | reply_markup = InlineKeyboardMarkup( 127 | [ 128 | [InlineKeyboardButton("sᴛʀᴇᴀᴍ", url=page_link), InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)] 129 | ] 130 | ) 131 | else: 132 | stream_text= LANG.STREAM_TEXT_X.format(file_name, file_size, stream_link, file_link) 133 | reply_markup = InlineKeyboardMarkup( 134 | [ 135 | [InlineKeyboardButton("ᴅᴏᴡɴʟᴏᴀᴅ", url=stream_link)] 136 | ] 137 | ) 138 | return reply_markup, stream_text 139 | 140 | #---------------------[ USER BANNED ]---------------------# 141 | 142 | async def is_user_banned(message): 143 | if await db.is_user_banned(message.from_user.id): 144 | await message.reply_text( 145 | text=LANG.BAN_TEXT.format(Telegram.OWNER_ID), 146 | parse_mode=ParseMode.MARKDOWN, 147 | disable_web_page_preview=True 148 | ) 149 | return True 150 | return False 151 | 152 | #---------------------[ CHANNEL BANNED ]---------------------# 153 | 154 | async def is_channel_banned(bot, message): 155 | if await db.is_user_banned(message.chat.id): 156 | await bot.edit_message_reply_markup( 157 | chat_id=message.chat.id, 158 | message_id=message.id, 159 | reply_markup=InlineKeyboardMarkup([[ 160 | InlineKeyboardButton(f"ᴄʜᴀɴɴᴇʟ ɪs ʙᴀɴɴᴇᴅ", callback_data="N/A")]]) 161 | ) 162 | return True 163 | return False 164 | 165 | #---------------------[ USER AUTH ]---------------------# 166 | 167 | async def is_user_authorized(message): 168 | if hasattr(Telegram, 'AUTH_USERS') and Telegram.AUTH_USERS: 169 | user_id = message.from_user.id 170 | 171 | if user_id == Telegram.OWNER_ID: 172 | return True 173 | 174 | if not (user_id in Telegram.AUTH_USERS): 175 | await message.reply_text( 176 | text="Yᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴀᴜᴛʜᴏʀɪᴢᴇᴅ ᴛᴏ ᴜsᴇ ᴛʜɪs ʙᴏᴛ.", 177 | parse_mode=ParseMode.MARKDOWN, 178 | disable_web_page_preview=True 179 | ) 180 | return False 181 | 182 | return True 183 | 184 | #---------------------[ USER EXIST ]---------------------# 185 | 186 | async def is_user_exist(bot, message): 187 | if not bool(await db.get_user(message.from_user.id)): 188 | await db.add_user(message.from_user.id) 189 | await bot.send_message( 190 | Telegram.ULOG_CHANNEL, 191 | f"**#NᴇᴡUsᴇʀ**\n**⬩ ᴜsᴇʀ ɴᴀᴍᴇ :** [{message.from_user.first_name}](tg://user?id={message.from_user.id})\n**⬩ ᴜsᴇʀ ɪᴅ :** `{message.from_user.id}`" 192 | ) 193 | 194 | async def is_channel_exist(bot, message): 195 | if not bool(await db.get_user(message.chat.id)): 196 | await db.add_user(message.chat.id) 197 | members = await bot.get_chat_members_count(message.chat.id) 198 | await bot.send_message( 199 | Telegram.ULOG_CHANNEL, 200 | f"**#NᴇᴡCʜᴀɴɴᴇʟ** \n**⬩ ᴄʜᴀᴛ ɴᴀᴍᴇ :** `{message.chat.title}`\n**⬩ ᴄʜᴀᴛ ɪᴅ :** `{message.chat.id}`\n**⬩ ᴛᴏᴛᴀʟ ᴍᴇᴍʙᴇʀs :** `{members}`" 201 | ) 202 | 203 | async def verify_user(bot, message): 204 | if not await is_user_authorized(message): 205 | return False 206 | 207 | if await is_user_banned(message): 208 | return False 209 | 210 | await is_user_exist(bot, message) 211 | 212 | if Telegram.FORCE_SUB: 213 | if not await is_user_joined(bot, message): 214 | return False 215 | 216 | return True 217 | -------------------------------------------------------------------------------- /FileStream/utils/broadcast_helper.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import traceback 3 | from pyrogram.errors import FloodWait, InputUserDeactivated, UserIsBlocked, PeerIdInvalid 4 | 5 | async def send_msg(user_id, message): 6 | try: 7 | await message.copy(chat_id=user_id) 8 | return 200, None 9 | except FloodWait as e: 10 | await asyncio.sleep(e.value) 11 | return send_msg(user_id, message) 12 | except InputUserDeactivated: 13 | return 400, f"{user_id} : deactivated\n" 14 | except UserIsBlocked: 15 | return 400, f"{user_id} : blocked the bot\n" 16 | except PeerIdInvalid: 17 | return 400, f"{user_id} : user id invalid\n" 18 | except Exception as e: 19 | return 500, f"{user_id} : {traceback.format_exc()}\n" 20 | -------------------------------------------------------------------------------- /FileStream/utils/custom_dl.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import logging 3 | from typing import Dict, Union 4 | from FileStream.bot import work_loads 5 | from pyrogram import Client, utils, raw 6 | from .file_properties import get_file_ids 7 | from pyrogram.session import Session, Auth 8 | from pyrogram.errors import AuthBytesInvalid 9 | from pyrogram.file_id import FileId, FileType, ThumbnailSource 10 | from pyrogram.types import Message 11 | 12 | class ByteStreamer: 13 | def __init__(self, client: Client): 14 | self.clean_timer = 30 * 60 15 | self.client: Client = client 16 | self.cached_file_ids: Dict[str, FileId] = {} 17 | asyncio.create_task(self.clean_cache()) 18 | 19 | async def get_file_properties(self, db_id: str, multi_clients) -> FileId: 20 | """ 21 | Returns the properties of a media of a specific message in a FIleId class. 22 | if the properties are cached, then it'll return the cached results. 23 | or it'll generate the properties from the Message ID and cache them. 24 | """ 25 | if not db_id in self.cached_file_ids: 26 | logging.debug("Before Calling generate_file_properties") 27 | await self.generate_file_properties(db_id, multi_clients) 28 | logging.debug(f"Cached file properties for file with ID {db_id}") 29 | return self.cached_file_ids[db_id] 30 | 31 | async def generate_file_properties(self, db_id: str, multi_clients) -> FileId: 32 | """ 33 | Generates the properties of a media file on a specific message. 34 | returns ths properties in a FIleId class. 35 | """ 36 | logging.debug("Before calling get_file_ids") 37 | file_id = await get_file_ids(self.client, db_id, multi_clients, Message) 38 | logging.debug(f"Generated file ID and Unique ID for file with ID {db_id}") 39 | self.cached_file_ids[db_id] = file_id 40 | logging.debug(f"Cached media file with ID {db_id}") 41 | return self.cached_file_ids[db_id] 42 | 43 | async def generate_media_session(self, client: Client, file_id: FileId) -> Session: 44 | """ 45 | Generates the media session for the DC that contains the media file. 46 | This is required for getting the bytes from Telegram servers. 47 | """ 48 | 49 | media_session = client.media_sessions.get(file_id.dc_id, None) 50 | 51 | if media_session is None: 52 | if file_id.dc_id != await client.storage.dc_id(): 53 | media_session = Session( 54 | client, 55 | file_id.dc_id, 56 | await Auth( 57 | client, file_id.dc_id, await client.storage.test_mode() 58 | ).create(), 59 | await client.storage.test_mode(), 60 | is_media=True, 61 | ) 62 | await media_session.start() 63 | 64 | for _ in range(6): 65 | exported_auth = await client.invoke( 66 | raw.functions.auth.ExportAuthorization(dc_id=file_id.dc_id) 67 | ) 68 | 69 | try: 70 | await media_session.invoke( 71 | raw.functions.auth.ImportAuthorization( 72 | id=exported_auth.id, bytes=exported_auth.bytes 73 | ) 74 | ) 75 | break 76 | except AuthBytesInvalid: 77 | logging.debug( 78 | f"Invalid authorization bytes for DC {file_id.dc_id}" 79 | ) 80 | continue 81 | else: 82 | await media_session.stop() 83 | raise AuthBytesInvalid 84 | else: 85 | media_session = Session( 86 | client, 87 | file_id.dc_id, 88 | await client.storage.auth_key(), 89 | await client.storage.test_mode(), 90 | is_media=True, 91 | ) 92 | await media_session.start() 93 | logging.debug(f"Created media session for DC {file_id.dc_id}") 94 | client.media_sessions[file_id.dc_id] = media_session 95 | else: 96 | logging.debug(f"Using cached media session for DC {file_id.dc_id}") 97 | return media_session 98 | 99 | 100 | @staticmethod 101 | async def get_location(file_id: FileId) -> Union[raw.types.InputPhotoFileLocation, 102 | raw.types.InputDocumentFileLocation, 103 | raw.types.InputPeerPhotoFileLocation,]: 104 | """ 105 | Returns the file location for the media file. 106 | """ 107 | file_type = file_id.file_type 108 | 109 | if file_type == FileType.CHAT_PHOTO: 110 | if file_id.chat_id > 0: 111 | peer = raw.types.InputPeerUser( 112 | user_id=file_id.chat_id, access_hash=file_id.chat_access_hash 113 | ) 114 | else: 115 | if file_id.chat_access_hash == 0: 116 | peer = raw.types.InputPeerChat(chat_id=-file_id.chat_id) 117 | else: 118 | peer = raw.types.InputPeerChannel( 119 | channel_id=utils.get_channel_id(file_id.chat_id), 120 | access_hash=file_id.chat_access_hash, 121 | ) 122 | 123 | location = raw.types.InputPeerPhotoFileLocation( 124 | peer=peer, 125 | volume_id=file_id.volume_id, 126 | local_id=file_id.local_id, 127 | big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG, 128 | ) 129 | elif file_type == FileType.PHOTO: 130 | location = raw.types.InputPhotoFileLocation( 131 | id=file_id.media_id, 132 | access_hash=file_id.access_hash, 133 | file_reference=file_id.file_reference, 134 | thumb_size=file_id.thumbnail_size, 135 | ) 136 | else: 137 | location = raw.types.InputDocumentFileLocation( 138 | id=file_id.media_id, 139 | access_hash=file_id.access_hash, 140 | file_reference=file_id.file_reference, 141 | thumb_size=file_id.thumbnail_size, 142 | ) 143 | return location 144 | 145 | async def yield_file( 146 | self, 147 | file_id: FileId, 148 | index: int, 149 | offset: int, 150 | first_part_cut: int, 151 | last_part_cut: int, 152 | part_count: int, 153 | chunk_size: int, 154 | ) -> Union[str, None]: 155 | """ 156 | Custom generator that yields the bytes of the media file. 157 | Modded from 158 | Thanks to Eyaadh 159 | """ 160 | client = self.client 161 | work_loads[index] += 1 162 | logging.debug(f"Starting to yielding file with client {index}.") 163 | media_session = await self.generate_media_session(client, file_id) 164 | 165 | current_part = 1 166 | 167 | location = await self.get_location(file_id) 168 | 169 | try: 170 | r = await media_session.invoke( 171 | raw.functions.upload.GetFile( 172 | location=location, offset=offset, limit=chunk_size 173 | ), 174 | ) 175 | if isinstance(r, raw.types.upload.File): 176 | while True: 177 | chunk = r.bytes 178 | if not chunk: 179 | break 180 | elif part_count == 1: 181 | yield chunk[first_part_cut:last_part_cut] 182 | elif current_part == 1: 183 | yield chunk[first_part_cut:] 184 | elif current_part == part_count: 185 | yield chunk[:last_part_cut] 186 | else: 187 | yield chunk 188 | 189 | current_part += 1 190 | offset += chunk_size 191 | 192 | if current_part > part_count: 193 | break 194 | 195 | r = await media_session.invoke( 196 | raw.functions.upload.GetFile( 197 | location=location, offset=offset, limit=chunk_size 198 | ), 199 | ) 200 | except (TimeoutError, AttributeError): 201 | pass 202 | finally: 203 | logging.debug(f"Finished yielding file with {current_part} parts.") 204 | work_loads[index] -= 1 205 | 206 | 207 | async def clean_cache(self) -> None: 208 | """ 209 | function to clean the cache to reduce memory usage 210 | """ 211 | while True: 212 | await asyncio.sleep(self.clean_timer) 213 | self.cached_file_ids.clear() 214 | logging.debug("Cleaned the cache") 215 | -------------------------------------------------------------------------------- /FileStream/utils/database.py: -------------------------------------------------------------------------------- 1 | import pymongo 2 | import time 3 | import motor.motor_asyncio 4 | from bson.objectid import ObjectId 5 | from bson.errors import InvalidId 6 | from FileStream.server.exceptions import FIleNotFound 7 | 8 | class Database: 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 | self.black = self.db.blacklist 14 | self.file = self.db.file 15 | 16 | #---------------------[ NEW USER ]---------------------# 17 | def new_user(self, id): 18 | return dict( 19 | id=id, 20 | join_date=time.time(), 21 | Links=0 22 | ) 23 | 24 | # ---------------------[ ADD USER ]---------------------# 25 | async def add_user(self, id): 26 | user = self.new_user(id) 27 | await self.col.insert_one(user) 28 | 29 | # ---------------------[ GET USER ]---------------------# 30 | async def get_user(self, id): 31 | user = await self.col.find_one({'id': int(id)}) 32 | return user 33 | 34 | # ---------------------[ CHECK USER ]---------------------# 35 | async def total_users_count(self): 36 | count = await self.col.count_documents({}) 37 | return count 38 | 39 | async def get_all_users(self): 40 | all_users = self.col.find({}) 41 | return all_users 42 | 43 | # ---------------------[ REMOVE USER ]---------------------# 44 | async def delete_user(self, user_id): 45 | await self.col.delete_many({'id': int(user_id)}) 46 | 47 | # ---------------------[ BAN, UNBAN USER ]---------------------# 48 | def black_user(self, id): 49 | return dict( 50 | id=id, 51 | ban_date=time.time() 52 | ) 53 | 54 | async def ban_user(self, id): 55 | user = self.black_user(id) 56 | await self.black.insert_one(user) 57 | 58 | async def unban_user(self, id): 59 | await self.black.delete_one({'id': int(id)}) 60 | 61 | async def is_user_banned(self, id): 62 | user = await self.black.find_one({'id': int(id)}) 63 | return True if user else False 64 | 65 | async def total_banned_users_count(self): 66 | count = await self.black.count_documents({}) 67 | return count 68 | 69 | # ---------------------[ ADD FILE TO DB ]---------------------# 70 | async def add_file(self, file_info): 71 | file_info["time"] = time.time() 72 | fetch_old = await self.get_file_by_fileuniqueid(file_info["user_id"], file_info["file_unique_id"]) 73 | if fetch_old: 74 | return fetch_old["_id"] 75 | await self.count_links(file_info["user_id"], "+") 76 | return (await self.file.insert_one(file_info)).inserted_id 77 | 78 | # ---------------------[ FIND FILE IN DB ]---------------------# 79 | async def find_files(self, user_id, range): 80 | user_files=self.file.find({"user_id": user_id}) 81 | user_files.skip(range[0] - 1) 82 | user_files.limit(range[1] - range[0] + 1) 83 | user_files.sort('_id', pymongo.DESCENDING) 84 | total_files = await self.file.count_documents({"user_id": user_id}) 85 | return user_files, total_files 86 | 87 | async def get_file(self, _id): 88 | try: 89 | file_info=await self.file.find_one({"_id": ObjectId(_id)}) 90 | if not file_info: 91 | raise FIleNotFound 92 | return file_info 93 | except InvalidId: 94 | raise FIleNotFound 95 | 96 | async def get_file_by_fileuniqueid(self, id, file_unique_id, many=False): 97 | if many: 98 | return self.file.find({"file_unique_id": file_unique_id}) 99 | else: 100 | file_info=await self.file.find_one({"user_id": id, "file_unique_id": file_unique_id}) 101 | if file_info: 102 | return file_info 103 | return False 104 | 105 | # ---------------------[ TOTAL FILES ]---------------------# 106 | async def total_files(self, id=None): 107 | if id: 108 | return await self.file.count_documents({"user_id": id}) 109 | return await self.file.count_documents({}) 110 | 111 | # ---------------------[ DELETE FILES ]---------------------# 112 | async def delete_one_file(self, _id): 113 | await self.file.delete_one({'_id': ObjectId(_id)}) 114 | 115 | # ---------------------[ UPDATE FILES ]---------------------# 116 | async def update_file_ids(self, _id, file_ids: dict): 117 | await self.file.update_one({"_id": ObjectId(_id)}, {"$set": {"file_ids": file_ids}}) 118 | 119 | # ---------------------[ PAID SYS ]---------------------# 120 | # async def link_available(self, id): 121 | # user = await self.col.find_one({"id": id}) 122 | # if user.get("Plan") == "Plus": 123 | # return "Plus" 124 | # elif user.get("Plan") == "Free": 125 | # files = await self.file.count_documents({"user_id": id}) 126 | # if files < 11: 127 | # return True 128 | # return False 129 | 130 | async def count_links(self, id, operation: str): 131 | if operation == "-": 132 | await self.col.update_one({"id": id}, {"$inc": {"Links": -1}}) 133 | elif operation == "+": 134 | await self.col.update_one({"id": id}, {"$inc": {"Links": 1}}) -------------------------------------------------------------------------------- /FileStream/utils/file_properties.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | import logging 3 | from datetime import datetime 4 | from pyrogram import Client 5 | from typing import Any, Optional 6 | 7 | from pyrogram.enums import ParseMode, ChatType 8 | from pyrogram.types import Message 9 | from pyrogram.file_id import FileId 10 | from FileStream.bot import FileStream 11 | from FileStream.utils.database import Database 12 | from FileStream.config import Telegram, Server 13 | 14 | db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME) 15 | 16 | 17 | async def get_file_ids(client: Client | bool, db_id: str, multi_clients, message) -> Optional[FileId]: 18 | logging.debug("Starting of get_file_ids") 19 | file_info = await db.get_file(db_id) 20 | if (not "file_ids" in file_info) or not client: 21 | logging.debug("Storing file_id of all clients in DB") 22 | log_msg = await send_file(FileStream, db_id, file_info['file_id'], message) 23 | await db.update_file_ids(db_id, await update_file_id(log_msg.id, multi_clients)) 24 | logging.debug("Stored file_id of all clients in DB") 25 | if not client: 26 | return 27 | file_info = await db.get_file(db_id) 28 | 29 | file_id_info = file_info.setdefault("file_ids", {}) 30 | if not str(client.id) in file_id_info: 31 | logging.debug("Storing file_id in DB") 32 | log_msg = await send_file(FileStream, db_id, file_info['file_id'], message) 33 | msg = await client.get_messages(Telegram.FLOG_CHANNEL, log_msg.id) 34 | media = get_media_from_message(msg) 35 | file_id_info[str(client.id)] = getattr(media, "file_id", "") 36 | await db.update_file_ids(db_id, file_id_info) 37 | logging.debug("Stored file_id in DB") 38 | 39 | logging.debug("Middle of get_file_ids") 40 | file_id = FileId.decode(file_id_info[str(client.id)]) 41 | setattr(file_id, "file_size", file_info['file_size']) 42 | setattr(file_id, "mime_type", file_info['mime_type']) 43 | setattr(file_id, "file_name", file_info['file_name']) 44 | setattr(file_id, "unique_id", file_info['file_unique_id']) 45 | logging.debug("Ending of get_file_ids") 46 | return file_id 47 | 48 | 49 | def get_media_from_message(message: "Message") -> Any: 50 | media_types = ( 51 | "audio", 52 | "document", 53 | "photo", 54 | "sticker", 55 | "animation", 56 | "video", 57 | "voice", 58 | "video_note", 59 | ) 60 | for attr in media_types: 61 | media = getattr(message, attr, None) 62 | if media: 63 | return media 64 | 65 | 66 | def get_media_file_size(m): 67 | media = get_media_from_message(m) 68 | return getattr(media, "file_size", "None") 69 | 70 | 71 | def get_name(media_msg: Message | FileId) -> str: 72 | if isinstance(media_msg, Message): 73 | media = get_media_from_message(media_msg) 74 | file_name = getattr(media, "file_name", "") 75 | 76 | elif isinstance(media_msg, FileId): 77 | file_name = getattr(media_msg, "file_name", "") 78 | 79 | if not file_name: 80 | if isinstance(media_msg, Message) and media_msg.media: 81 | media_type = media_msg.media.value 82 | elif media_msg.file_type: 83 | media_type = media_msg.file_type.name.lower() 84 | else: 85 | media_type = "file" 86 | 87 | formats = { 88 | "photo": "jpg", "audio": "mp3", "voice": "ogg", 89 | "video": "mp4", "animation": "mp4", "video_note": "mp4", 90 | "sticker": "webp" 91 | } 92 | 93 | ext = formats.get(media_type) 94 | ext = "." + ext if ext else "" 95 | 96 | date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") 97 | file_name = f"{media_type}-{date}{ext}" 98 | 99 | return file_name 100 | 101 | 102 | def get_file_info(message): 103 | media = get_media_from_message(message) 104 | if message.chat.type == ChatType.PRIVATE: 105 | user_idx = message.from_user.id 106 | else: 107 | user_idx = message.chat.id 108 | return { 109 | "user_id": user_idx, 110 | "file_id": getattr(media, "file_id", ""), 111 | "file_unique_id": getattr(media, "file_unique_id", ""), 112 | "file_name": get_name(message), 113 | "file_size": getattr(media, "file_size", 0), 114 | "mime_type": getattr(media, "mime_type", "None/unknown") 115 | } 116 | 117 | 118 | async def update_file_id(msg_id, multi_clients): 119 | file_ids = {} 120 | for client_id, client in multi_clients.items(): 121 | log_msg = await client.get_messages(Telegram.FLOG_CHANNEL, msg_id) 122 | media = get_media_from_message(log_msg) 123 | file_ids[str(client.id)] = getattr(media, "file_id", "") 124 | 125 | return file_ids 126 | 127 | 128 | async def send_file(client: Client, db_id, file_id: str, message): 129 | file_caption = getattr(message, 'caption', None) or get_name(message) 130 | log_msg = await client.send_cached_media(chat_id=Telegram.FLOG_CHANNEL, file_id=file_id, 131 | caption=f'**{file_caption}**') 132 | 133 | if message.chat.type == ChatType.PRIVATE: 134 | await log_msg.reply_text( 135 | text=f"**RᴇQᴜᴇꜱᴛᴇᴅ ʙʏ :** [{message.from_user.first_name}](tg://user?id={message.from_user.id})\n**Uꜱᴇʀ ɪᴅ :** `{message.from_user.id}`\n**Fɪʟᴇ ɪᴅ :** `{db_id}`", 136 | disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN, quote=True) 137 | else: 138 | await log_msg.reply_text( 139 | text=f"**RᴇQᴜᴇꜱᴛᴇᴅ ʙʏ :** {message.chat.title} \n**Cʜᴀɴɴᴇʟ ɪᴅ :** `{message.chat.id}`\n**Fɪʟᴇ ɪᴅ :** `{db_id}`", 140 | disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN, quote=True) 141 | 142 | return log_msg 143 | # return await client.send_cached_media(Telegram.BIN_CHANNEL, file_id) 144 | 145 | -------------------------------------------------------------------------------- /FileStream/utils/human_readable.py: -------------------------------------------------------------------------------- 1 | def humanbytes(size): 2 | if not size: 3 | return "" 4 | power = 2**10 5 | n = 0 6 | Dic_powerN = {0: ' ', 1: 'Ki', 2: 'Mi', 3: 'Gi', 4: 'Ti'} 7 | while size > power: 8 | size /= power 9 | n += 1 10 | return str(round(size, 2)) + " " + Dic_powerN[n] + 'B' 11 | -------------------------------------------------------------------------------- /FileStream/utils/render_template.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | import jinja2 3 | import urllib.parse 4 | from FileStream.config import Telegram, Server 5 | from FileStream.utils.database import Database 6 | from FileStream.utils.human_readable import humanbytes 7 | db = Database(Telegram.DATABASE_URL, Telegram.SESSION_NAME) 8 | 9 | async def render_page(db_id): 10 | file_data=await db.get_file(db_id) 11 | src = urllib.parse.urljoin(Server.URL, f'dl/{file_data["_id"]}') 12 | file_size = humanbytes(file_data['file_size']) 13 | file_name = file_data['file_name'].replace("_", " ") 14 | 15 | if str((file_data['mime_type']).split('/')[0].strip()) == 'video': 16 | template_file = "FileStream/template/play.html" 17 | else: 18 | template_file = "FileStream/template/dl.html" 19 | async with aiohttp.ClientSession() as s: 20 | async with s.get(src) as u: 21 | file_size = humanbytes(int(u.headers.get('Content-Length'))) 22 | 23 | with open(template_file) as f: 24 | template = jinja2.Template(f.read()) 25 | 26 | return template.render( 27 | file_name=file_name, 28 | file_url=src, 29 | file_size=file_size 30 | ) 31 | -------------------------------------------------------------------------------- /FileStream/utils/time_format.py: -------------------------------------------------------------------------------- 1 | def get_readable_time(seconds: int) -> str: 2 | count = 0 3 | readable_time = "" 4 | time_list = [] 5 | time_suffix_list = ["s", "m", "h", " days"] 6 | while count < 4: 7 | count += 1 8 | if count < 3: 9 | remainder, result = divmod(seconds, 60) 10 | else: 11 | remainder, result = divmod(seconds, 24) 12 | if seconds == 0 and remainder == 0: 13 | break 14 | time_list.append(int(result)) 15 | seconds = int(remainder) 16 | for x in range(len(time_list)): 17 | time_list[x] = str(time_list[x]) + time_suffix_list[x] 18 | if len(time_list) == 4: 19 | readable_time += time_list.pop() + ", " 20 | time_list.reverse() 21 | readable_time += ": ".join(time_list) 22 | return readable_time 23 | -------------------------------------------------------------------------------- /FileStream/utils/translation.py: -------------------------------------------------------------------------------- 1 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton 2 | from FileStream.config import Telegram 3 | 4 | class LANG(object): 5 | 6 | START_TEXT = """ 7 | 👋 Hᴇʏ, {}\n 8 | I'ᴍ ᴛᴇʟᴇɢʀᴀᴍ ғɪʟᴇs sᴛʀᴇᴀᴍɪɴɢ ʙᴏᴛ ᴀs ᴡᴇʟʟ ᴅɪʀᴇᴄᴛ ʟɪɴᴋs ɢᴇɴᴇʀᴀᴛᴏʀ\n 9 | ᴡᴏʀᴋɪɴɢ ᴏɴ ᴄʜᴀɴɴᴇʟs ᴀɴᴅ ᴘʀɪᴠᴀᴛᴇ ᴄʜᴀᴛ\n 10 | 💕 @{}\n""" 11 | 12 | HELP_TEXT = """ 13 | - ᴀᴅᴅ ᴍᴇ ᴀs ᴀɴ ᴀᴅᴍɪɴ ᴏɴ ᴛʜᴇ ᴄʜᴀɴɴᴇʟ 14 | - sᴇɴᴅ ᴍᴇ ᴀɴʏ ᴅᴏᴄᴜᴍᴇɴᴛ ᴏʀ ᴍᴇᴅɪᴀ 15 | - ɪ'ʟʟ ᴘʀᴏᴠɪᴅᴇ sᴛʀᴇᴀᴍᴀʙʟᴇ ʟɪɴᴋ\n 16 | 🔞 ᴀᴅᴜʟᴛ ᴄᴏɴᴛᴇɴᴛ sᴛʀɪᴄᴛʟʏ ᴘʀᴏʜɪʙɪᴛᴇᴅ.\n 17 | ʀᴇᴘᴏʀᴛ ʙᴜɢs ᴛᴏ ᴅᴇᴠᴇʟᴏᴘᴇʀ""" 18 | 19 | ABOUT_TEXT = """ 20 | ⚜ ᴍʏ ɴᴀᴍᴇ : {}\n 21 | ✦ ᴠᴇʀsɪᴏɴ : {} 22 | ✦ ᴜᴘᴅᴀᴛᴇᴅ ᴏɴ : 06-January-2024 23 | ✦ ᴅᴇᴠᴇʟᴏᴘᴇʀ : Avishkar Patil\n 24 | """ 25 | 26 | STREAM_TEXT = """ 27 | 𝗬𝗼𝘂𝗿 𝗟𝗶𝗻𝗸 𝗚𝗲𝗻𝗲𝗿𝗮𝘁𝗲𝗱 !\n 28 | 📂 Fɪʟᴇ ɴᴀᴍᴇ : {}\n 29 | 📦 Fɪʟᴇ ꜱɪᴢᴇ : {}\n 30 | 📥 Dᴏᴡɴʟᴏᴀᴅ : {}\n 31 | 🖥 Wᴀᴛᴄʜ : {}\n 32 | 🔗 Sʜᴀʀᴇ : {}\n""" 33 | 34 | STREAM_TEXT_X = """ 35 | 𝗬𝗼𝘂𝗿 𝗟𝗶𝗻𝗸 𝗚𝗲𝗻𝗲𝗿𝗮𝘁𝗲𝗱 !\n 36 | 📂 Fɪʟᴇ ɴᴀᴍᴇ : {}\n 37 | 📦 Fɪʟᴇ ꜱɪᴢᴇ : {}\n 38 | 📥 Dᴏᴡɴʟᴏᴀᴅ : {}\n 39 | 🔗 Sʜᴀʀᴇ : {}\n""" 40 | 41 | 42 | BAN_TEXT = "__Sᴏʀʀʏ Sɪʀ, Yᴏᴜ ᴀʀᴇ Bᴀɴɴᴇᴅ ᴛᴏ ᴜsᴇ ᴍᴇ.__\n\n**[Cᴏɴᴛᴀᴄᴛ Dᴇᴠᴇʟᴏᴘᴇʀ](tg://user?id={}) Tʜᴇʏ Wɪʟʟ Hᴇʟᴘ Yᴏᴜ**" 43 | 44 | 45 | class BUTTON(object): 46 | START_BUTTONS = InlineKeyboardMarkup( 47 | [[ 48 | InlineKeyboardButton('ʜᴇʟᴘ', callback_data='help'), 49 | InlineKeyboardButton('ᴀʙᴏᴜᴛ', callback_data='about'), 50 | InlineKeyboardButton('ᴄʟᴏsᴇ', callback_data='close') 51 | ], 52 | [InlineKeyboardButton("📢 ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ", url=f'https://t.me/{Telegram.UPDATES_CHANNEL}')] 53 | ] 54 | ) 55 | HELP_BUTTONS = InlineKeyboardMarkup( 56 | [[ 57 | InlineKeyboardButton('ʜᴏᴍᴇ', callback_data='home'), 58 | InlineKeyboardButton('ᴀʙᴏᴜᴛ', callback_data='about'), 59 | InlineKeyboardButton('ᴄʟᴏsᴇ', callback_data='close'), 60 | ], 61 | [InlineKeyboardButton("📢 ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ", url=f'https://t.me/{Telegram.UPDATES_CHANNEL}')] 62 | ] 63 | ) 64 | ABOUT_BUTTONS = InlineKeyboardMarkup( 65 | [[ 66 | InlineKeyboardButton('ʜᴏᴍᴇ', callback_data='home'), 67 | InlineKeyboardButton('ʜᴇʟᴘ', callback_data='help'), 68 | InlineKeyboardButton('ᴄʟᴏsᴇ', callback_data='close'), 69 | ], 70 | [InlineKeyboardButton("📢 ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ", url=f'https://t.me/{Telegram.UPDATES_CHANNEL}')] 71 | ] 72 | ) 73 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: python -m FileStream -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

FileStreamBot

2 |

3 | 4 | Cover Image 5 | 6 |

7 |

8 | 9 |
10 | Report a Bug 11 | | 12 | Request Feature 13 |

14 | 15 | 16 | 17 | ### 🍁 About : 18 | 19 |

20 | 21 | FileStreamBot Logo 22 | 23 |

24 |

25 | This bot provides stream links for Telegram files without the necessity of waiting for the download to complete, offering the ability to store files. 26 |

27 | 28 | 29 | ### ♢ How to Deploy : 30 | 31 | Either you could locally host, VPS, or deploy on [Heroku](https://heroku.com) 32 | 33 | #### ♢ Click on This Drop-down and get more details 34 | 35 |
36 |
37 | Deploy on Heroku (Paid) : 38 | 39 | - Fork This Repo 40 | - Click on Deploy Easily 41 | - Press the below button to Fast deploy on Heroku 42 | 43 | 44 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 45 | - Go to variables tab for more info on setting up environmental variables.
46 | 47 |
48 | Deploy Locally : 49 |
50 | 51 | ```sh 52 | git clone https://github.com/avipatilpro/FileStreamBot 53 | cd FileStreamBot 54 | python3 -m venv ./venv 55 | . ./venv/bin/activate 56 | pip install -r requirements.txt 57 | python3 -m FileStream 58 | ``` 59 | 60 | - To stop the whole bot, 61 | do CTRL+C 62 | 63 | - If you want to run this bot 24/7 on the VPS, follow these steps. 64 | ```sh 65 | sudo apt install tmux -y 66 | tmux 67 | python3 -m FileStream 68 | ``` 69 | - now you can close the VPS and the bot will run on it. 70 | 71 |
72 | 73 |
74 | Deploy using Docker : 75 |
76 | * Clone the repository: 77 | ```sh 78 | git clone https://github.com/avipatilpro/FileStreamBot 79 | cd FileStreamBot 80 | ``` 81 | * Build own Docker image: 82 | ```sh 83 | docker build -t file-stream . 84 | ``` 85 | 86 | * Create ENV and Start Container: 87 | ```sh 88 | docker run -d --restart unless-stopped --name fsb \ 89 | -v /PATH/TO/.env:/app/.env \ 90 | -p 8000:8000 \ 91 | file-stream 92 | ``` 93 | - if you need to change the variables in .env file after your bot was already started, all you need to do is restart the container for the bot settings to get updated: 94 | ```sh 95 | docker restart fsb 96 | ``` 97 | 98 |
99 | 100 |
101 | Setting up things : 102 | 103 | 104 | If you're on Heroku, just add these in the Environmental Variables 105 | or if you're Locally hosting, create a file named `.env` in the root directory and add all the variables there. 106 | An example of `.env` file: 107 | 108 | ```sh 109 | API_ID = 789456 110 | API_HASH = ysx275f9638x896g43sfzx65 111 | BOT_TOKEN = 12345678:your_bot_token 112 | ULOG_CHANNEL = -100123456789 113 | FLOG_CHANNEL = -100123456789 114 | DATABASE_URL = mongodb://admin:pass@192.168.27.1 115 | FQDN = 192.168.27.1 116 | HAS_SSL = False 117 | MULTI_TOKEN1 = 12345678:bot_token_multi_client_1 118 | MULTI_TOKEN2 = 12345678:bot_token_multi_client_2 119 | OWNER_ID = 987456321 120 | PORT = 8080 121 | ``` 122 |
123 | 124 | 125 |
126 | Vars and Details : 127 | 128 | #### 📝 Mandatory Vars : 129 | 130 | * `API_ID`: API ID of your Telegram account, can be obtained from [My Telegram](https://my.telegram.org). `int` 131 | * `API_HASH`: API hash of your Telegram account, can be obtained from [My Telegram](https://my.telegram.org). `str` 132 | * `OWNER_ID`: Your Telegram User ID, Send `/id` to [@missrose_bot](https://telegram.dog/MissRose_bot) to get Your Telegram User ID `int` 133 | * `BOT_TOKEN`: Telegram API token of your bot, can be obtained from [@BotFather](https://t.me/BotFather). `str` 134 | * `FLOG_CHANNEL`: ID of the channel where bot will store all Files from users `int`. 135 | * `ULOG_CHANNEL`: ID of the channel where bot will send logs of New Users`int`. 136 | * `BOT_WORKERS`: Number of updates bot should process from Telegram at once, by default to 10 updates. `int` 137 | * `DATABASE_URL`: MongoDB URI for saving User Data and Files List created by user. `str` 138 | * `FQDN`: A Fully Qualified Domain Name if present without http/s. Defaults to `BIND_ADDRESS`. `str` 139 | 140 | #### 🗼 MultiClient Vars : 141 | * `MULTI_TOKEN1`: Add your first bot token or session strings here. `str` 142 | * `MULTI_TOKEN2`: Add your second bot token or session strings here. `str` 143 | 144 | #### 🪐 Optional Vars : 145 | 146 | * `UPDATES_CHANNEL`: Channel Username without `@` to set channel as Update Channel `str` 147 | * `FORCE_SUB_ID`: Force Sub Channel ID, if you want to use Force Sub. start with `-100` `int 148 | * `FORCE_SUB`: Set to True, so every user have to Join update channel to use the bot. `bool` 149 | * `AUTH_USERS`: Put authorized user IDs to use bot, separated by Space. `int` 150 | * `SLEEP_THRESHOLD`: Set global flood wait threshold, auto-retry requests under 60s. `int` 151 | * `SESSION_NAME`: Name for the Database created on your MongoDB. Defaults to `FileStream`. `str` 152 | * `FILE_PIC`: To set Image at `/files` command. Defaults to pre-set image. `str` 153 | * `START_PIC`: To set Image at `/start` command. Defaults to pre-set image. `str` 154 | * `VERIFY_PIC`: To set Image at Force Sub Verification. Defaults to pre-set image. `str` 155 | * `WORKERS`: Number of maximum concurrent workers for handling incoming updates. Defaults to `6`. `int` 156 | * `PORT`: The port that you want your webapp to be listened to. Defaults to `8080`. `int` 157 | * `BIND_ADDRESS`: Your server bind adress. Defauls to `0.0.0.0`. `int` 158 | * `MODE`: Should be set to `secondary` if you only want to use the server for serving files. `str` 159 | * `NO_PORT`: (True/False) Set PORT to 80 or 443 hide port display; ignore if on Heroku. Defaults to `False`. 160 | * `HAS_SSL`: (can be either `True` or `False`) If you want the generated links in https format. Defaults to `False`. 161 | 162 |
163 | 164 |
165 | How to Use : 166 | 167 | :warning: **Before using the bot, don't forget to add the bot to the `LOG_CHANNEL` as an Admin** 168 | 169 | #### ‍☠️ Bot Commands : 170 | 171 | ```sh 172 | /start : To check the bot is alive or not. 173 | /help : To Get Help Message. 174 | /about : To check About the Bot. 175 | /files : To Get All Files List of User. 176 | /del : To Delete Files from DB with FileID. [ADMIN] 177 | /ban : To Ban Any Channel or User to use bot. [ADMIN] 178 | /unban : To Unban Any Channel or User to use bot. [ADMIN] 179 | /status : To Get Bot Status and Total Users. [ADMIN] 180 | /broadcast : To Broadcast any message to all users of bot. [ADMIN] 181 | ``` 182 | 183 | #### 🍟 Channel Support : 184 | 185 | *Bot also Supported with Channels. Just add bot Channel as Admin. If any new file comes in Channel it will edit it with **Get Download Link** Button.* 186 | 187 |
188 | 189 | ### ❤️ Thanks To : 190 | 191 | - [**Me**](https://github.com/AvishkarPatil) : Owner of This FileStreamBot 192 | - [**Deekshith SH**](https://github.com/DeekshithSH) : for some modules. 193 | - [**EverythingSuckz**](https://github.com/EverythingSuckz) : for his [FileStreamBot](https://github.com/EverythingSuckz/FileStreamBot) 194 | - [**Biisal**](https://github.com/biisal) : for Stream Page UI 195 | 196 | --- 197 |

© 2024 Aνιѕнкαя Pαтιℓ

198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FileStreamBot", 3 | "description": "A Telegram Bot to Convert Telegram Files To Direct & Streamable Links.", 4 | "keywords": [ 5 | "telegram", 6 | "files", 7 | "stream" 8 | ], 9 | "repository": "https://github.com/avipatilpro/FileStreamBot/", 10 | "success_url": "/", 11 | "logo": "https://i.ibb.co/ZJzJ9Hq/link-3x.png", 12 | "env": { 13 | "ENV": { 14 | "description": "Set this to True if you don't want to crash the bot", 15 | "value": "True" 16 | }, 17 | "API_ID": { 18 | "description": "Get this value from https://my.telegram.org" 19 | }, 20 | "API_HASH": { 21 | "description": "Get this value from https://my.telegram.org" 22 | }, 23 | "BOT_TOKEN": { 24 | "description": "Get this value from @BotFather" 25 | }, 26 | "FLOG_CHANNEL": { 27 | "description": "ID of Channel Where store files sent by users. Read the readme for more info about this var" 28 | }, 29 | "ULOG_CHANNEL": { 30 | "description": "ID of Channel Where store New Users data. Read the readme for more info about this var" 31 | }, 32 | "OWNER_ID": { 33 | "description": "Your Telegram User ID as Owner" 34 | }, 35 | "DATABASE_URL": { 36 | "description": "MongoDB URI for saving User Data and Files List created by user." 37 | }, 38 | "AUTH_USERS": { 39 | "description": "Put IDs of Auth Usrs where bot will work. You can add multiple IDs & separate with Space.", 40 | "required": false 41 | }, 42 | "UPDATES_CHANNEL": { 43 | "description": "Channel Username without `@` to set channel as Update Channel", 44 | "required": false 45 | }, 46 | "FORCE_SUB_ID": { 47 | "description": "Channel ID starts with -100 to set channel as Force Sub Channel", 48 | "required": false 49 | }, 50 | "FORCE_SUB": { 51 | "description": "Set to True, so every user have to Join update channel to use the bot.", 52 | "required": false 53 | }, 54 | "SLEEP_THRESHOLD": { 55 | "description": "Set global flood wait threshold, auto-retry requests under 60s. ", 56 | "required": false 57 | }, 58 | "WORKERS": { 59 | "description": "No. of workers that is to be assigned.", 60 | "required": false 61 | }, 62 | "PORT": { 63 | "description": "The port that you want your webapp to be listened to", 64 | "required": false 65 | }, 66 | "NO_PORT": { 67 | "description": "If you don't want your port to be displayed. Set True or False", 68 | "value": "True", 69 | "required": false 70 | }, 71 | "HAS_SSL": { 72 | "description": "(can be either True or False) If you want the generated links in https format.", 73 | "value": "True", 74 | "required": false 75 | }, 76 | "BIND_ADDRESS": { 77 | "description": "Your server bind address. default to 0.0.0.0", 78 | "required": false 79 | }, 80 | "FQDN": { 81 | "description": "Heroku app Link without http/s://, you can set later. its required", 82 | "required": false 83 | }, 84 | "SESSION_NAME": { 85 | "description": "Name for the Database created on your MongoDB. Defaults to FileStream", 86 | "required": false 87 | }, 88 | "FILE_PIC": { 89 | "description": "To set Image at /files command. Defaults to pre-set image.", 90 | "required": false 91 | }, 92 | "START_PIC": { 93 | "description": "To set Image at /start command. Defaults to pre-set image.", 94 | "required": false 95 | }, 96 | "VERIFY_PIC": { 97 | "description": "To set Image at Force Subscribe Verify. Defaults to pre-set image.", 98 | "required": false 99 | }, 100 | "MODE": { 101 | "description": "Should be set to `secondary` if you only want to use the server for serving files.", 102 | "required": false 103 | } 104 | }, 105 | "buildpacks": [ 106 | { 107 | "url": "heroku/python" 108 | } 109 | ], 110 | "stack": "heroku-22", 111 | "formation": { 112 | "web": { 113 | "quantity": 1, 114 | "size": "eco" 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | pyrofork 3 | python-dotenv 4 | tgcrypto 5 | motor 6 | aiofiles 7 | dnspython 8 | requests 9 | jinja2 10 | --------------------------------------------------------------------------------