From 28164dfe5b3b653fd751390333adf8f44fb54330 Mon Sep 17 00:00:00 2001 From: Deekshith SH Date: Mon, 24 Mar 2025 18:32:27 +0530 Subject: [PATCH 1/8] Use fork of pyrogram --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3974efd3..13bdd536 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -aiohttp<=3.8.1 -pyrogram<=2.0.93 -python-dotenv<=0.20.0 +aiohttp<=3.11.14 +kurigram<=2.1.39 +python-dotenv<=1.0.1 TgCrypto<=1.2.5 \ No newline at end of file From cca1452c2b1edcdaddb8ee28f6336ad6657aed7a Mon Sep 17 00:00:00 2001 From: Deekshith SH Date: Mon, 24 Mar 2025 19:25:02 +0530 Subject: [PATCH 2/8] Show Stream Button if Message is Audio or Video --- WebStreamer/bot/plugins/stream.py | 10 ++++++---- WebStreamer/server/stream_routes.py | 6 +++--- WebStreamer/utils/__init__.py | 2 +- WebStreamer/utils/file_properties.py | 4 ++++ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/WebStreamer/bot/plugins/stream.py b/WebStreamer/bot/plugins/stream.py index 0c0b368b..26daf9e6 100644 --- a/WebStreamer/bot/plugins/stream.py +++ b/WebStreamer/bot/plugins/stream.py @@ -6,7 +6,7 @@ from WebStreamer.vars import Var from urllib.parse import quote_plus from WebStreamer.bot import StreamBot, logger -from WebStreamer.utils import get_hash, get_name +from WebStreamer.utils import get_hash, get_name, get_mimetype from pyrogram.enums.parse_mode import ParseMode from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton @@ -30,9 +30,13 @@ async def media_receive_handler(_, m: Message): return await m.reply("You are not allowed to use this bot.", quote=True) log_msg = await m.forward(chat_id=Var.BIN_CHANNEL) file_hash = get_hash(log_msg, Var.HASH_LENGTH) + mimetype = get_mimetype(log_msg) stream_link = f"{Var.URL}{log_msg.id}/{quote_plus(get_name(m))}?hash={file_hash}" short_link = f"{Var.URL}{file_hash}{log_msg.id}" logger.info(f"Generated link: {stream_link} for {m.from_user.first_name}") + markup = [InlineKeyboardButton("Download", url=stream_link+"&d=true")] + if set(mimetype.split("/")) & {"video","audio","pdf"}: + markup.append(InlineKeyboardButton("Stream", url=stream_link)) try: await m.reply_text( text="{}\n(shortened)".format( @@ -40,9 +44,7 @@ async def media_receive_handler(_, m: Message): ), quote=True, parse_mode=ParseMode.HTML, - reply_markup=InlineKeyboardMarkup( - [[InlineKeyboardButton("Open", url=stream_link)]] - ), + reply_markup=InlineKeyboardMarkup([markup]), ) except errors.ButtonUrlInvalid: await m.reply_text( diff --git a/WebStreamer/server/stream_routes.py b/WebStreamer/server/stream_routes.py index 94da4827..b1dc21e5 100644 --- a/WebStreamer/server/stream_routes.py +++ b/WebStreamer/server/stream_routes.py @@ -117,13 +117,13 @@ async def media_streamer(request: web.Request, message_id: int, secure_hash: str ) mime_type = file_id.mime_type file_name = utils.get_name(file_id) - disposition = "attachment" + disposition = "inline" if not mime_type: mime_type = mimetypes.guess_type(file_name)[0] or "application/octet-stream" - if "video/" in mime_type or "audio/" in mime_type or "/html" in mime_type: - disposition = "inline" + if request.rel_url.query.get("d") == "true": + disposition = "attachment" return web.Response( status=206 if range_header else 200, diff --git a/WebStreamer/utils/__init__.py b/WebStreamer/utils/__init__.py index edf43fea..bfef2ba7 100644 --- a/WebStreamer/utils/__init__.py +++ b/WebStreamer/utils/__init__.py @@ -3,5 +3,5 @@ from .keepalive import ping_server from .time_format import get_readable_time -from .file_properties import get_hash, get_name +from .file_properties import get_hash, get_name, get_mimetype from .custom_dl import ByteStreamer \ No newline at end of file diff --git a/WebStreamer/utils/file_properties.py b/WebStreamer/utils/file_properties.py index f44ccba1..1bc28347 100644 --- a/WebStreamer/utils/file_properties.py +++ b/WebStreamer/utils/file_properties.py @@ -88,3 +88,7 @@ def get_name(media_msg: Union[Message, FileId]) -> str: file_name = f"{media_type}-{date}{ext}" return file_name + +def get_mimetype(media_msg: Message) -> str: + media = get_media_from_message(media_msg) + return media.mime_type or "application/octet-stream" \ No newline at end of file From bcf0d73af7830dc68ed8c0d3714f22ad702fbb1a Mon Sep 17 00:00:00 2001 From: Deekshith SH Date: Mon, 24 Mar 2025 20:20:09 +0530 Subject: [PATCH 3/8] Removed shortened link --- WebStreamer/bot/plugins/stream.py | 11 +++-------- WebStreamer/server/stream_routes.py | 12 +++--------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/WebStreamer/bot/plugins/stream.py b/WebStreamer/bot/plugins/stream.py index 26daf9e6..76e47e20 100644 --- a/WebStreamer/bot/plugins/stream.py +++ b/WebStreamer/bot/plugins/stream.py @@ -31,26 +31,21 @@ async def media_receive_handler(_, m: Message): log_msg = await m.forward(chat_id=Var.BIN_CHANNEL) file_hash = get_hash(log_msg, Var.HASH_LENGTH) mimetype = get_mimetype(log_msg) - stream_link = f"{Var.URL}{log_msg.id}/{quote_plus(get_name(m))}?hash={file_hash}" - short_link = f"{Var.URL}{file_hash}{log_msg.id}" + stream_link = f"{Var.URL}{log_msg.id}?hash={file_hash}" logger.info(f"Generated link: {stream_link} for {m.from_user.first_name}") markup = [InlineKeyboardButton("Download", url=stream_link+"&d=true")] if set(mimetype.split("/")) & {"video","audio","pdf"}: markup.append(InlineKeyboardButton("Stream", url=stream_link)) try: await m.reply_text( - text="{}\n(shortened)".format( - stream_link, short_link - ), + text=f"{stream_link}", quote=True, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup([markup]), ) except errors.ButtonUrlInvalid: await m.reply_text( - text="{}\n\nshortened: {})".format( - stream_link, short_link - ), + text=f"{stream_link})", quote=True, parse_mode=ParseMode.HTML, ) diff --git a/WebStreamer/server/stream_routes.py b/WebStreamer/server/stream_routes.py index b1dc21e5..b88cae9c 100644 --- a/WebStreamer/server/stream_routes.py +++ b/WebStreamer/server/stream_routes.py @@ -37,17 +37,11 @@ async def root_route_handler(_): ) -@routes.get(r"/{path:\S+}", allow_head=True) +@routes.get(r"/{message_id:\d+}", allow_head=True) async def stream_handler(request: web.Request): try: - path = request.match_info["path"] - match = re.search(r"^([0-9a-f]{%s})(\d+)$" % (Var.HASH_LENGTH), path) - if match: - secure_hash = match.group(1) - message_id = int(match.group(2)) - else: - message_id = int(re.search(r"(\d+)(?:\/\S+)?", path).group(1)) - secure_hash = request.rel_url.query.get("hash") + message_id = int(request.match_info["message_id"]) + secure_hash = request.rel_url.query.get("hash") return await media_streamer(request, message_id, secure_hash) except InvalidHash as e: raise web.HTTPForbidden(text=e.message) From 6eca763539bbc1ca1ed5ae7ca4f8663034ebdc86 Mon Sep 17 00:00:00 2001 From: Deekshith SH Date: Mon, 24 Mar 2025 21:09:30 +0530 Subject: [PATCH 4/8] Changed import order Removed Unused imports, Trailing spaces Use Lazy formating in logging Type Hinting --- WebStreamer/__init__.py | 2 +- WebStreamer/__main__.py | 10 +++---- WebStreamer/bot/__init__.py | 9 +++--- WebStreamer/bot/clients.py | 12 ++++---- WebStreamer/bot/plugins/start.py | 2 +- WebStreamer/bot/plugins/stream.py | 10 +++---- WebStreamer/server/exceptions.py | 2 +- WebStreamer/server/stream_routes.py | 20 ++++++------- WebStreamer/utils/__init__.py | 2 +- WebStreamer/utils/custom_dl.py | 42 +++++++++++++--------------- WebStreamer/utils/file_properties.py | 6 ++-- WebStreamer/utils/keepalive.py | 4 +-- WebStreamer/vars.py | 6 ++-- 13 files changed, 59 insertions(+), 68 deletions(-) diff --git a/WebStreamer/__init__.py b/WebStreamer/__init__.py index 9f25f5d4..e5dbb97b 100644 --- a/WebStreamer/__init__.py +++ b/WebStreamer/__init__.py @@ -3,8 +3,8 @@ import time -from .vars import Var from WebStreamer.bot.clients import StreamBot +from .vars import Var __version__ = "2.2.4" StartTime = time.time() diff --git a/WebStreamer/__main__.py b/WebStreamer/__main__.py index 7eed09a1..5237f82e 100644 --- a/WebStreamer/__main__.py +++ b/WebStreamer/__main__.py @@ -4,13 +4,13 @@ import sys import asyncio import logging -from .vars import Var from aiohttp import web from pyrogram import idle from WebStreamer import utils from WebStreamer import StreamBot from WebStreamer.server import web_server from WebStreamer.bot.clients import initialize_clients +from .vars import Var logging.basicConfig( @@ -43,10 +43,10 @@ async def start_services(): await server.setup() await web.TCPSite(server, Var.BIND_ADDRESS, Var.PORT).start() logging.info("Service Started") - logging.info("bot =>> {}".format(bot_info.first_name)) + logging.info("bot =>> %s", bot_info.first_name) if bot_info.dc_id: - logging.info("DC ID =>> {}".format(str(bot_info.dc_id))) - logging.info("URL =>> {}".format(Var.URL)) + logging.info("DC ID =>> %d", bot_info.dc_id) + logging.info("URL =>> %s", Var.URL) await idle() async def cleanup(): @@ -63,4 +63,4 @@ async def cleanup(): finally: loop.run_until_complete(cleanup()) loop.stop() - logging.info("Stopped Services") \ No newline at end of file + logging.info("Stopped Services") diff --git a/WebStreamer/bot/__init__.py b/WebStreamer/bot/__init__.py index 6c7794d0..0e277a3f 100644 --- a/WebStreamer/bot/__init__.py +++ b/WebStreamer/bot/__init__.py @@ -4,16 +4,17 @@ import os import os.path -from ..vars import Var import logging +from typing import Dict from pyrogram import Client +from ..vars import Var logger = logging.getLogger("bot") sessions_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "sessions") if Var.USE_SESSION_FILE: logger.info("Using session files") - logger.info("Session folder path: {}".format(sessions_dir)) + logger.info("Session folder path: %s", sessions_dir) if not os.path.isdir(sessions_dir): os.makedirs(sessions_dir) @@ -29,5 +30,5 @@ in_memory=not Var.USE_SESSION_FILE, ) -multi_clients = {} -work_loads = {} +multi_clients: Dict[int, Client] = {} +work_loads: Dict[int, int] = {} diff --git a/WebStreamer/bot/clients.py b/WebStreamer/bot/clients.py index f7fa82bf..b83ff68b 100644 --- a/WebStreamer/bot/clients.py +++ b/WebStreamer/bot/clients.py @@ -4,8 +4,8 @@ import asyncio import logging from os import environ -from ..vars import Var from pyrogram import Client +from ..vars import Var from . import multi_clients, work_loads, sessions_dir, StreamBot logger = logging.getLogger("multi_client") @@ -24,10 +24,10 @@ async def initialize_clients(): if not all_tokens: logger.info("No additional clients found, using default client") return - + async def start_client(client_id, token): try: - logger.info(f"Starting - Client {client_id}") + logger.info("Starting - Client %s", client_id) if client_id == len(all_tokens): await asyncio.sleep(2) print("This will take some time, please wait...") @@ -44,12 +44,12 @@ async def start_client(client_id, token): work_loads[client_id] = 0 return client_id, client except Exception: - logger.error(f"Failed starting Client - {client_id} Error:", exc_info=True) - + logger.error("Failed starting Client - %s Error:", client_id, exc_info=True) + clients = await asyncio.gather(*[start_client(i, token) for i, token in all_tokens.items()]) multi_clients.update(dict(clients)) if len(multi_clients) != 1: Var.MULTI_CLIENT = True logger.info("Multi-client mode enabled") else: - logger.info("No additional clients were initialized, using default client") \ No newline at end of file + logger.info("No additional clients were initialized, using default client") diff --git a/WebStreamer/bot/plugins/start.py b/WebStreamer/bot/plugins/start.py index d0b6898e..3e7451f1 100644 --- a/WebStreamer/bot/plugins/start.py +++ b/WebStreamer/bot/plugins/start.py @@ -4,7 +4,7 @@ from pyrogram import filters from pyrogram.types import Message -from WebStreamer.vars import Var +from WebStreamer.vars import Var from WebStreamer.bot import StreamBot @StreamBot.on_message(filters.command(["start", "help"]) & filters.private) diff --git a/WebStreamer/bot/plugins/stream.py b/WebStreamer/bot/plugins/stream.py index 76e47e20..139d7fba 100644 --- a/WebStreamer/bot/plugins/stream.py +++ b/WebStreamer/bot/plugins/stream.py @@ -1,14 +1,12 @@ # This file is a part of TG-FileStreamBot # Coding : Jyothis Jayanth [@EverythingSuckz] -import logging from pyrogram import filters, errors -from WebStreamer.vars import Var -from urllib.parse import quote_plus -from WebStreamer.bot import StreamBot, logger -from WebStreamer.utils import get_hash, get_name, get_mimetype from pyrogram.enums.parse_mode import ParseMode from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton +from WebStreamer.vars import Var +from WebStreamer.bot import StreamBot, logger +from WebStreamer.utils import get_hash, get_mimetype @StreamBot.on_message( @@ -32,7 +30,7 @@ async def media_receive_handler(_, m: Message): file_hash = get_hash(log_msg, Var.HASH_LENGTH) mimetype = get_mimetype(log_msg) stream_link = f"{Var.URL}{log_msg.id}?hash={file_hash}" - logger.info(f"Generated link: {stream_link} for {m.from_user.first_name}") + logger.info("Generated link: %s for %s", stream_link, m.from_user.first_name) markup = [InlineKeyboardButton("Download", url=stream_link+"&d=true")] if set(mimetype.split("/")) & {"video","audio","pdf"}: markup.append(InlineKeyboardButton("Stream", url=stream_link)) diff --git a/WebStreamer/server/exceptions.py b/WebStreamer/server/exceptions.py index 438f6de1..7d2ca95e 100644 --- a/WebStreamer/server/exceptions.py +++ b/WebStreamer/server/exceptions.py @@ -3,4 +3,4 @@ class InvalidHash(Exception): message = "Invalid hash" class FIleNotFound(Exception): - message = "File not found" \ No newline at end of file + message = "File not found" diff --git a/WebStreamer/server/stream_routes.py b/WebStreamer/server/stream_routes.py index b88cae9c..77ff26e2 100644 --- a/WebStreamer/server/stream_routes.py +++ b/WebStreamer/server/stream_routes.py @@ -1,11 +1,9 @@ # Taken from megadlbot_oss # Thanks to Eyaadh -import re import time import math import logging -import secrets import mimetypes from aiohttp import web from aiohttp.http_exceptions import BadStatusLine @@ -57,29 +55,29 @@ async def stream_handler(request: web.Request): async def media_streamer(request: web.Request, message_id: int, secure_hash: str): range_header = request.headers.get("Range", 0) - + index = min(work_loads, key=work_loads.get) faster_client = multi_clients[index] - + if Var.MULTI_CLIENT: - logger.info(f"Client {index} is now serving {request.remote}") + logger.info("Client %d is now serving %s", index, request.remote) if faster_client in class_cache: tg_connect = class_cache[faster_client] - logger.debug(f"Using cached ByteStreamer object for client {index}") + logger.debug("Using cached ByteStreamer object for client %d", index) else: - logger.debug(f"Creating new ByteStreamer object for client {index}") + logger.debug("Creating new ByteStreamer object for client %d", index) tg_connect = utils.ByteStreamer(faster_client) class_cache[faster_client] = tg_connect logger.debug("before calling get_file_properties") file_id = await tg_connect.get_file_properties(message_id) logger.debug("after calling get_file_properties") - - + + if utils.get_hash(file_id.unique_id, Var.HASH_LENGTH) != secure_hash: - logger.debug(f"Invalid hash for message with ID {message_id}") + logger.debug("Invalid hash for message with ID %d", message_id) raise InvalidHash - + file_size = file_id.file_size if range_header: diff --git a/WebStreamer/utils/__init__.py b/WebStreamer/utils/__init__.py index bfef2ba7..f17a62bc 100644 --- a/WebStreamer/utils/__init__.py +++ b/WebStreamer/utils/__init__.py @@ -4,4 +4,4 @@ from .keepalive import ping_server from .time_format import get_readable_time from .file_properties import get_hash, get_name, get_mimetype -from .custom_dl import ByteStreamer \ No newline at end of file +from .custom_dl import ByteStreamer diff --git a/WebStreamer/utils/custom_dl.py b/WebStreamer/utils/custom_dl.py index 7c89602f..e11e4f0d 100644 --- a/WebStreamer/utils/custom_dl.py +++ b/WebStreamer/utils/custom_dl.py @@ -1,15 +1,14 @@ -import math import asyncio import logging -from WebStreamer import Var -from typing import Dict, Union -from WebStreamer.bot import work_loads +from typing import AsyncGenerator, Dict, Union from pyrogram import Client, utils, raw -from .file_properties import get_file_ids from pyrogram.session import Session, Auth from pyrogram.errors import AuthBytesInvalid -from WebStreamer.server.exceptions import FIleNotFound from pyrogram.file_id import FileId, FileType, ThumbnailSource +from WebStreamer.server.exceptions import FIleNotFound +from WebStreamer import Var +from WebStreamer.bot import work_loads +from .file_properties import get_file_ids logger = logging.getLogger("streamer") @@ -42,21 +41,21 @@ async def get_file_properties(self, message_id: int) -> FileId: """ if message_id not in self.cached_file_ids: await self.generate_file_properties(message_id) - logger.debug(f"Cached file properties for message with ID {message_id}") + logger.debug("Cached file properties for message with ID %d", message_id) return self.cached_file_ids[message_id] - + async def generate_file_properties(self, message_id: int) -> FileId: """ Generates the properties of a media file on a specific message. returns ths properties in a FIleId class. """ file_id = await get_file_ids(self.client, Var.BIN_CHANNEL, message_id) - logger.debug(f"Generated file ID and Unique ID for message with ID {message_id}") + logger.debug("Generated file ID and Unique ID for message with ID %d", message_id) if not file_id: - logger.debug(f"Message with ID {message_id} not found") + logger.debug("Message with ID %d not found", message_id) raise FIleNotFound self.cached_file_ids[message_id] = file_id - logger.debug(f"Cached media message with ID {message_id}") + logger.debug("Cached media message with ID %d", message_id) return self.cached_file_ids[message_id] async def generate_media_session(self, client: Client, file_id: FileId) -> Session: @@ -93,9 +92,7 @@ async def generate_media_session(self, client: Client, file_id: FileId) -> Sessi ) break except AuthBytesInvalid: - logger.debug( - f"Invalid authorization bytes for DC {file_id.dc_id}" - ) + logger.debug("Invalid authorization bytes for DC %d", file_id.dc_id) continue else: await media_session.stop() @@ -109,10 +106,10 @@ async def generate_media_session(self, client: Client, file_id: FileId) -> Sessi is_media=True, ) await media_session.start() - logger.debug(f"Created media session for DC {file_id.dc_id}") + logger.debug("Created media session for DC %d", file_id.dc_id) client.media_sessions[file_id.dc_id] = media_session else: - logger.debug(f"Using cached media session for DC {file_id.dc_id}") + logger.debug("Using cached media session for DC %d", file_id.dc_id) return media_session @@ -141,9 +138,8 @@ async def get_location(file_id: FileId) -> Union[raw.types.InputPhotoFileLocatio location = raw.types.InputPeerPhotoFileLocation( peer=peer, - volume_id=file_id.volume_id, - local_id=file_id.local_id, - big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG, + photo_id=file_id.media_id, + big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG ) elif file_type == FileType.PHOTO: location = raw.types.InputPhotoFileLocation( @@ -170,7 +166,7 @@ async def yield_file( last_part_cut: int, part_count: int, chunk_size: int, - ) -> Union[str, None]: + ) -> AsyncGenerator[bytes, None]: """ Custom generator that yields the bytes of the media file. Modded from @@ -178,7 +174,7 @@ async def yield_file( """ client = self.client work_loads[index] += 1 - logger.debug(f"Starting to yielding file with client {index}.") + logger.debug("Starting to yielding file with client %d.", index) media_session = await self.generate_media_session(client, file_id) current_part = 1 @@ -218,10 +214,10 @@ async def yield_file( except (TimeoutError, AttributeError): pass finally: - logger.debug(f"Finished yielding file with {current_part} parts.") + logger.debug("Finished yielding file with %d parts.", current_part) work_loads[index] -= 1 - + async def clean_cache(self) -> None: """ function to clean the cache to reduce memory usage diff --git a/WebStreamer/utils/file_properties.py b/WebStreamer/utils/file_properties.py index 1bc28347..105692be 100644 --- a/WebStreamer/utils/file_properties.py +++ b/WebStreamer/utils/file_properties.py @@ -1,11 +1,11 @@ import hashlib +from datetime import datetime +from typing import Any, Optional, Union from pyrogram import Client from pyrogram.types import Message from pyrogram.file_id import FileId -from typing import Any, Optional, Union from pyrogram.raw.types.messages import Messages from WebStreamer.server.exceptions import FIleNotFound -from datetime import datetime async def parse_file_id(message: "Message") -> Optional[FileId]: @@ -91,4 +91,4 @@ def get_name(media_msg: Union[Message, FileId]) -> str: def get_mimetype(media_msg: Message) -> str: media = get_media_from_message(media_msg) - return media.mime_type or "application/octet-stream" \ No newline at end of file + return media.mime_type or "application/octet-stream" diff --git a/WebStreamer/utils/keepalive.py b/WebStreamer/utils/keepalive.py index 6fa4d7a6..dc03dbbf 100644 --- a/WebStreamer/utils/keepalive.py +++ b/WebStreamer/utils/keepalive.py @@ -7,7 +7,7 @@ async def ping_server(): sleep_time = Var.PING_INTERVAL - logger.info("Started with {}s interval between pings".format(sleep_time)) + logger.info("Started with %ds interval between pings", sleep_time) while True: await asyncio.sleep(sleep_time) try: @@ -15,7 +15,7 @@ async def ping_server(): timeout=aiohttp.ClientTimeout(total=10) ) as session: async with session.get(Var.URL) as resp: - logger.info("Pinged server with response: {}".format(resp.status)) + logger.info("Pinged server with response: %d", resp.status) except TimeoutError: logger.warning("Couldn't connect to the site URL..") except Exception: diff --git a/WebStreamer/vars.py b/WebStreamer/vars.py index 2887743d..e4350e6b 100644 --- a/WebStreamer/vars.py +++ b/WebStreamer/vars.py @@ -27,10 +27,8 @@ class Var(object): if not 5 < HASH_LENGTH < 64: sys.exit("Hash length should be greater than 5 and less than 64") FQDN = str(environ.get("FQDN", BIND_ADDRESS)) - URL = "http{}://{}{}/".format( - "s" if HAS_SSL else "", FQDN, "" if NO_PORT else ":" + str(PORT) - ) + URL = f"http{"s" if HAS_SSL else ""}://{FQDN}{"" if NO_PORT else ":" + str(PORT)}/" KEEP_ALIVE = str(environ.get("KEEP_ALIVE", "0").lower()) in ("1", "true", "t", "yes", "y") DEBUG = str(environ.get("DEBUG", "0").lower()) in ("1", "true", "t", "yes", "y") USE_SESSION_FILE = str(environ.get("USE_SESSION_FILE", "0").lower()) in ("1", "true", "t", "yes", "y") - ALLOWED_USERS = [x.strip("@ ") for x in str(environ.get("ALLOWED_USERS", "") or "").split(",") if x.strip("@ ")] \ No newline at end of file + ALLOWED_USERS = [x.strip("@ ") for x in str(environ.get("ALLOWED_USERS", "") or "").split(",") if x.strip("@ ")] From 39792c60ea0643aaf4a8135eecca41d8ac9a3c04 Mon Sep 17 00:00:00 2001 From: Deekshith SH Date: Mon, 24 Mar 2025 22:31:10 +0530 Subject: [PATCH 5/8] Revert "Removed shortened link" This reverts commit bcf0d73af7830dc68ed8c0d3714f22ad702fbb1a. --- WebStreamer/bot/plugins/stream.py | 10 ++++++---- WebStreamer/server/stream_routes.py | 13 ++++++++++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/WebStreamer/bot/plugins/stream.py b/WebStreamer/bot/plugins/stream.py index 139d7fba..5e5c0fbb 100644 --- a/WebStreamer/bot/plugins/stream.py +++ b/WebStreamer/bot/plugins/stream.py @@ -1,12 +1,13 @@ # This file is a part of TG-FileStreamBot # Coding : Jyothis Jayanth [@EverythingSuckz] +from urllib.parse import quote_plus from pyrogram import filters, errors from pyrogram.enums.parse_mode import ParseMode from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton from WebStreamer.vars import Var from WebStreamer.bot import StreamBot, logger -from WebStreamer.utils import get_hash, get_mimetype +from WebStreamer.utils import get_hash, get_name, get_mimetype @StreamBot.on_message( @@ -29,21 +30,22 @@ async def media_receive_handler(_, m: Message): log_msg = await m.forward(chat_id=Var.BIN_CHANNEL) file_hash = get_hash(log_msg, Var.HASH_LENGTH) mimetype = get_mimetype(log_msg) - stream_link = f"{Var.URL}{log_msg.id}?hash={file_hash}" + stream_link = f"{Var.URL}{log_msg.id}/{quote_plus(get_name(m))}?hash={file_hash}" + short_link = f"{Var.URL}{file_hash}{log_msg.id}" logger.info("Generated link: %s for %s", stream_link, m.from_user.first_name) markup = [InlineKeyboardButton("Download", url=stream_link+"&d=true")] if set(mimetype.split("/")) & {"video","audio","pdf"}: markup.append(InlineKeyboardButton("Stream", url=stream_link)) try: await m.reply_text( - text=f"{stream_link}", + text=f"{stream_link}\n(shortened)", quote=True, parse_mode=ParseMode.HTML, reply_markup=InlineKeyboardMarkup([markup]), ) except errors.ButtonUrlInvalid: await m.reply_text( - text=f"{stream_link})", + text=f"{stream_link}\n(shortened)", quote=True, parse_mode=ParseMode.HTML, ) diff --git a/WebStreamer/server/stream_routes.py b/WebStreamer/server/stream_routes.py index 77ff26e2..384ec1aa 100644 --- a/WebStreamer/server/stream_routes.py +++ b/WebStreamer/server/stream_routes.py @@ -1,6 +1,7 @@ # Taken from megadlbot_oss # Thanks to Eyaadh +import re import time import math import logging @@ -35,11 +36,17 @@ async def root_route_handler(_): ) -@routes.get(r"/{message_id:\d+}", allow_head=True) +@routes.get(r"/{path:\S+}", allow_head=True) async def stream_handler(request: web.Request): try: - message_id = int(request.match_info["message_id"]) - secure_hash = request.rel_url.query.get("hash") + path = request.match_info["path"] + match = re.search(r"^([0-9a-f]{%s})(\d+)$" % (Var.HASH_LENGTH), path) + if match: + secure_hash = match.group(1) + message_id = int(match.group(2)) + else: + message_id = int(re.search(r"(\d+)(?:\/\S+)?", path).group(1)) + secure_hash = request.rel_url.query.get("hash") return await media_streamer(request, message_id, secure_hash) except InvalidHash as e: raise web.HTTPForbidden(text=e.message) From abc00f7614f1870325c28dc5154c96e4944e61d0 Mon Sep 17 00:00:00 2001 From: Deekshith SH Date: Mon, 24 Mar 2025 22:43:04 +0530 Subject: [PATCH 6/8] use dict --- WebStreamer/bot/__init__.py | 5 ++--- WebStreamer/utils/custom_dl.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/WebStreamer/bot/__init__.py b/WebStreamer/bot/__init__.py index 0e277a3f..bb89b30b 100644 --- a/WebStreamer/bot/__init__.py +++ b/WebStreamer/bot/__init__.py @@ -5,7 +5,6 @@ import os import os.path import logging -from typing import Dict from pyrogram import Client from ..vars import Var @@ -30,5 +29,5 @@ in_memory=not Var.USE_SESSION_FILE, ) -multi_clients: Dict[int, Client] = {} -work_loads: Dict[int, int] = {} +multi_clients: dict[int, Client] = {} +work_loads: dict[int, int] = {} diff --git a/WebStreamer/utils/custom_dl.py b/WebStreamer/utils/custom_dl.py index e11e4f0d..5b5b33a2 100644 --- a/WebStreamer/utils/custom_dl.py +++ b/WebStreamer/utils/custom_dl.py @@ -1,6 +1,6 @@ import asyncio import logging -from typing import AsyncGenerator, Dict, Union +from typing import AsyncGenerator, Union from pyrogram import Client, utils, raw from pyrogram.session import Session, Auth from pyrogram.errors import AuthBytesInvalid @@ -30,7 +30,7 @@ def __init__(self, client: Client): """ self.clean_timer = 30 * 60 self.client: Client = client - self.cached_file_ids: Dict[int, FileId] = {} + self.cached_file_ids: dict[int, FileId] = {} asyncio.create_task(self.clean_cache()) async def get_file_properties(self, message_id: int) -> FileId: From 7327b37b7690aded1d0620ecae65beec3e0e4033 Mon Sep 17 00:00:00 2001 From: Deekshith SH Date: Mon, 24 Mar 2025 23:05:56 +0530 Subject: [PATCH 7/8] Implement Rotating Logs Use rotating logs to prevent the log file from growing indefinitely and consuming excessive storage. --- WebStreamer/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WebStreamer/__main__.py b/WebStreamer/__main__.py index 5237f82e..73a10988 100644 --- a/WebStreamer/__main__.py +++ b/WebStreamer/__main__.py @@ -4,6 +4,7 @@ import sys import asyncio import logging +from logging import handlers from aiohttp import web from pyrogram import idle from WebStreamer import utils @@ -18,7 +19,7 @@ datefmt="%d/%m/%Y %H:%M:%S", format="[%(asctime)s][%(name)s][%(levelname)s] ==> %(message)s", handlers=[logging.StreamHandler(stream=sys.stdout), - logging.FileHandler("streambot.log", mode="a", encoding="utf-8")],) + handlers.RotatingFileHandler("streambot.log", mode="a", maxBytes=1048576*25, backupCount=2, encoding="utf-8")],) logging.getLogger("aiohttp").setLevel(logging.DEBUG if Var.DEBUG else logging.ERROR) logging.getLogger("pyrogram").setLevel(logging.INFO if Var.DEBUG else logging.ERROR) From e45f5e0592b5fb9cbfb3e721d313cc91a532fce4 Mon Sep 17 00:00:00 2001 From: Deekshith SH Date: Tue, 25 Mar 2025 10:09:56 +0530 Subject: [PATCH 8/8] Don't load body if HTTP method is HEAD --- WebStreamer/server/stream_routes.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/WebStreamer/server/stream_routes.py b/WebStreamer/server/stream_routes.py index 384ec1aa..ea4097d5 100644 --- a/WebStreamer/server/stream_routes.py +++ b/WebStreamer/server/stream_routes.py @@ -61,6 +61,7 @@ async def stream_handler(request: web.Request): class_cache = {} async def media_streamer(request: web.Request, message_id: int, secure_hash: str): + head: bool = request.method == "HEAD" range_header = request.headers.get("Range", 0) index = min(work_loads, key=work_loads.get) @@ -111,9 +112,12 @@ async def media_streamer(request: web.Request, message_id: int, secure_hash: str req_length = until_bytes - from_bytes + 1 part_count = math.ceil(until_bytes / chunk_size) - math.floor(offset / chunk_size) - body = tg_connect.yield_file( - file_id, index, offset, first_part_cut, last_part_cut, part_count, chunk_size - ) + if head: + body=None + else: + body = tg_connect.yield_file( + file_id, index, offset, first_part_cut, last_part_cut, part_count, chunk_size + ) mime_type = file_id.mime_type file_name = utils.get_name(file_id) disposition = "inline"