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..73a10988 100644
--- a/WebStreamer/__main__.py
+++ b/WebStreamer/__main__.py
@@ -4,13 +4,14 @@
import sys
import asyncio
import logging
-from .vars import Var
+from logging import handlers
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(
@@ -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)
@@ -43,10 +44,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 +64,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..bb89b30b 100644
--- a/WebStreamer/bot/__init__.py
+++ b/WebStreamer/bot/__init__.py
@@ -4,16 +4,16 @@
import os
import os.path
-from ..vars import Var
import logging
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 +29,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 0c0b368b..5e5c0fbb 100644
--- a/WebStreamer/bot/plugins/stream.py
+++ b/WebStreamer/bot/plugins/stream.py
@@ -1,14 +1,13 @@
# 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
+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_name, get_mimetype
@StreamBot.on_message(
@@ -30,25 +29,23 @@ 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}")
+ 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="{}\n(shortened)".format(
- stream_link, short_link
- ),
+ text=f"{stream_link}\n(shortened)",
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(
- text="{}\n\nshortened: {})".format(
- stream_link, short_link
- ),
+ text=f"{stream_link}\n(shortened)",
quote=True,
parse_mode=ParseMode.HTML,
)
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 94da4827..ea4097d5 100644
--- a/WebStreamer/server/stream_routes.py
+++ b/WebStreamer/server/stream_routes.py
@@ -5,7 +5,6 @@
import time
import math
import logging
-import secrets
import mimetypes
from aiohttp import web
from aiohttp.http_exceptions import BadStatusLine
@@ -62,30 +61,31 @@ 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)
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:
@@ -112,18 +112,21 @@ 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 = "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..f17a62bc 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 .custom_dl import ByteStreamer
\ No newline at end of file
+from .file_properties import get_hash, get_name, get_mimetype
+from .custom_dl import ByteStreamer
diff --git a/WebStreamer/utils/custom_dl.py b/WebStreamer/utils/custom_dl.py
index 7c89602f..5b5b33a2 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, 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")
@@ -31,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:
@@ -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 f44ccba1..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]:
@@ -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"
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("@ ")]
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