├── .gitattributes
├── .vscode
└── settings.json
├── requirements.txt
├── WebStreamer
├── server
│ ├── exceptions.py
│ ├── __init__.py
│ └── stream_routes.py
├── __init__.py
├── utils
│ ├── __init__.py
│ ├── human_readable.py
│ ├── keepalive.py
│ ├── broadcast_helper.py
│ ├── time_format.py
│ ├── render_template.py
│ ├── database.py
│ ├── Translation.py
│ ├── file_properties.py
│ └── custom_dl.py
├── bot
│ ├── __init__.py
│ ├── clients.py
│ └── plugins
│ │ ├── callback.py
│ │ ├── admin.py
│ │ ├── start.py
│ │ └── stream.py
├── vars.py
├── template
│ ├── dl.html
│ └── req.html
└── __main__.py
├── .changelog
├── .gitignore
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "VSCord.enabled": true,
3 | "cSpell.words": [
4 | "DYNO"
5 | ]
6 | }
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | aiohttp
2 | pyrogram
3 | python-dotenv
4 | tgcrypto
5 | motor
6 | aiofiles
7 | dnspython
8 | psutil
--------------------------------------------------------------------------------
/WebStreamer/server/exceptions.py:
--------------------------------------------------------------------------------
1 |
2 | class InvalidHash(Exception):
3 | message = "Invalid hash"
4 |
5 | class FIleNotFound(Exception):
6 | message = "File not found"
--------------------------------------------------------------------------------
/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/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
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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")
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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.*
--------------------------------------------------------------------------------
/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/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/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 |
36 | Fork me on
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
55 |
56 |
--------------------------------------------------------------------------------
/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/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 |
30 | Fork me on
31 |
32 |
33 |
34 |
35 |
36 |
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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Telegram File Stream Bot
2 |
3 |
4 |
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 |
22 | About this Bot
23 |
26 |
27 |
28 | How to make your own
29 |
32 |
33 | Setting up things
34 |
38 | How to use the bot
39 | Contributing
40 | Contact me
41 | Credits
42 |
43 |
44 |
45 | ## About This Bot
46 |
47 |
48 |
49 |
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 | [](https://xn--r1a.click/AWeirdText)
181 | [](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/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 |
--------------------------------------------------------------------------------