├── .changelog ├── .gitattributes ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── WebStreamer ├── __init__.py ├── __main__.py ├── bot │ ├── __init__.py │ ├── clients.py │ └── plugins │ │ ├── admin.py │ │ ├── callback.py │ │ ├── start.py │ │ └── stream.py ├── server │ ├── __init__.py │ ├── exceptions.py │ └── stream_routes.py ├── template │ ├── dl.html │ └── req.html ├── utils │ ├── Translation.py │ ├── __init__.py │ ├── broadcast_helper.py │ ├── custom_dl.py │ ├── database.py │ ├── file_properties.py │ ├── human_readable.py │ ├── keepalive.py │ ├── render_template.py │ └── time_format.py └── vars.py └── requirements.txt /.changelog: -------------------------------------------------------------------------------- 1 | - Fixed Bot not working in Channel 2 | - Removed files related to Heroku 3 | - Switched to Rotating Logs 4 | 5 | Updated Changes Made in EverythingSuckz's repo 6 | - README: update VPS deployment commands 7 | - Fix stream seeking 8 | - Fix bugs in stream seeking 9 | - Fix last_part_cut 10 | - REFACTOR: token parser 11 | - chore: remove heroku stuffs 12 | - Fix for files without filename 13 | - Fixed download with weird file name 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 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 | test2.py 123 | 124 | streambot.log.* -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "VSCord.enabled": true, 3 | "cSpell.words": [ 4 | "DYNO" 5 | ] 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Telegram File Stream Bot

2 |

3 | 4 | FileStreamBot 5 | 6 |

7 | A Telegram bot to stream files to web
8 | Demo Bot (Not Available)» 9 |
10 | Report a Bug 11 | | 12 | Request Feature 13 |

14 |

15 | 16 |
17 | 18 |
19 | Table of Contents 20 |
    21 |
  1. 22 | About this Bot 23 | 26 |
  2. 27 |
  3. 28 | How to make your own 29 | 32 |
  4. 33 |
  5. Setting up things
  6. 34 | 38 |
  7. How to use the bot
  8. 39 |
  9. Contributing
  10. 40 |
  11. Contact me
  12. 41 |
  13. Credits
  14. 42 |
43 |
44 | 45 | ## About This Bot 46 | 47 |

48 | 49 | Telegram Logo 50 | 51 |

52 |

53 | This bot will give you stream links for Telegram files without the need of waiting till the download completes 54 |

55 | 56 | ### Original Repository 57 | 58 | The main working part was taken from [Megatron](https://github.com/eyaadh/megadlbot_oss) and thanks to [eyaadh](https://github.com/eyaadh) for his awesome project. 59 | 60 | ## How to make your own 61 | 62 | 63 | 64 | ### Host it on VPS or Locally 65 | 66 | ```sh 67 | git clone https://github.com/DeekshithSH/FileStreamBot 68 | cd FileStreamBot 69 | python3 -m venv ./venv 70 | . ./venv/bin/activate 71 | pip3 install -r requirements.txt 72 | python3 -m WebStreamer 73 | ``` 74 | 75 | and to stop the whole bot, 76 | do CTRL+C 77 | 78 | - **If you wanna run this bot 24/7 on the VPS, follow these steps.** 79 | ```sh 80 | sudo apt install tmux -y 81 | tmux 82 | python3 -m WebStreamer 83 | ``` 84 | 85 | now you can close the VPS and the bot will run on it. 86 | 87 | ## Setting up things 88 | 89 | If you're on Heroku, just add these in the Environmental Variables 90 | or if you're Locally hosting, create a file named `.env` in the root directory and add all the variables there. 91 | An example of `.env` file: 92 | 93 | ```sh 94 | API_ID=452525 95 | API_HASH=esx576f8738x883f3sfzx83 96 | BOT_TOKEN=55838383:yourtbottokenhere 97 | BIN_CHANNEL=-100 98 | DATABASE_URL=mongodb://admin:pAswaRd@192.168.27.1 99 | FQDN=192.168.27.1 100 | HAS_SSL=False 101 | MULTI_TOKEN1=55838383:yourfirstmulticlientbottokenhere 102 | MULTI_TOKEN2=55838383:yoursecondmulticlientbottokenhere 103 | MULTI_TOKEN3=55838383:yourthirdmulticlientbottokenhere 104 | OWNER_ID=777000 105 | PORT=8080 106 | ``` 107 | 108 | ### Mandatory Vars 109 | 110 | `API_ID` : Goto [my.telegram.org](https://my.telegram.org) to obtain this. 111 | 112 | `API_HASH` : Goto [my.telegram.org](https://my.telegram.org) to obtain this. 113 | 114 | `BOT_TOKEN` : Get the bot token from [@BotFather](https://telegram.dog/BotFather) 115 | 116 | `BIN_CHANNEL` : Create a new channel (private/public), post something in your channel. Forward that post to [@missrose_bot](https://telegram.dog/MissRose_bot) and **reply** `/id`. Now copy paste the forwarded channel ID in this field. 117 | 118 | `OWNER_ID` : Your Telegram User ID, Send `/id` to [@missrose_bot](https://telegram.dog/MissRose_bot) to get Your Telegram User ID 119 | 120 | `DATABASE_URL` : MongoDB URI for saving User IDs when they first Start the Bot. We will use that for Broadcasting to them. I will try to add more features related with Database. If you need help to get the URI you can ask in [Me Telegram](https://t.me/Avishkarpatil). 121 | 122 | ### For MultiClient 123 | 124 | `MULTI_TOKEN1`: Add your first bot token or session strings here. 125 | 126 | `MULTI_TOKEN2`: Add your second bot token or session strings here. 127 | 128 | you may also add as many as bots you want. (max limit is not tested yet) 129 | `MULTI_TOKEN3`, `MULTI_TOKEN4`, etc. 130 | 131 | 132 | 133 | ### Optional Vars 134 | 135 | `SLEEP_THRESHOLD` : Set a sleep threshold for flood wait exceptions happening globally in this telegram bot instance, below which any request that raises a flood wait will be automatically invoked again after sleeping for the required amount of time. Flood wait exceptions requiring higher waiting times will be raised. Defaults to 60 seconds. 136 | 137 | `WORKERS` : Number of maximum concurrent workers for handling incoming updates. Defaults to `3` 138 | 139 | `PORT` : The port that you want your webapp to be listened to. Defaults to `8080` 140 | 141 | `WEB_SERVER_BIND_ADDRESS` : Your server bind address. Defauls to `0.0.0.0` 142 | 143 | `NO_PORT` : (can be either `True` or `False`) If you don't want your port to be displayed. You should point your `PORT` to `80` (http) or `443` (https) for the links to work. Ignore this if you're on Heroku. 144 | 145 | `FQDN` : A Fully Qualified Domain Name if present. Defaults to `WEB_SERVER_BIND_ADDRESS` 146 | 147 | `HAS_SSL` : (can be either `True` or `False`) If you want the generated links in https format. 148 | 149 | `PING_INTERVAL` : The time in ms you want the servers to be pinged each time to avoid sleeping (Only for Heroku). Defaults to `1200` or 20 minutes. 150 | 151 | `UPDATES_CHANNEL` : Your Telegram Channel 152 | 153 | `FORCE_UPDATES_CHANNEL` : Set to True, so every user have to Join update channel to use the bot. 154 | 155 | `SESSION_NAME` : Name for the Database created on your MongoDB. Defaults to `F2LxBot` 156 | 157 | `BANNED_CHANNELS` : Put IDs of Banned Channels where bot will not work. You can add multiple IDs & separate with Space. 158 | 159 | `KEEP_ALIVE` : If you want to make the server ping itself every `PING_INTERVAL` seconds to avoid sleeping. Helpful in PaaS Free tiers. Defaults to `False` 160 | 161 | ## How to use the bot 162 | 163 | :warning: **Before using the bot, don't forget to add all the bots (multi-client ones too) to the `BIN_CHANNEL` as an admin** 164 | 165 | `/start` : To check if the bot is alive or not. 166 | 167 | To get an instant stream link, just forward any media to the bot and boom, its fast af. 168 | 169 | ## faQ 170 | 171 | - How long the links will remain valid or is there any expiration time for the links generated by the bot? 172 | > The links will will be valid as longs as your bot is alive and you haven't deleted the log channel. 173 | 174 | ## Contributing 175 | 176 | Feel free to contribute to this project if you have any further ideas 177 | 178 | ## Contact me 179 | 180 | [![Telegram Channel](https://img.shields.io/static/v1?label=Join&message=Telegram%20Channel&color=blueviolet&style=for-the-badge&logo=telegram&logoColor=violet)](https://xn--r1a.click/AWeirdText) 181 | [![Telegram Group](https://img.shields.io/static/v1?label=Join&message=Telegram%20Group&color=blueviolet&style=for-the-badge&logo=telegram&logoColor=violet)](https://xn--r1a.click/AWeirdString) 182 | 183 | You can contact either via my [Telegram Group](https://xn--r1a.click/AWeirdString) ~~or you can PM me on [@DeekshithSH](https://xn--r1a.click/DeekshithSH)~~ 184 | 185 | 186 | ## Credits 187 | 188 | - [Me](https://xn--r1a.click/DeekshithSH) 189 | - [EverythingSuckz](https://github.com/EverythingSuckz) for his [FileStreamBot](https://github.com/EverythingSuckz/FileStreamBot) 190 | - [Avishkar Patil](https://github.com/avipatilpro) for his [FileStreamBot](https://github.com/avipatilpro/FileStreamBot) 191 | - [eyaadh](https://github.com/eyaadh) for his awesome [Megatron Bot](https://github.com/eyaadh/megadlbot_oss). 192 | - [BlackStone](https://github.com/eyMarv) for adding multi-client support. 193 | - [Dan Tès](https://telegram.dog/haskell) for his [Pyrogram Library](https://github.com/pyrogram/pyrogram) 194 | - [TheHamkerCat](https://github.com/TheHamkerCat) for helping me with my common doubts. 195 | -------------------------------------------------------------------------------- /WebStreamer/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | 4 | import time 5 | from .vars import Var 6 | from WebStreamer.bot.clients import StreamBot 7 | 8 | __version__ = 2.21 9 | StartTime = time.time() 10 | -------------------------------------------------------------------------------- /WebStreamer/__main__.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | import sys 4 | import asyncio 5 | import logging 6 | import logging.handlers as handlers 7 | from .vars import Var 8 | from aiohttp import web 9 | from pyrogram import idle 10 | from WebStreamer import utils 11 | from WebStreamer import StreamBot 12 | from WebStreamer.server import web_server 13 | from WebStreamer.bot.clients import initialize_clients 14 | 15 | 16 | logging.basicConfig( 17 | level=logging.INFO, 18 | datefmt="%d/%m/%Y %H:%M:%S", 19 | format='[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s', 20 | handlers=[logging.StreamHandler(stream=sys.stdout), 21 | handlers.RotatingFileHandler("streambot.log", mode="a", maxBytes=104857600, backupCount=2, encoding="utf-8")],) 22 | 23 | logging.getLogger("aiohttp").setLevel(logging.ERROR) 24 | logging.getLogger("pyrogram").setLevel(logging.ERROR) 25 | logging.getLogger("aiohttp.web").setLevel(logging.ERROR) 26 | 27 | server = web.AppRunner(web_server()) 28 | 29 | #if sys.version_info[1] > 9: 30 | # loop = asyncio.new_event_loop() 31 | # asyncio.set_event_loop(loop) 32 | #else: 33 | loop = asyncio.get_event_loop() 34 | 35 | async def start_services(): 36 | print() 37 | print("-------------------- Initializing Telegram Bot --------------------") 38 | await StreamBot.start() 39 | bot_info = await StreamBot.get_me() 40 | StreamBot.username = bot_info.username 41 | print("------------------------------ DONE ------------------------------") 42 | print() 43 | print( 44 | "---------------------- Initializing Clients ----------------------" 45 | ) 46 | await initialize_clients() 47 | print("------------------------------ DONE ------------------------------") 48 | if Var.KEEP_ALIVE: 49 | print("------------------ Starting Keep Alive Service ------------------") 50 | print() 51 | asyncio.create_task(utils.ping_server()) 52 | print("--------------------- Initializing Web Server ---------------------") 53 | await server.setup() 54 | await web.TCPSite(server, Var.BIND_ADDRESS, Var.PORT).start() 55 | print("------------------------------ DONE ------------------------------") 56 | print() 57 | print("------------------------- Service Started -------------------------") 58 | print(" bot =>> {}".format(bot_info.first_name)) 59 | if bot_info.dc_id: 60 | print(" DC ID =>> {}".format(str(bot_info.dc_id))) 61 | print(" URL =>> {}".format(Var.URL)) 62 | print("------------------------------------------------------------------") 63 | await idle() 64 | 65 | async def cleanup(): 66 | await server.cleanup() 67 | await StreamBot.stop() 68 | 69 | if __name__ == "__main__": 70 | try: 71 | loop.run_until_complete(start_services()) 72 | except KeyboardInterrupt: 73 | pass 74 | except Exception as err: 75 | logging.error(err.with_traceback(None)) 76 | finally: 77 | loop.run_until_complete(cleanup()) 78 | loop.stop() 79 | print("------------------------ Stopped Services ------------------------") -------------------------------------------------------------------------------- /WebStreamer/bot/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | 4 | from ..vars import Var 5 | from pyrogram import Client 6 | from os import getcwd 7 | 8 | StreamBot = Client( 9 | name="WebStreamer", 10 | api_id=Var.API_ID, 11 | api_hash=Var.API_HASH, 12 | workdir="WebStreamer", 13 | plugins={"root": "WebStreamer/bot/plugins"}, 14 | bot_token=Var.BOT_TOKEN, 15 | sleep_threshold=Var.SLEEP_THRESHOLD, 16 | workers=Var.WORKERS, 17 | ) 18 | 19 | multi_clients = {} 20 | work_loads = {} 21 | -------------------------------------------------------------------------------- /WebStreamer/bot/clients.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | import asyncio 4 | import logging 5 | from os import environ 6 | from ..vars import Var 7 | from pyrogram import Client 8 | from . import multi_clients, work_loads, StreamBot 9 | 10 | 11 | async def initialize_clients(): 12 | multi_clients[0] = StreamBot 13 | work_loads[0] = 0 14 | all_tokens = dict( 15 | (c + 1, t) 16 | for c, (_, t) in enumerate( 17 | filter( 18 | lambda n: n[0].startswith("MULTI_TOKEN"), sorted(environ.items()) 19 | ) 20 | ) 21 | ) 22 | if not all_tokens: 23 | print("No additional clients found, using default client") 24 | return 25 | 26 | async def start_client(client_id, token): 27 | try: 28 | if len(token) >= 100: 29 | session_string=token 30 | bot_token=None 31 | print(f'Starting Client - {client_id} Using Session String') 32 | else: 33 | session_string=None 34 | bot_token=token 35 | print(f'Starting Client - {client_id} Using Bot Token') 36 | if client_id == len(all_tokens): 37 | await asyncio.sleep(2) 38 | print("This will take some time, please wait...") 39 | client = await Client( 40 | name=str(client_id), 41 | api_id=Var.API_ID, 42 | api_hash=Var.API_HASH, 43 | bot_token=bot_token, 44 | sleep_threshold=Var.SLEEP_THRESHOLD, 45 | no_updates=True, 46 | session_string=session_string, 47 | in_memory=True, 48 | ).start() 49 | work_loads[client_id] = 0 50 | return client_id, client 51 | except Exception: 52 | logging.error(f"Failed starting Client - {client_id} Error:", exc_info=True) 53 | 54 | clients = await asyncio.gather(*[start_client(i, token) for i, token in all_tokens.items()]) 55 | multi_clients.update(dict(clients)) 56 | if len(multi_clients) != 1: 57 | Var.MULTI_CLIENT = True 58 | print("Multi-Client Mode Enabled") 59 | else: 60 | print("No additional clients were initialized, using default client") 61 | -------------------------------------------------------------------------------- /WebStreamer/bot/plugins/admin.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | import os 4 | import time 5 | import string 6 | import random 7 | import asyncio 8 | import aiofiles 9 | import datetime 10 | from WebStreamer.utils.broadcast_helper import send_msg 11 | from WebStreamer.utils.database import Database 12 | from WebStreamer.bot import StreamBot 13 | from WebStreamer.vars import Var 14 | from pyrogram import filters, Client 15 | from pyrogram.types import Message 16 | from pyrogram.enums.parse_mode import ParseMode 17 | db = Database(Var.DATABASE_URL, Var.SESSION_NAME) 18 | broadcast_ids = {} 19 | 20 | 21 | @StreamBot.on_message(filters.command("status") & filters.private & filters.user(Var.OWNER_ID)) 22 | async def sts(c: Client, m: Message): 23 | total_users = await db.total_users_count() 24 | banned_users = await db.total_banned_users_count() 25 | await m.reply_text(text=f"**Total Users in DB:** `{total_users}` \n**Banned Users in DB:** `{banned_users}`", parse_mode=ParseMode.MARKDOWN, quote=True) 26 | 27 | @StreamBot.on_message(filters.command("ban") & filters.private & filters.user(Var.OWNER_ID)) 28 | async def sts(b, m: Message): 29 | id = m.text.split("/ban ")[-1] 30 | if not await db.is_user_banned(int(id)): 31 | await db.ban_user(int(id)) 32 | await db.delete_user(int(id)) 33 | if await db.is_user_banned(int(id)): 34 | await m.reply_text(text=f"`{id}`** is Banned** ", parse_mode=ParseMode.MARKDOWN, quote=True) 35 | await b.send_message( 36 | chat_id=id, 37 | text="**Your Banned to Use The Bot**", 38 | parse_mode=ParseMode.MARKDOWN, 39 | disable_web_page_preview=True 40 | ) 41 | else: 42 | await m.reply_text(text=f"**can't ban **`{id}`** something went wrong** ", parse_mode=ParseMode.MARKDOWN, quote=True) 43 | else: 44 | await m.reply_text(text=f"`{id}`** is Already Banned** ", parse_mode=ParseMode.MARKDOWN, quote=True) 45 | 46 | @StreamBot.on_message(filters.command("unban") & filters.private & filters.user(Var.OWNER_ID)) 47 | async def sts(b, m: Message): 48 | 49 | id = m.text.split("/unban ")[-1] 50 | if await db.is_user_banned(int(id)): 51 | await db.unban_user(int(id)) 52 | if not await db.is_user_banned(int(id)): 53 | await m.reply_text(text=f"`{id}`** is Unbanned** ", parse_mode=ParseMode.MARKDOWN, quote=True) 54 | await b.send_message( 55 | chat_id=id, 56 | text="**Your Unbanned now Use can use The Bot**", 57 | parse_mode=ParseMode.MARKDOWN, 58 | disable_web_page_preview=True 59 | ) 60 | else: 61 | await m.reply_text(text=f"**can't unban **`{id}`** something went wrong** ", parse_mode=ParseMode.MARKDOWN, quote=True) 62 | else: 63 | await m.reply_text(text=f"`{id}`** is not Banned** ", parse_mode=ParseMode.MARKDOWN, quote=True) 64 | 65 | @StreamBot.on_message(filters.command("broadcast") & filters.private & filters.user(Var.OWNER_ID) & filters.reply) 66 | async def broadcast_(c, m): 67 | all_users = await db.get_all_users() 68 | broadcast_msg = m.reply_to_message 69 | while True: 70 | broadcast_id = ''.join([random.choice(string.ascii_letters) for i in range(3)]) 71 | if not broadcast_ids.get(broadcast_id): 72 | break 73 | out = await m.reply_text( 74 | text=f"Broadcast initiated! You will be notified with log file when all the users are notified." 75 | ) 76 | start_time = time.time() 77 | total_users = await db.total_users_count() 78 | done = 0 79 | failed = 0 80 | success = 0 81 | broadcast_ids[broadcast_id] = dict( 82 | total=total_users, 83 | current=done, 84 | failed=failed, 85 | success=success 86 | ) 87 | async with aiofiles.open('broadcast.txt', 'w') as broadcast_log_file: 88 | async for user in all_users: 89 | sts, msg = await send_msg( 90 | user_id=int(user['id']), 91 | message=broadcast_msg 92 | ) 93 | if msg is not None: 94 | await broadcast_log_file.write(msg) 95 | if sts == 200: 96 | success += 1 97 | else: 98 | failed += 1 99 | if sts == 400: 100 | await db.delete_user(user['id']) 101 | done += 1 102 | if broadcast_ids.get(broadcast_id) is None: 103 | break 104 | else: 105 | broadcast_ids[broadcast_id].update( 106 | dict( 107 | current=done, 108 | failed=failed, 109 | success=success 110 | ) 111 | ) 112 | if broadcast_ids.get(broadcast_id): 113 | broadcast_ids.pop(broadcast_id) 114 | completed_in = datetime.timedelta(seconds=int(time.time() - start_time)) 115 | await asyncio.sleep(3) 116 | await out.delete() 117 | if failed == 0: 118 | await m.reply_text( 119 | text=f"broadcast completed in `{completed_in}`\n\nTotal users {total_users}.\nTotal done {done}, {success} success and {failed} failed.", 120 | quote=True 121 | ) 122 | else: 123 | await m.reply_document( 124 | document='broadcast.txt', 125 | caption=f"broadcast completed in `{completed_in}`\n\nTotal users {total_users}.\nTotal done {done}, {success} success and {failed} failed.", 126 | quote=True 127 | ) 128 | os.remove('broadcast.txt') 129 | -------------------------------------------------------------------------------- /WebStreamer/bot/plugins/callback.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | import random 4 | from WebStreamer.bot import StreamBot 5 | from WebStreamer.utils.file_properties import gen_link, get_media_file_unique_id 6 | from WebStreamer.vars import Var 7 | from WebStreamer.utils.Translation import Language, BUTTON 8 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery 9 | from pyrogram.errors import MessageDeleteForbidden 10 | from pyrogram.enums.parse_mode import ParseMode 11 | 12 | 13 | deldbtnmsg=["Your Already Deleted the Link", "You can't undo the Action", "You can Resend the File to Regenerate New Link", "Why Clicking me Your Link is Dead", "This is Just a Button Showing that Your Link is Deleted"] 14 | 15 | @StreamBot.on_callback_query() 16 | async def cb_data(bot, update: CallbackQuery): 17 | lang = Language(update) 18 | if update.data == "home": 19 | await update.message.edit_text( 20 | text=lang.START_TEXT.format(update.from_user.mention), 21 | disable_web_page_preview=True, 22 | reply_markup=BUTTON.START_BUTTONS 23 | ) 24 | elif update.data == "help": 25 | await update.message.edit_text( 26 | text=lang.HELP_TEXT.format(Var.UPDATES_CHANNEL), 27 | disable_web_page_preview=True, 28 | reply_markup=BUTTON.HELP_BUTTONS 29 | ) 30 | elif update.data == "about": 31 | await update.message.edit_text( 32 | text=lang.ABOUT_TEXT, 33 | disable_web_page_preview=True, 34 | reply_markup=BUTTON.ABOUT_BUTTONS 35 | ) 36 | elif update.data == "close": 37 | await update.message.delete() 38 | elif update.data == "msgdeleted": 39 | await update.answer(random.choice(deldbtnmsg), show_alert=True) 40 | else: 41 | usr_cmd = update.data.split("_") 42 | if usr_cmd[0] == "msgdelconf2": 43 | await update.message.edit_text( 44 | text=update.message.text, 45 | disable_web_page_preview=True, 46 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("✔️", callback_data=f"msgdelyes_{usr_cmd[1]}_{usr_cmd[2]}"), InlineKeyboardButton("✖️", callback_data=f"msgdelno_{usr_cmd[1]}_{usr_cmd[2]}")]]) 47 | ) 48 | elif usr_cmd[0] == "msgdelno": 49 | get_msg = await bot.get_messages(chat_id=Var.BIN_CHANNEL, message_ids=int(usr_cmd[1])) 50 | if get_media_file_unique_id(get_msg) == usr_cmd[2]: 51 | reply_markup, Stream_Text, stream_link = await gen_link(m=update, log_msg=get_msg, from_channel=False) 52 | 53 | await update.message.edit_text( 54 | text=Stream_Text, 55 | disable_web_page_preview=True, 56 | reply_markup=reply_markup 57 | ) 58 | elif resp.empty: 59 | await update.answer("Sorry Your File is Missing from the Server", show_alert=True) 60 | else: 61 | await update.answer("Message id and file_unique_id miss match", show_alert=True) 62 | elif usr_cmd[0] == "msgdelyes": 63 | try: 64 | resp = await bot.get_messages(Var.BIN_CHANNEL, int(usr_cmd[1])) 65 | if get_media_file_unique_id(resp) == usr_cmd[2]: 66 | await bot.delete_messages( 67 | chat_id=Var.BIN_CHANNEL, 68 | message_ids=int(usr_cmd[1]) 69 | ) 70 | await update.message.edit_text( 71 | text=update.message.text, 72 | disable_web_page_preview=True, 73 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("Link Deleted", callback_data="msgdeleted")]]) 74 | ) 75 | elif resp.empty: 76 | await update.answer("Sorry Your File is Missing from the Server", show_alert=True) 77 | else: 78 | await update.answer("Message id and file_unique_id miss match", show_alert=True) 79 | except MessageDeleteForbidden as e: 80 | print(e) 81 | await bot.send_message( 82 | chat_id=Var.BIN_CHANNEL, 83 | text=f"**#ᴇʀʀᴏʀ_ᴛʀᴀᴄᴇʙᴀᴄᴋ:** `{e}`\n#Delete_Link", disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN, 84 | ) 85 | await update.answer(text='message too old', show_alert=True) 86 | except Exception as e: 87 | print(e) 88 | error_id=await bot.send_message( 89 | chat_id=Var.BIN_CHANNEL, 90 | text=f"**#ᴇʀʀᴏʀ_ᴛʀᴀᴄᴇʙᴀᴄᴋ:** `{e}`\n#Delete_Link", disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN, 91 | ) 92 | await update.message.reply_text( 93 | text=f"**#ᴇʀʀᴏʀ_ᴛʀᴀᴄᴇʙᴀᴄᴋ:** `message-id={error_id.message_id}`\nYou can get Help from [Public Link Generator (Support)](https://t.me/{Var.UPDATES_CHANNEL})", disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN, 94 | ) 95 | else: 96 | await update.message.delete() -------------------------------------------------------------------------------- /WebStreamer/bot/plugins/start.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | from WebStreamer.bot import StreamBot 4 | from WebStreamer.vars import Var 5 | from WebStreamer.utils.database import Database 6 | from pyrogram import filters, Client 7 | from WebStreamer.utils.Translation import Language, BUTTON 8 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton 9 | from pyrogram.errors import UserNotParticipant 10 | from pyrogram.enums.parse_mode import ParseMode 11 | 12 | db = Database(Var.DATABASE_URL, Var.SESSION_NAME) 13 | 14 | @StreamBot.on_message(filters.command('start') & filters.private) 15 | async def start(b, m): 16 | lang = Language(m) 17 | # Check The User is Banned or Not 18 | if await db.is_user_banned(m.from_user.id): 19 | await b.send_message( 20 | chat_id=m.chat.id, 21 | text=lang.ban_text.format(Var.OWNER_ID), 22 | parse_mode=ParseMode.MARKDOWN, 23 | disable_web_page_preview=True 24 | ) 25 | return 26 | if not await db.is_user_exist(m.from_user.id): 27 | await db.add_user(m.from_user.id) 28 | await b.send_message( 29 | Var.BIN_CHANNEL, 30 | f"**Nᴇᴡ Usᴇʀ Jᴏɪɴᴇᴅ:** \n\n__Mʏ Nᴇᴡ Fʀɪᴇɴᴅ__ [{m.from_user.first_name}](tg://user?id={m.from_user.id}) __Sᴛᴀʀᴛᴇᴅ Yᴏᴜʀ Bᴏᴛ !!__" 31 | ) 32 | usr_cmd = m.text.split("_")[-1] 33 | if Var.FORCE_UPDATES_CHANNEL: 34 | try: 35 | user = await b.get_chat_member(Var.UPDATES_CHANNEL, m.chat.id) 36 | if user.status == "kicked": 37 | await b.send_message( 38 | chat_id=m.chat.id, 39 | text=lang.ban_text.format(Var.OWNER_ID), 40 | parse_mode=ParseMode.MARKDOWN, 41 | disable_web_page_preview=True 42 | ) 43 | return 44 | except UserNotParticipant: 45 | await b.send_message( 46 | chat_id=m.chat.id, 47 | text="Jᴏɪɴ ᴍʏ ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ ᴛᴏ ᴜsᴇ ᴍᴇ 🔐", 48 | reply_markup=InlineKeyboardMarkup( 49 | [[ 50 | InlineKeyboardButton("Jᴏɪɴ ɴᴏᴡ 🔓", url=f"https://t.me/{Var.UPDATES_CHANNEL}") 51 | ]] 52 | ), 53 | parse_mode=ParseMode.HTML 54 | ) 55 | return 56 | except Exception: 57 | await b.send_message( 58 | chat_id=m.chat.id, 59 | text=f"Sᴏᴍᴇᴛʜɪɴɢ ᴡʀᴏɴɢ ᴄᴏɴᴛᴀᴄᴛ ᴍʏ ᴅᴇᴠᴇʟᴏᴘᴇʀ [ ᴄʟɪᴄᴋ ʜᴇʀᴇ ]", 60 | parse_mode=ParseMode.HTML, 61 | disable_web_page_preview=True) 62 | return 63 | await m.reply_text( 64 | text=lang.START_TEXT.format(m.from_user.mention), 65 | parse_mode=ParseMode.HTML, 66 | disable_web_page_preview=True, 67 | reply_markup=BUTTON.START_BUTTONS 68 | ) 69 | 70 | 71 | @StreamBot.on_message(filters.private & filters.command(["about"])) 72 | async def start(bot, update): 73 | lang = Language(update) 74 | await update.reply_text( 75 | text=lang.ABOUT_TEXT.format(update.from_user.mention), 76 | disable_web_page_preview=True, 77 | reply_markup=BUTTON.ABOUT_BUTTONS 78 | ) 79 | 80 | 81 | @StreamBot.on_message((filters.command('help')) & filters.private) 82 | async def help_handler(bot, message): 83 | lang = Language(message) 84 | # Check The User is Banned or Not 85 | if await db.is_user_banned(message.from_user.id): 86 | await bot.send_message( 87 | chat_id=message.chat.id, 88 | text=lang.ban_text.format(Var.OWNER_ID), 89 | parse_mode=ParseMode.MARKDOWN, 90 | disable_web_page_preview=True 91 | ) 92 | return 93 | if not await db.is_user_exist(message.from_user.id): 94 | await db.add_user(message.from_user.id) 95 | await bot.send_message( 96 | Var.BIN_CHANNEL, 97 | f"**Nᴇᴡ Usᴇʀ Jᴏɪɴᴇᴅ **\n\n__Mʏ Nᴇᴡ Fʀɪᴇɴᴅ__ [{message.from_user.first_name}](tg://user?id={message.from_user.id}) __Started Your Bot !!__" 98 | ) 99 | if Var.FORCE_UPDATES_CHANNEL: 100 | try: 101 | user = await bot.get_chat_member(Var.UPDATES_CHANNEL, message.chat.id) 102 | if user.status == "kicked": 103 | await bot.send_message( 104 | chat_id=message.chat.id, 105 | text=lang.ban_text.format(Var.OWNER_ID), 106 | parse_mode=ParseMode.HTML, 107 | disable_web_page_preview=True 108 | ) 109 | return 110 | except UserNotParticipant: 111 | await bot.send_message( 112 | chat_id=message.chat.id, 113 | text="**Pʟᴇᴀsᴇ Jᴏɪɴ Mʏ Uᴘᴅᴀᴛᴇs Cʜᴀɴɴᴇʟ ᴛᴏ ᴜsᴇ ᴛʜɪs Bᴏᴛ!**\n\n__Dᴜᴇ ᴛᴏ Oᴠᴇʀʟᴏᴀᴅ, Oɴʟʏ Cʜᴀɴɴᴇʟ Sᴜʙsᴄʀɪʙᴇʀs ᴄᴀɴ ᴜsᴇ ᴛʜᴇ Bᴏᴛ!__", 114 | reply_markup=InlineKeyboardMarkup( 115 | [[ 116 | InlineKeyboardButton("🤖 Jᴏɪɴ Uᴘᴅᴀᴛᴇs Cʜᴀɴɴᴇʟ", url=f"https://t.me/{Var.UPDATES_CHANNEL}") 117 | ]] 118 | ), 119 | parse_mode=ParseMode.MARKDOWN 120 | ) 121 | return 122 | except Exception: 123 | await bot.send_message( 124 | chat_id=message.chat.id, 125 | text=f"__Sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ Wʀᴏɴɢ. Cᴏɴᴛᴀᴄᴛ ᴍᴇ__ [ ᴄʟɪᴄᴋ ʜᴇʀᴇ ](https://t.me/{Var.UPDATES_CHANNEL}).", 126 | parse_mode=ParseMode.MARKDOWN, 127 | disable_web_page_preview=True) 128 | return 129 | await message.reply_text( 130 | text=lang.HELP_TEXT.format(Var.UPDATES_CHANNEL), 131 | parse_mode=ParseMode.HTML, 132 | disable_web_page_preview=True, 133 | reply_markup=BUTTON.HELP_BUTTONS 134 | ) 135 | 136 | # -----------------------------Only for me you can remove below line ------------------------------------------------------- 137 | 138 | @StreamBot.on_message(filters.command('getid') & filters.private) 139 | async def start(b, m): 140 | await b.send_message( 141 | chat_id=m.chat.id, 142 | text=f"Your ID is: `{m.chat.id}`" 143 | ) -------------------------------------------------------------------------------- /WebStreamer/bot/plugins/stream.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | 4 | import asyncio 5 | from WebStreamer.utils.Translation import Language 6 | from WebStreamer.bot import StreamBot 7 | from WebStreamer.utils.database import Database 8 | from WebStreamer.utils.file_properties import gen_link, get_file_info, get_hash 9 | from WebStreamer.vars import Var 10 | from pyrogram import filters, Client 11 | from pyrogram.errors import FloodWait, UserNotParticipant 12 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton 13 | from pyrogram.enums.parse_mode import ParseMode 14 | db = Database(Var.DATABASE_URL, Var.SESSION_NAME) 15 | 16 | @StreamBot.on_message( 17 | filters.private 18 | & ( 19 | filters.document 20 | | filters.video 21 | | filters.audio 22 | | filters.animation 23 | | filters.voice 24 | | filters.video_note 25 | | filters.photo 26 | | filters.sticker 27 | ), 28 | group=4, 29 | ) 30 | async def private_receive_handler(c: Client, m: Message): 31 | lang = Language(m) 32 | # Check The User is Banned or Not 33 | if await db.is_user_banned(m.from_user.id): 34 | await c.send_message( 35 | chat_id=m.chat.id, 36 | text=lang.ban_text.format(Var.OWNER_ID), 37 | parse_mode=ParseMode.MARKDOWN, 38 | disable_web_page_preview=True 39 | ) 40 | return 41 | if not await db.is_user_exist(m.from_user.id): 42 | await db.add_user(m.from_user.id) 43 | await c.send_message( 44 | Var.BIN_CHANNEL, 45 | f"Nᴇᴡ Usᴇʀ Jᴏɪɴᴇᴅ : \n\nNᴀᴍᴇ : [{m.from_user.first_name}](tg://user?id={m.from_user.id}) Sᴛᴀʀᴛᴇᴅ Yᴏᴜʀ Bᴏᴛ !!" 46 | ) 47 | if Var.FORCE_UPDATES_CHANNEL: 48 | try: 49 | user = await c.get_chat_member(Var.UPDATES_CHANNEL, m.chat.id) 50 | if user.status == "kicked": 51 | await c.send_message( 52 | chat_id=m.chat.id, 53 | text=lang.ban_text.format(Var.OWNER_ID), 54 | parse_mode=ParseMode.MARKDOWN, 55 | disable_web_page_preview=True 56 | ) 57 | return 58 | except UserNotParticipant: 59 | await c.send_message( 60 | chat_id=m.chat.id, 61 | text="""Jᴏɪɴ ᴍʏ ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ ᴛᴏ ᴜꜱᴇ ᴍᴇ 🔐""", 62 | reply_markup=InlineKeyboardMarkup( 63 | [[ InlineKeyboardButton("Jᴏɪɴ ɴᴏᴡ 🔓", url=f"https://t.me/{Var.UPDATES_CHANNEL}") ]] 64 | ), 65 | parse_mode=ParseMode.HTML 66 | ) 67 | return 68 | except Exception: 69 | await c.send_message( 70 | chat_id=m.chat.id, 71 | text=f"**Sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ Wʀᴏɴɢ. [Cᴏɴᴛᴀᴄᴛ ᴍʏ ʙᴏss](tg://user?id={Var.OWNER_ID})**", 72 | parse_mode=ParseMode.MARKDOWN, 73 | disable_web_page_preview=True) 74 | return 75 | 76 | try: 77 | log_msg = await m.forward(chat_id=Var.BIN_CHANNEL) 78 | reply_markup, Stream_Text, stream_link = await gen_link(m=m, log_msg=log_msg, from_channel=False) 79 | await log_msg.reply_text(text=f"**RᴇQᴜᴇꜱᴛᴇᴅ ʙʏ :** [{m.from_user.first_name}](tg://user?id={m.from_user.id})\n**Uꜱᴇʀ ɪᴅ :** `{m.from_user.id}`\n**Dᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ :** {stream_link}", disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN, quote=True) 80 | # await db.add_file(get_file_info(log_msg,m)) 81 | await m.reply_text( 82 | text=Stream_Text, 83 | parse_mode=ParseMode.HTML, 84 | disable_web_page_preview=True, 85 | reply_markup=reply_markup, 86 | quote=True 87 | ) 88 | except FloodWait as e: 89 | print(f"Sleeping for {str(e.value)}s") 90 | await asyncio.sleep(e.value) 91 | await c.send_message(chat_id=Var.BIN_CHANNEL, text=f"Gᴏᴛ FʟᴏᴏᴅWᴀɪᴛ ᴏғ {str(e.value)}s from [{m.from_user.first_name}](tg://user?id={m.from_user.id})\n\n**𝚄𝚜𝚎𝚛 𝙸𝙳 :** `{str(m.from_user.id)}`", disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN) 92 | 93 | @StreamBot.on_message(filters.channel & (filters.document | filters.video), group=-1) 94 | async def channel_receive_handler(bot, broadcast: Message): 95 | if int(broadcast.chat.id) in Var.BANNED_CHANNELS: 96 | await bot.leave_chat(broadcast.chat.id) 97 | return 98 | try: 99 | log_msg = await broadcast.forward(chat_id=Var.BIN_CHANNEL) 100 | stream_link = "{}{}{}".format(Var.URL, get_hash(log_msg), log_msg.id) 101 | await log_msg.reply_text( 102 | text=f"**Cʜᴀɴɴᴇʟ Nᴀᴍᴇ:** `{broadcast.chat.title}`\n**Cʜᴀɴɴᴇʟ ID:** `{broadcast.chat.id}`\n**Rᴇǫᴜᴇsᴛ ᴜʀʟ:** https://t.me/{(await bot.get_me()).username}?start=msgid_{str(log_msg.id)}", 103 | # text=f"**Cʜᴀɴɴᴇʟ Nᴀᴍᴇ:** `{broadcast.chat.title}`\n**Cʜᴀɴɴᴇʟ ID:** `{broadcast.chat.id}`\n**Rᴇǫᴜᴇsᴛ ᴜʀʟ:** https://t.me/FxStreamBot?start=msgid_{str(log_msg.id)}", 104 | quote=True, 105 | parse_mode=ParseMode.MARKDOWN 106 | ) 107 | await bot.edit_message_reply_markup( 108 | chat_id=broadcast.chat.id, 109 | message_id=broadcast.id, 110 | reply_markup=InlineKeyboardMarkup( 111 | [[InlineKeyboardButton("Dᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ 📥", url=stream_link)]]) 112 | # [[InlineKeyboardButton("Dᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ 📥", url=f"https://t.me/{(await bot.get_me()).username}?start=msgid_{str(log_msg.id)}")]]) 113 | # [[InlineKeyboardButton("Dᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ 📥", url=f"https://t.me/FxStreamBot?start=msgid_{str(log_msg.id)}")]]) 114 | ) 115 | except FloodWait as w: 116 | print(f"Sleeping for {str(w.value)}s") 117 | await asyncio.sleep(w.value) 118 | await bot.send_message(chat_id=Var.BIN_CHANNEL, 119 | text=f"Gᴏᴛ FʟᴏᴏᴅWᴀɪᴛ ᴏғ {str(w.value)}s from {broadcast.chat.title}\n\n**Cʜᴀɴɴᴇʟ ID:** `{str(broadcast.chat.id)}`", 120 | disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN) 121 | except Exception as e: 122 | await bot.send_message(chat_id=Var.BIN_CHANNEL, text=f"**#ᴇʀʀᴏʀ_ᴛʀᴀᴄᴇʙᴀᴄᴋ:** `{e}`", disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN) 123 | print(f"Cᴀɴ'ᴛ Eᴅɪᴛ Bʀᴏᴀᴅᴄᴀsᴛ Mᴇssᴀɢᴇ!\nEʀʀᴏʀ: {e}") 124 | -------------------------------------------------------------------------------- /WebStreamer/server/__init__.py: -------------------------------------------------------------------------------- 1 | # Taken from megadlbot_oss 2 | # Thanks to Eyaadh 3 | # This file is a part of FileStreamBot 4 | 5 | from aiohttp import web 6 | from .stream_routes import routes 7 | 8 | 9 | def web_server(): 10 | web_app = web.Application(client_max_size=30000000) 11 | web_app.add_routes(routes) 12 | return web_app 13 | -------------------------------------------------------------------------------- /WebStreamer/server/exceptions.py: -------------------------------------------------------------------------------- 1 | 2 | class InvalidHash(Exception): 3 | message = "Invalid hash" 4 | 5 | class FIleNotFound(Exception): 6 | message = "File not found" -------------------------------------------------------------------------------- /WebStreamer/server/stream_routes.py: -------------------------------------------------------------------------------- 1 | # Taken from megadlbot_oss 2 | # Thanks to Eyaadh 3 | 4 | import re 5 | import time 6 | import math 7 | import logging 8 | import secrets 9 | import mimetypes 10 | from aiohttp import web 11 | from aiohttp.http_exceptions import BadStatusLine 12 | from WebStreamer.bot import multi_clients, work_loads 13 | from WebStreamer.server.exceptions import FIleNotFound, InvalidHash 14 | from WebStreamer import Var, utils, StartTime, __version__, StreamBot 15 | from WebStreamer.utils.render_template import render_page 16 | 17 | 18 | routes = web.RouteTableDef() 19 | 20 | @routes.get("/", allow_head=True) 21 | async def root_route_handler(_): 22 | return web.json_response( 23 | { 24 | "server_status": "running", 25 | "uptime": utils.get_readable_time(time.time() - StartTime), 26 | "telegram_bot": "@" + StreamBot.username, 27 | "connected_bots": len(multi_clients), 28 | "loads": dict( 29 | ("bot" + str(c + 1), l) 30 | for c, (_, l) in enumerate( 31 | sorted(work_loads.items(), key=lambda x: x[1], reverse=True) 32 | ) 33 | ), 34 | "version": __version__, 35 | } 36 | ) 37 | 38 | @routes.get(r"/watch/{path:\S+}", allow_head=True) 39 | async def stream_handler(request: web.Request): 40 | try: 41 | path = request.match_info["path"] 42 | match = re.search(r"^([a-zA-Z0-9_-]{6})(\d+)$", path) 43 | if match: 44 | secure_hash = match.group(1) 45 | message_id = int(match.group(2)) 46 | else: 47 | message_id = int(re.search(r"(\d+)(?:\/\S+)?", path).group(1)) 48 | secure_hash = request.rel_url.query.get("hash") 49 | return web.Response(text=await render_page(message_id, secure_hash), content_type='text/html') 50 | except InvalidHash as e: 51 | raise web.HTTPForbidden(text=e.message) 52 | except FIleNotFound as e: 53 | raise web.HTTPNotFound(text=e.message) 54 | except (AttributeError, BadStatusLine, ConnectionResetError): 55 | pass 56 | # except Exception as e: 57 | # logging.critical(e.with_traceback(None)) 58 | # raise web.HTTPInternalServerError(text=str(e)) 59 | 60 | @routes.get(r"/{path:\S+}", allow_head=True) 61 | async def stream_handler(request: web.Request): 62 | try: 63 | path = request.match_info["path"] 64 | match = re.search(r"^([a-zA-Z0-9_-]{6})(\d+)$", path) 65 | if match: 66 | secure_hash = match.group(1) 67 | message_id = int(match.group(2)) 68 | else: 69 | message_id = int(re.search(r"(\d+)(?:\/\S+)?", path).group(1)) 70 | secure_hash = request.rel_url.query.get("hash") 71 | return await media_streamer(request, message_id, secure_hash) 72 | except InvalidHash as e: 73 | raise web.HTTPForbidden(text=e.message) 74 | except FIleNotFound as e: 75 | raise web.HTTPNotFound(text=e.message) 76 | except (AttributeError, BadStatusLine, ConnectionResetError): 77 | pass 78 | except Exception as e: 79 | logging.critical(e.with_traceback(None)) 80 | raise web.HTTPInternalServerError(text=str(e)) 81 | 82 | class_cache = {} 83 | 84 | async def media_streamer(request: web.Request, message_id: int, secure_hash: str): 85 | range_header = request.headers.get("Range", 0) 86 | 87 | index = min(work_loads, key=work_loads.get) 88 | faster_client = multi_clients[index] 89 | 90 | if Var.MULTI_CLIENT: 91 | logging.info(f"Client {index} is now serving {request.remote}") 92 | 93 | if faster_client in class_cache: 94 | tg_connect = class_cache[faster_client] 95 | logging.debug(f"Using cached ByteStreamer object for client {index}") 96 | else: 97 | logging.debug(f"Creating new ByteStreamer object for client {index}") 98 | tg_connect = utils.ByteStreamer(faster_client) 99 | class_cache[faster_client] = tg_connect 100 | logging.debug("before calling get_file_properties") 101 | file_id = await tg_connect.get_file_properties(message_id) 102 | logging.debug("after calling get_file_properties") 103 | 104 | if file_id.unique_id[:6] != secure_hash: 105 | logging.debug(f"Invalid hash for message with ID {message_id}") 106 | raise InvalidHash 107 | 108 | file_size = file_id.file_size 109 | 110 | if range_header: 111 | from_bytes, until_bytes = range_header.replace("bytes=", "").split("-") 112 | from_bytes = int(from_bytes) 113 | until_bytes = int(until_bytes) if until_bytes else file_size - 1 114 | else: 115 | from_bytes = request.http_range.start or 0 116 | until_bytes = (request.http_range.stop or file_size) - 1 117 | 118 | if (until_bytes > file_size) or (from_bytes < 0) or (until_bytes < from_bytes): 119 | return web.Response( 120 | status=416, 121 | body="416: Range not satisfiable", 122 | headers={"Content-Range": f"bytes */{file_size}"}, 123 | ) 124 | 125 | chunk_size = 1024 * 1024 126 | until_bytes = min(until_bytes, file_size - 1) 127 | 128 | offset = from_bytes - (from_bytes % chunk_size) 129 | first_part_cut = from_bytes - offset 130 | last_part_cut = until_bytes % chunk_size + 1 131 | 132 | req_length = until_bytes - from_bytes + 1 133 | part_count = math.ceil(until_bytes / chunk_size) - math.floor(offset / chunk_size) 134 | body = tg_connect.yield_file( 135 | file_id, index, offset, first_part_cut, last_part_cut, part_count, chunk_size 136 | ) 137 | 138 | mime_type = file_id.mime_type 139 | file_name = utils.get_name(file_id) 140 | disposition = "attachment" 141 | 142 | if not mime_type: 143 | mime_type = mimetypes.guess_type(file_name)[0] or "application/octet-stream" 144 | 145 | # if "video/" in mime_type or "audio/" in mime_type: 146 | # disposition = "inline" 147 | 148 | return web.Response( 149 | status=206 if range_header else 200, 150 | body=body, 151 | headers={ 152 | "Content-Type": f"{mime_type}", 153 | "Content-Range": f"bytes {from_bytes}-{until_bytes}/{file_size}", 154 | "Content-Length": str(req_length), 155 | "Content-Disposition": f'{disposition}; filename="{file_name}"', 156 | "Accept-Ranges": "bytes", 157 | }, 158 | ) 159 | -------------------------------------------------------------------------------- /WebStreamer/template/dl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %s 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 | %s 21 |
22 |
23 | 24 | 34 | 35 | 43 | 44 | 55 | 56 | -------------------------------------------------------------------------------- /WebStreamer/template/req.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %s 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 | %s 22 |
23 |
24 | 25 |
26 | 27 |
28 | 29 | 37 | 38 | 39 | 40 | 70 | 71 | -------------------------------------------------------------------------------- /WebStreamer/utils/Translation.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | from WebStreamer.vars import Var 3 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, Message, CallbackQuery 4 | 5 | 6 | class Language(object): 7 | def __new__ (self, message): 8 | if getattr(message.from_user, 'language_code', 'Unknown') in self.available: 9 | return getattr(self, getattr(message.from_user, 'language_code', self.en), self.en) 10 | else: 11 | return self.en 12 | 13 | available=['en', 'language_code'] 14 | 15 | class en(object): 16 | START_TEXT = """ 17 | 👋 Hᴇʏ,{}\n 18 | I'm Telegram Files Streaming Bot As Well Direct Links Generator\n 19 | Cʟɪᴄᴋ ᴏɴ Hᴇʟᴘ ᴛᴏ ɢᴇᴛ ᴍᴏʀᴇ ɪɴғᴏʀᴍᴀᴛɪᴏɴ\n 20 | 𝗪𝗔𝗥𝗡𝗜𝗡𝗚 🚸\n 21 | 🔞 Pʀᴏɴ ᴄᴏɴᴛᴇɴᴛꜱ ʟᴇᴀᴅꜱ ᴛᴏ ᴘᴇʀᴍᴀɴᴇɴᴛ ʙᴀɴ ʏᴏᴜ.\n\n""" 22 | 23 | HELP_TEXT = """ 24 | - Sᴇɴᴅ ᴍᴇ ᴀɴʏ ꜰɪʟᴇ (ᴏʀ) ᴍᴇᴅɪᴀ ꜰʀᴏᴍ ᴛᴇʟᴇɢʀᴀᴍ. 25 | - I ᴡɪʟʟ ᴘʀᴏᴠɪᴅᴇ ᴇxᴛᴇʀɴᴀʟ ᴅɪʀᴇᴄᴛ ᴅᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ !. 26 | - ᴅᴏᴡɴʟᴏᴀᴅ Lɪɴᴋ Wɪᴛʜ Fᴀsᴛᴇsᴛ Sᴘᴇᴇᴅ 27 | 🔸 𝗪𝗔𝗥𝗡𝗜𝗡𝗚 🚸\n 28 | 🔞 Pʀᴏɴ ᴄᴏɴᴛᴇɴᴛꜱ ʟᴇᴀᴅꜱ ᴛᴏ ᴘᴇʀᴍᴀɴᴇɴᴛ ʙᴀɴ ʏᴏᴜ.\n 29 | Cᴏɴᴛᴀᴄᴛ ᴅᴇᴠᴇʟᴏᴘᴇʀ (ᴏʀ) ʀᴇᴘᴏʀᴛ ʙᴜɢꜱ : [ ᴄʟɪᴄᴋ ʜᴇʀᴇ ]""" 30 | 31 | ABOUT_TEXT = """ 32 | ⚜ Mʏ ɴᴀᴍᴇ : Public Link Generator\n 33 | 🔸Vᴇʀꜱɪᴏɴ : 2.21\n 34 | 🔹Lᴀꜱᴛ ᴜᴘᴅᴀᴛᴇᴅ : [ 26-Feb-2023 ] 8:46 PM 35 | """ 36 | 37 | stream_msg_text =""" 38 | 𝗬𝗼𝘂𝗿 𝗟𝗶𝗻𝗸 𝗚𝗲𝗻𝗲𝗿𝗮𝘁𝗲𝗱 !\n 39 | 📂 Fɪʟᴇ ɴᴀᴍᴇ : {}\n 40 | 📦 Fɪʟᴇ ꜱɪᴢᴇ : {}\n 41 | 📥 Dᴏᴡɴʟᴏᴀᴅ : {}\n 42 | 🖥WATCH : {}""" 43 | 44 | ban_text="__Sᴏʀʀʏ Sɪʀ, Yᴏᴜ ᴀʀᴇ Bᴀɴɴᴇᴅ ᴛᴏ ᴜsᴇ ᴍᴇ.__\n\n**[Cᴏɴᴛᴀᴄᴛ Dᴇᴠᴇʟᴏᴘᴇʀ](tg://user?id={}) Tʜᴇʏ Wɪʟʟ Hᴇʟᴘ Yᴏᴜ**" 45 | 46 | #----------------------# 47 | # Change the Text's below to add suport for your language 48 | 49 | # you can find the language_code for your language here 50 | # https://en.wikipedia.org/wiki/IETF_language_tag#List_of_common_primary_language_subtags 51 | # change language_code with your language code 52 | # eg: class kn(object): 53 | class language_code(object): 54 | START_TEXT = """ 55 | 👋 Hᴇʏ,{}\n 56 | I'm Telegram Files Streaming Bot As Well Direct Links Generator\n 57 | Cʟɪᴄᴋ ᴏɴ Hᴇʟᴘ ᴛᴏ ɢᴇᴛ ᴍᴏʀᴇ ɪɴғᴏʀᴍᴀᴛɪᴏɴ\n 58 | 𝗪𝗔𝗥𝗡𝗜𝗡𝗚 🚸\n 59 | 🔞 Pʀᴏɴ ᴄᴏɴᴛᴇɴᴛꜱ ʟᴇᴀᴅꜱ ᴛᴏ ᴘᴇʀᴍᴀɴᴇɴᴛ ʙᴀɴ ʏᴏᴜ.\n\n""" 60 | 61 | HELP_TEXT = """ 62 | - Sᴇɴᴅ ᴍᴇ ᴀɴʏ ꜰɪʟᴇ (ᴏʀ) ᴍᴇᴅɪᴀ ꜰʀᴏᴍ ᴛᴇʟᴇɢʀᴀᴍ. 63 | - I ᴡɪʟʟ ᴘʀᴏᴠɪᴅᴇ ᴇxᴛᴇʀɴᴀʟ ᴅɪʀᴇᴄᴛ ᴅᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ !. 64 | - ᴅᴏᴡɴʟᴏᴀᴅ Lɪɴᴋ Wɪᴛʜ Fᴀsᴛᴇsᴛ Sᴘᴇᴇᴅ 65 | 🔸 𝗪𝗔𝗥𝗡𝗜𝗡𝗚 🚸\n 66 | 🔞 Pʀᴏɴ ᴄᴏɴᴛᴇɴᴛꜱ ʟᴇᴀᴅꜱ ᴛᴏ ᴘᴇʀᴍᴀɴᴇɴᴛ ʙᴀɴ ʏᴏᴜ.\n 67 | Cᴏɴᴛᴀᴄᴛ ᴅᴇᴠᴇʟᴏᴘᴇʀ (ᴏʀ) ʀᴇᴘᴏʀᴛ ʙᴜɢꜱ : [ ᴄʟɪᴄᴋ ʜᴇʀᴇ ]""" 68 | 69 | ABOUT_TEXT = """ 70 | ⚜ Mʏ ɴᴀᴍᴇ : Public Link Generator\n 71 | 🔸Vᴇʀꜱɪᴏɴ : 3.0.3.1\n 72 | 🔹Lᴀꜱᴛ ᴜᴘᴅᴀᴛᴇᴅ : [ 18-Feb-22 ] 12:36 AM 73 | """ 74 | 75 | stream_msg_text =""" 76 | 𝗬𝗼𝘂𝗿 𝗟𝗶𝗻𝗸 𝗚𝗲𝗻𝗲𝗿𝗮𝘁𝗲𝗱 !\n 77 | 📂 Fɪʟᴇ ɴᴀᴍᴇ : {}\n 78 | 📦 Fɪʟᴇ ꜱɪᴢᴇ : {}\n 79 | 📥 Dᴏᴡɴʟᴏᴀᴅ : {}\n 80 | 🖥WATCH : {}""" 81 | 82 | ban_text="__Sᴏʀʀʏ Sɪʀ, Yᴏᴜ ᴀʀᴇ Bᴀɴɴᴇᴅ ᴛᴏ ᴜsᴇ ᴍᴇ.__\n\n**[Cᴏɴᴛᴀᴄᴛ Dᴇᴠᴇʟᴏᴘᴇʀ](tg://user?id={}) Tʜᴇʏ Wɪʟʟ Hᴇʟᴘ Yᴏᴜ**" 83 | 84 | # ------------------------------------------------------------------------------ 85 | 86 | class BUTTON(object): 87 | START_BUTTONS = InlineKeyboardMarkup( 88 | [[ 89 | InlineKeyboardButton('Hᴇʟᴘ', callback_data='help'), 90 | InlineKeyboardButton('Aʙᴏᴜᴛ', callback_data='about'), 91 | InlineKeyboardButton('Cʟᴏsᴇ', callback_data='close') 92 | ], 93 | [InlineKeyboardButton("📢 Bot Channel", url=f'https://t.me/{Var.UPDATES_CHANNEL}')] 94 | ] 95 | ) 96 | HELP_BUTTONS = InlineKeyboardMarkup( 97 | [[ 98 | InlineKeyboardButton('Hᴏᴍᴇ', callback_data='home'), 99 | InlineKeyboardButton('Aʙᴏᴜᴛ', callback_data='about'), 100 | InlineKeyboardButton('Cʟᴏsᴇ', callback_data='close'), 101 | ], 102 | [InlineKeyboardButton("📢 Bot Channel", url=f'https://t.me/{Var.UPDATES_CHANNEL}')] 103 | ] 104 | ) 105 | ABOUT_BUTTONS = InlineKeyboardMarkup( 106 | [[ 107 | InlineKeyboardButton('Hᴏᴍᴇ', callback_data='home'), 108 | InlineKeyboardButton('Hᴇʟᴘ', callback_data='help'), 109 | InlineKeyboardButton('Cʟᴏsᴇ', callback_data='close'), 110 | ], 111 | [InlineKeyboardButton("📢 Bot Channel", url=f'https://t.me/{Var.UPDATES_CHANNEL}')] 112 | ] 113 | ) -------------------------------------------------------------------------------- /WebStreamer/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | from .keepalive import ping_server 4 | from .time_format import get_readable_time 5 | from .file_properties import get_hash, get_name, get_file_ids 6 | from .custom_dl import ByteStreamer -------------------------------------------------------------------------------- /WebStreamer/utils/broadcast_helper.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | import asyncio 4 | import traceback 5 | from pyrogram.errors import FloodWait, InputUserDeactivated, UserIsBlocked, PeerIdInvalid 6 | 7 | 8 | async def send_msg(user_id, message): 9 | try: 10 | await message.forward(chat_id=user_id) 11 | return 200, None 12 | except FloodWait as e: 13 | await asyncio.sleep(e.value) 14 | return send_msg(user_id, message) 15 | except InputUserDeactivated: 16 | return 400, f"{user_id} : deactivated\n" 17 | except UserIsBlocked: 18 | return 400, f"{user_id} : blocked the bot\n" 19 | except PeerIdInvalid: 20 | return 400, f"{user_id} : user id invalid\n" 21 | except Exception as e: 22 | return 500, f"{user_id} : {traceback.format_exc()}\n" 23 | -------------------------------------------------------------------------------- /WebStreamer/utils/custom_dl.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | import math 4 | import asyncio 5 | import logging 6 | from WebStreamer import Var 7 | from typing import Dict, Union 8 | from WebStreamer.bot import work_loads 9 | from pyrogram import Client, utils, raw 10 | from .file_properties import get_file_ids 11 | from pyrogram.session import Session, Auth 12 | from pyrogram.errors import AuthBytesInvalid 13 | from WebStreamer.server.exceptions import FIleNotFound 14 | from pyrogram.file_id import FileId, FileType, ThumbnailSource 15 | 16 | 17 | class ByteStreamer: 18 | def __init__(self, client: Client): 19 | """A custom class that holds the cache of a specific client and class functions. 20 | attributes: 21 | client: the client that the cache is for. 22 | cached_file_ids: a dict of cached file IDs. 23 | cached_file_properties: a dict of cached file properties. 24 | 25 | functions: 26 | generate_file_properties: returns the properties for a media of a specific message contained in Tuple. 27 | generate_media_session: returns the media session for the DC that contains the media file. 28 | yield_file: yield a file from telegram servers for streaming. 29 | 30 | This is a modified version of the 31 | Thanks to Eyaadh 32 | """ 33 | self.clean_timer = 30 * 60 34 | self.client: Client = client 35 | self.cached_file_ids: Dict[int, FileId] = {} 36 | asyncio.create_task(self.clean_cache()) 37 | 38 | async def get_file_properties(self, message_id: int) -> FileId: 39 | """ 40 | Returns the properties of a media of a specific message in a FIleId class. 41 | if the properties are cached, then it'll return the cached results. 42 | or it'll generate the properties from the Message ID and cache them. 43 | """ 44 | if message_id not in self.cached_file_ids: 45 | await self.generate_file_properties(message_id) 46 | logging.debug(f"Cached file properties for message with ID {message_id}") 47 | return self.cached_file_ids[message_id] 48 | 49 | async def generate_file_properties(self, message_id: int) -> FileId: 50 | """ 51 | Generates the properties of a media file on a specific message. 52 | returns ths properties in a FIleId class. 53 | """ 54 | file_id = await get_file_ids(self.client, Var.BIN_CHANNEL, message_id) 55 | logging.debug(f"Generated file ID and Unique ID for message with ID {message_id}") 56 | if not file_id: 57 | logging.debug(f"Message with ID {message_id} not found") 58 | raise FIleNotFound 59 | self.cached_file_ids[message_id] = file_id 60 | logging.debug(f"Cached media message with ID {message_id}") 61 | return self.cached_file_ids[message_id] 62 | 63 | async def generate_media_session(self, client: Client, file_id: FileId) -> Session: 64 | """ 65 | Generates the media session for the DC that contains the media file. 66 | This is required for getting the bytes from Telegram servers. 67 | """ 68 | 69 | media_session = client.media_sessions.get(file_id.dc_id, None) 70 | 71 | if media_session is None: 72 | if file_id.dc_id != await client.storage.dc_id(): 73 | media_session = Session( 74 | client, 75 | file_id.dc_id, 76 | await Auth( 77 | client, file_id.dc_id, await client.storage.test_mode() 78 | ).create(), 79 | await client.storage.test_mode(), 80 | is_media=True, 81 | ) 82 | await media_session.start() 83 | 84 | for _ in range(6): 85 | exported_auth = await client.invoke( 86 | raw.functions.auth.ExportAuthorization(dc_id=file_id.dc_id) 87 | ) 88 | 89 | try: 90 | await media_session.invoke( 91 | raw.functions.auth.ImportAuthorization( 92 | id=exported_auth.id, bytes=exported_auth.bytes 93 | ) 94 | ) 95 | break 96 | except AuthBytesInvalid: 97 | logging.debug( 98 | f"Invalid authorization bytes for DC {file_id.dc_id}" 99 | ) 100 | continue 101 | else: 102 | await media_session.stop() 103 | raise AuthBytesInvalid 104 | else: 105 | media_session = Session( 106 | client, 107 | file_id.dc_id, 108 | await client.storage.auth_key(), 109 | await client.storage.test_mode(), 110 | is_media=True, 111 | ) 112 | await media_session.start() 113 | logging.debug(f"Created media session for DC {file_id.dc_id}") 114 | client.media_sessions[file_id.dc_id] = media_session 115 | else: 116 | logging.debug(f"Using cached media session for DC {file_id.dc_id}") 117 | return media_session 118 | 119 | 120 | @staticmethod 121 | async def get_location(file_id: FileId) -> Union[raw.types.InputPhotoFileLocation, 122 | raw.types.InputDocumentFileLocation, 123 | raw.types.InputPeerPhotoFileLocation,]: 124 | """ 125 | Returns the file location for the media file. 126 | """ 127 | file_type = file_id.file_type 128 | 129 | if file_type == FileType.CHAT_PHOTO: 130 | if file_id.chat_id > 0: 131 | peer = raw.types.InputPeerUser( 132 | user_id=file_id.chat_id, access_hash=file_id.chat_access_hash 133 | ) 134 | else: 135 | if file_id.chat_access_hash == 0: 136 | peer = raw.types.InputPeerChat(chat_id=-file_id.chat_id) 137 | else: 138 | peer = raw.types.InputPeerChannel( 139 | channel_id=utils.get_channel_id(file_id.chat_id), 140 | access_hash=file_id.chat_access_hash, 141 | ) 142 | 143 | location = raw.types.InputPeerPhotoFileLocation( 144 | peer=peer, 145 | volume_id=file_id.volume_id, 146 | local_id=file_id.local_id, 147 | big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG, 148 | ) 149 | elif file_type == FileType.PHOTO: 150 | location = raw.types.InputPhotoFileLocation( 151 | id=file_id.media_id, 152 | access_hash=file_id.access_hash, 153 | file_reference=file_id.file_reference, 154 | thumb_size=file_id.thumbnail_size, 155 | ) 156 | else: 157 | location = raw.types.InputDocumentFileLocation( 158 | id=file_id.media_id, 159 | access_hash=file_id.access_hash, 160 | file_reference=file_id.file_reference, 161 | thumb_size=file_id.thumbnail_size, 162 | ) 163 | return location 164 | 165 | async def yield_file( 166 | self, 167 | file_id: FileId, 168 | index: int, 169 | offset: int, 170 | first_part_cut: int, 171 | last_part_cut: int, 172 | part_count: int, 173 | chunk_size: int, 174 | ) -> Union[str, None]: 175 | """ 176 | Custom generator that yields the bytes of the media file. 177 | Modded from 178 | Thanks to Eyaadh 179 | """ 180 | client = self.client 181 | work_loads[index] += 1 182 | logging.debug(f"Starting to yielding file with client {index}.") 183 | media_session = await self.generate_media_session(client, file_id) 184 | 185 | current_part = 1 186 | 187 | location = await self.get_location(file_id) 188 | 189 | try: 190 | r = await media_session.invoke( 191 | raw.functions.upload.GetFile( 192 | location=location, offset=offset, limit=chunk_size 193 | ), 194 | ) 195 | if isinstance(r, raw.types.upload.File): 196 | while True: 197 | chunk = r.bytes 198 | if not chunk: 199 | break 200 | elif part_count == 1: 201 | yield chunk[first_part_cut:last_part_cut] 202 | elif current_part == 1: 203 | yield chunk[first_part_cut:] 204 | elif current_part == part_count: 205 | yield chunk[:last_part_cut] 206 | else: 207 | yield chunk 208 | 209 | current_part += 1 210 | offset += chunk_size 211 | 212 | if current_part > part_count: 213 | break 214 | 215 | r = await media_session.invoke( 216 | raw.functions.upload.GetFile( 217 | location=location, offset=offset, limit=chunk_size 218 | ), 219 | ) 220 | except (TimeoutError, AttributeError): 221 | pass 222 | finally: 223 | logging.debug("Finished yielding file with {current_part} parts.") 224 | work_loads[index] -= 1 225 | 226 | 227 | async def clean_cache(self) -> None: 228 | """ 229 | function to clean the cache to reduce memory usage 230 | """ 231 | while True: 232 | await asyncio.sleep(self.clean_timer) 233 | self.cached_file_ids.clear() 234 | logging.debug("Cleaned the cache") 235 | -------------------------------------------------------------------------------- /WebStreamer/utils/database.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | import datetime 4 | import motor.motor_asyncio 5 | 6 | 7 | class Database: 8 | def __init__(self, uri, database_name): 9 | self._client = motor.motor_asyncio.AsyncIOMotorClient(uri) 10 | self.db = self._client[database_name] 11 | self.col = self.db.users 12 | self.black = self.db.blacklist 13 | self.file = self.db.file 14 | 15 | # ----------------------add ,check or remove user---------------------- 16 | def new_user(self, id): 17 | return dict( 18 | id=id, 19 | join_date=datetime.date.today().isoformat() 20 | ) 21 | 22 | async def add_user(self, id): 23 | user = self.new_user(id) 24 | await self.col.insert_one(user) 25 | 26 | async def is_user_exist(self, id): 27 | user = await self.col.find_one({'id': int(id)}) 28 | return True if user else False 29 | 30 | async def total_users_count(self): 31 | count = await self.col.count_documents({}) 32 | return count 33 | 34 | async def get_all_users(self): 35 | all_users = self.col.find({}) 36 | return all_users 37 | 38 | async def delete_user(self, user_id): 39 | await self.col.delete_many({'id': int(user_id)}) 40 | 41 | # ----------------------ban, check banned or unban user---------------------- 42 | def black_user(self, id): 43 | return dict( 44 | id=id, 45 | ban_date=datetime.date.today().isoformat() 46 | ) 47 | 48 | async def ban_user(self, id): 49 | user = self.black_user(id) 50 | await self.black.insert_one(user) 51 | 52 | async def unban_user(self, id): 53 | await self.black.delete_one({'id': int(id)}) 54 | 55 | async def is_user_banned(self, id): 56 | user = await self.black.find_one({'id': int(id)}) 57 | return True if user else False 58 | 59 | async def total_banned_users_count(self): 60 | count = await self.black.count_documents({}) 61 | return count 62 | 63 | # -------------------------------------------------------- 64 | async def add_file(self, file_info): 65 | file_info["time"]=datetime.date.today().isoformat() 66 | await self.file.insert_one(file_info) -------------------------------------------------------------------------------- /WebStreamer/utils/file_properties.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | from __future__ import annotations 4 | from urllib.parse import quote_plus 5 | from datetime import datetime 6 | from pyrogram import Client 7 | from typing import Any, Optional 8 | from pyrogram.types import Message 9 | from pyrogram.file_id import FileId 10 | from pyrogram.raw.types.messages import Messages 11 | from WebStreamer.server.exceptions import FIleNotFound 12 | from WebStreamer.utils.Translation import Language 13 | from WebStreamer.utils.human_readable import humanbytes 14 | from WebStreamer.vars import Var 15 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup 16 | 17 | async def parse_file_id(message: "Message") -> Optional[FileId]: 18 | media = get_media_from_message(message) 19 | if media: 20 | return FileId.decode(media.file_id) 21 | 22 | async def parse_file_unique_id(message: "Messages") -> Optional[str]: 23 | media = get_media_from_message(message) 24 | if media: 25 | return media.file_unique_id 26 | 27 | async def get_file_ids(client: Client, chat_id: int, message_id: int) -> Optional[FileId]: 28 | message = await client.get_messages(chat_id, message_id) 29 | if message.empty: 30 | raise FIleNotFound 31 | media = get_media_from_message(message) 32 | file_unique_id = await parse_file_unique_id(message) 33 | file_id = await parse_file_id(message) 34 | setattr(file_id, "file_size", getattr(media, "file_size", 0)) 35 | setattr(file_id, "mime_type", getattr(media, "mime_type", "")) 36 | setattr(file_id, "file_name", getattr(media, "file_name", "")) 37 | setattr(file_id, "unique_id", file_unique_id) 38 | return file_id 39 | 40 | def get_media_from_message(message: "Message") -> Any: 41 | media_types = ( 42 | "audio", 43 | "document", 44 | "photo", 45 | "sticker", 46 | "animation", 47 | "video", 48 | "voice", 49 | "video_note", 50 | ) 51 | for attr in media_types: 52 | media = getattr(message, attr, None) 53 | if media: 54 | return media 55 | 56 | 57 | def get_hash(media_msg: Message) -> str: 58 | media = get_media_from_message(media_msg) 59 | return getattr(media, "file_unique_id", "")[:6] 60 | 61 | def get_media_file_size(m): 62 | media = get_media_from_message(m) 63 | return getattr(media, "file_size", "None") 64 | 65 | def get_name(media_msg: Message | FileId) -> str: 66 | 67 | if isinstance(media_msg, Message): 68 | media = get_media_from_message(media_msg) 69 | file_name = getattr(media, "file_name", "") 70 | 71 | elif isinstance(media_msg, FileId): 72 | file_name = getattr(media_msg, "file_name", "") 73 | 74 | if not file_name: 75 | if isinstance(media_msg, Message) and media_msg.media: 76 | media_type = media_msg.media.value 77 | elif media_msg.file_type: 78 | media_type = media_msg.file_type.name.lower() 79 | else: 80 | media_type = "file" 81 | 82 | formats = { 83 | "photo": "jpg", "audio": "mp3", "voice": "ogg", 84 | "video": "mp4", "animation": "mp4", "video_note": "mp4", 85 | "sticker": "webp" 86 | } 87 | 88 | ext = formats.get(media_type) 89 | ext = "." + ext if ext else "" 90 | 91 | date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") 92 | file_name = f"{media_type}-{date}{ext}" 93 | 94 | return file_name 95 | 96 | def get_media_mime_type(m): 97 | media = get_media_from_message(m) 98 | return getattr(media, "mime_type", "None/unknown") 99 | 100 | def get_media_file_unique_id(m): 101 | media = get_media_from_message(m) 102 | return getattr(media, "file_unique_id", "") 103 | 104 | def get_file_info(log_msg, message): 105 | media = get_media_from_message(log_msg) 106 | return { 107 | "user_id": message.from_user.id, 108 | "msg_id":log_msg.id, 109 | "file_id": getattr(media, "file_id", ""), 110 | "file_unique_id":getattr(media, "file_unique_id", ""), 111 | "file_name": get_name(log_msg), 112 | "file_size":getattr(media, "file_size", 0), 113 | "mime_type": getattr(media, "mime_type", "None/unknown") 114 | } 115 | 116 | # Generate Text, Stream Link, reply_markup 117 | async def gen_link(m: Message,log_msg: Messages, from_channel: bool): 118 | """Generate Text for Stream Link, Reply Text and reply_markup""" 119 | # lang = getattr(Language, message.from_user.language_code) 120 | lang = Language(m) 121 | file_name = get_name(log_msg) 122 | file_size = humanbytes(get_media_file_size(log_msg)) 123 | page_link = f"{Var.URL}watch/{get_hash(log_msg)}{log_msg.id}" 124 | 125 | stream_link = f"{Var.URL}{log_msg.id}/{quote_plus(file_name)}?hash={get_hash(log_msg)}" 126 | Stream_Text=lang.stream_msg_text.format(file_name, file_size, stream_link, page_link) 127 | reply_markup=InlineKeyboardMarkup( 128 | [ 129 | [InlineKeyboardButton("🖥STREAM", url=page_link), InlineKeyboardButton("Dᴏᴡɴʟᴏᴀᴅ 📥", url=stream_link)] 130 | ] 131 | ) 132 | 133 | return reply_markup, Stream_Text, stream_link -------------------------------------------------------------------------------- /WebStreamer/utils/human_readable.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | def humanbytes(size): 4 | # https://stackoverflow.com/a/49361727/4723940 5 | # 2**10 = 1024 6 | if not size: 7 | return "" 8 | power = 2**10 9 | n = 0 10 | Dic_powerN = {0: ' ', 1: 'Ki', 2: 'Mi', 3: 'Gi', 4: 'Ti'} 11 | while size > power: 12 | size /= power 13 | n += 1 14 | return str(round(size, 2)) + " " + Dic_powerN[n] + 'B' 15 | -------------------------------------------------------------------------------- /WebStreamer/utils/keepalive.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | import asyncio 4 | import logging 5 | import aiohttp 6 | import traceback 7 | from WebStreamer import Var 8 | 9 | 10 | async def ping_server(): 11 | sleep_time = Var.PING_INTERVAL 12 | while True: 13 | await asyncio.sleep(sleep_time) 14 | try: 15 | async with aiohttp.ClientSession( 16 | timeout=aiohttp.ClientTimeout(total=10) 17 | ) as session: 18 | async with session.get(Var.URL) as resp: 19 | logging.info("Pinged server with response: {}".format(resp.status)) 20 | except TimeoutError: 21 | logging.warning("Couldn't connect to the site URL..!") 22 | except Exception: 23 | traceback.print_exc() 24 | -------------------------------------------------------------------------------- /WebStreamer/utils/render_template.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | from WebStreamer import utils 4 | from WebStreamer.vars import Var 5 | from WebStreamer.bot import StreamBot 6 | from WebStreamer.utils.human_readable import humanbytes 7 | from WebStreamer.server.exceptions import InvalidHash 8 | import urllib.parse 9 | import aiofiles 10 | import logging 11 | import aiohttp 12 | 13 | 14 | async def render_page(message_id, secure_hash): 15 | file_data=await utils.get_file_ids(StreamBot, int(Var.BIN_CHANNEL), int(message_id)) 16 | if file_data.unique_id[:6] != secure_hash: 17 | logging.debug(f'link hash: {secure_hash} - {file_data.unique_id[:6]}') 18 | logging.debug(f"Invalid hash for message with - ID {message_id}") 19 | raise InvalidHash 20 | src = urllib.parse.urljoin(Var.URL, f'{secure_hash}{str(message_id)}') 21 | if str(file_data.mime_type.split('/')[0].strip()) == 'video': 22 | async with aiofiles.open('WebStreamer/template/req.html') as r: 23 | heading = 'Watch {}'.format(file_data.file_name) 24 | tag = file_data.mime_type.split('/')[0].strip() 25 | html = (await r.read()).replace('tag', tag) % (heading, file_data.file_name, src) 26 | elif str(file_data.mime_type.split('/')[0].strip()) == 'audio': 27 | async with aiofiles.open('WebStreamer/template/req.html') as r: 28 | heading = 'Listen {}'.format(file_data.file_name) 29 | tag = file_data.mime_type.split('/')[0].strip() 30 | html = (await r.read()).replace('tag', tag) % (heading, file_data.file_name, src) 31 | else: 32 | async with aiofiles.open('WebStreamer/template/dl.html') as r: 33 | async with aiohttp.ClientSession() as s: 34 | async with s.get(src) as u: 35 | heading = 'Download {}'.format(file_data.file_name) 36 | file_size = humanbytes(int(u.headers.get('Content-Length'))) 37 | html = (await r.read()) % (heading, file_data.file_name, src, file_size) 38 | return html 39 | -------------------------------------------------------------------------------- /WebStreamer/utils/time_format.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | def get_readable_time(seconds: int) -> str: 4 | count = 0 5 | readable_time = "" 6 | time_list = [] 7 | time_suffix_list = ["s", "m", "h", " days"] 8 | while count < 4: 9 | count += 1 10 | if count < 3: 11 | remainder, result = divmod(seconds, 60) 12 | else: 13 | remainder, result = divmod(seconds, 24) 14 | if seconds == 0 and remainder == 0: 15 | break 16 | time_list.append(int(result)) 17 | seconds = int(remainder) 18 | for x in range(len(time_list)): 19 | time_list[x] = str(time_list[x]) + time_suffix_list[x] 20 | if len(time_list) == 4: 21 | readable_time += time_list.pop() + ", " 22 | time_list.reverse() 23 | readable_time += ": ".join(time_list) 24 | return readable_time 25 | -------------------------------------------------------------------------------- /WebStreamer/vars.py: -------------------------------------------------------------------------------- 1 | # This file is a part of FileStreamBot 2 | 3 | from os import environ 4 | from dotenv import load_dotenv 5 | 6 | load_dotenv() 7 | 8 | 9 | class Var(object): 10 | MULTI_CLIENT = False 11 | API_ID = int(environ.get("API_ID")) 12 | API_HASH = str(environ.get("API_HASH")) 13 | BOT_TOKEN = str(environ.get("BOT_TOKEN")) 14 | SLEEP_THRESHOLD = int(environ.get("SLEEP_THRESHOLD", "60")) # 1 minte 15 | WORKERS = int(environ.get("WORKERS", "6")) # 6 workers = 6 commands at once 16 | BIN_CHANNEL = int( 17 | environ.get("BIN_CHANNEL", None) 18 | ) # you NEED to use a CHANNEL when you're using MULTI_CLIENT 19 | PORT = int(environ.get("PORT", 8080)) 20 | BIND_ADDRESS = str(environ.get("WEB_SERVER_BIND_ADDRESS", "0.0.0.0")) 21 | PING_INTERVAL = int(environ.get("PING_INTERVAL", "1200")) # 20 minutes 22 | HAS_SSL = str(environ.get("HAS_SSL", "0").lower()) in ("1", "true", "t", "yes", "y") 23 | NO_PORT = str(environ.get("NO_PORT", "0").lower()) in ("1", "true", "t", "yes", "y") 24 | FQDN = str(environ.get("FQDN", BIND_ADDRESS)) 25 | URL = "http{}://{}{}/".format( 26 | "s" if HAS_SSL else "", FQDN, "" if NO_PORT else ":" + str(PORT) 27 | ) 28 | 29 | DATABASE_URL = str(environ.get('DATABASE_URL')) 30 | UPDATES_CHANNEL = str(environ.get('UPDATES_CHANNEL', "Telegram")) 31 | OWNER_ID = int(environ.get('OWNER_ID', '777000')) 32 | SESSION_NAME = str(environ.get('SESSION_NAME', 'F2LxBot')) 33 | FORCE_UPDATES_CHANNEL = environ.get('FORCE_UPDATES_CHANNEL', False) 34 | FORCE_UPDATES_CHANNEL = True if str(FORCE_UPDATES_CHANNEL).lower() == "true" and UPDATES_CHANNEL != 'aredirect' else False 35 | 36 | BANNED_CHANNELS = list(set(int(x) for x in str(environ.get("BANNED_CHANNELS", "-1001296894100")).split())) 37 | KEEP_ALIVE = str(environ.get("KEEP_ALIVE", "0").lower()) in ("1", "true", "t", "yes", "y") -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | pyrogram 3 | python-dotenv 4 | tgcrypto 5 | motor 6 | aiofiles 7 | dnspython 8 | psutil --------------------------------------------------------------------------------