diff --git a/README.md b/README.md
index a7a79f9..3df31dc 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,5 @@
๐File Stream Bot
An open-source Python Telegram bot to transmit Telegram files over HTTP.
-
-
Demo Bot
## **๐ INDEX**
@@ -34,7 +32,7 @@ For Linux:
```
sudo apt-get update && sudo apt-get install -y python3.11 git pip
```
-For macOS:
+For MacOS:
```
brew install python@3.11 git
```
@@ -71,13 +69,13 @@ pip install -r requirements.txt
**The variables provided below should either be completed within the [config.py](https://github.com/TheCaduceus/FileStreamBot/blob/main/bot/config.py) file or configured as environment variables.**
* `API_ID`|`TELEGRAM_API_ID`: API ID of your Telegram account, can be obtained from [My Telegram](https://my.telegram.org). `int`
* `API_HASH`|`TELEGRAM_API_HASH`: API hash of your Telegram account, can be obtained from [My Telegram](https://my.telegram.org). `str`
-* `OWNER_ID`: ID of your Telegram account, can be obtained by sending **/info** to [@DrFileStreamBot](https://t.me/DrFileStreamBot). `int`
+* `OWNER_ID`: ID of your Telegram account, can be obtained by sending **/info** to [@DumpJsonBot](https://t.me/DumpJsonBot). `int`
* `ALLOWED_USER_IDS`: A list of Telegram account IDs (separated by spaces) that are permitted to use the bot. Leave this field empty to allow anyone to use it. `str`
* `BOT_USERNAME`|`TELEGRAM_BOT_USERNAME`: Username of your Telegram bot, create one using [@BotFather](https://t.me/BotFather). `str`
* `BOT_TOKEN`|`TELEGRAM_BOT_TOKEN`: Telegram API token of your bot, can be obtained from [@BotFather](https://t.me/BotFather). `str`
-* `CHANNEL_ID`|`TELEGRAM_CHANNEL_ID`: ID of the channel where bot will forward all files received from users, can be obtained by forwarding any message from channel to [@ShowJsonBot](https://t.me/ShowJsonBot) and then looking from `forward_from_chat` key. `int`
+* `CHANNEL_ID`|`TELEGRAM_CHANNEL_ID`: ID of the channel where bot will forward all files received from users, can be obtained by forwarding any message from channel to [@DumpJsonBot](https://t.me/DumpJsonBot) and then looking from `forward_from_chat` key. `int`
* `BOT_WORKERS`: Number of updates bot should process from Telegram at once, by default to 10 updates. `int`
-* `SECRET_CODE_LENGTH`: Number of characters that file code should contain, by default to 12 characters. `int`
+* `SECRET_CODE_LENGTH`: Number of characters that file code should contain, by default to 24 characters. `int`
* `BASE_URL`: Base URL that bot should use while generating file links, can be FQDN and by default to `127.0.0.1`. `str`
* `BIND_ADDRESS`: Bind address for web server, by default to `0.0.0.0` to run on all possible addresses. `str`
* `PORT`: Port for web server to run on, by default to `8080`. `int`
diff --git a/bot/__init__.py b/bot/__init__.py
index e17e9ea..05fc31e 100644
--- a/bot/__init__.py
+++ b/bot/__init__.py
@@ -1,15 +1,19 @@
-from telethon import TelegramClient
+from hydrogram import Client
from logging import getLogger
from logging.config import dictConfig
from .config import Telegram, LOGGER_CONFIG_JSON
dictConfig(LOGGER_CONFIG_JSON)
-version = 1.6
+version = 1.7
logger = getLogger('bot')
-TelegramBot = TelegramClient(
- session='bot',
- api_id=Telegram.API_ID,
- api_hash=Telegram.API_HASH
+TelegramBot = Client(
+ name ='bot',
+ api_id = Telegram.API_ID,
+ api_hash = Telegram.API_HASH,
+ bot_token = Telegram.BOT_TOKEN,
+ plugins = {'root': 'bot/plugins'},
+ workers = Telegram.BOT_WORKERS,
+ max_concurrent_transmissions = 1000
)
diff --git a/bot/__main__.py b/bot/__main__.py
index 7c8d32e..1ecf629 100644
--- a/bot/__main__.py
+++ b/bot/__main__.py
@@ -1,22 +1,6 @@
-from importlib import import_module
-from pathlib import Path
-from bot import TelegramBot, logger
-from bot.config import Telegram
+from bot import TelegramBot
from bot.server import server
-def load_plugins():
- count = 0
- for path in Path('bot/plugins').rglob('*.py'):
- import_module(f'bot.plugins.{path.stem}')
- count += 1
- logger.info(f'Loaded {count} {"plugins" if count > 1 else "plugin"}.')
-
if __name__ == '__main__':
- logger.info('initializing...')
TelegramBot.loop.create_task(server.serve())
- TelegramBot.start(bot_token=Telegram.BOT_TOKEN)
- logger.info('Telegram client is now started.')
- logger.info('Loading bot plugins...')
- load_plugins()
- logger.info('Bot is now ready!')
- TelegramBot.run_until_disconnected()
+ TelegramBot.run()
diff --git a/bot/config.py b/bot/config.py
index 49ffd6e..505d1f9 100644
--- a/bot/config.py
+++ b/bot/config.py
@@ -1,14 +1,15 @@
from os import environ as env
class Telegram:
- API_ID = int(env.get("TELEGRAM_API_ID", 1234))
+ API_ID = int(env.get("TELEGRAM_API_ID", 12345))
API_HASH = env.get("TELEGRAM_API_HASH", "xyz")
- OWNER_ID = int(env.get("OWNER_ID", 1234567890))
+ OWNER_ID = int(env.get("OWNER_ID", 5530237028))
ALLOWED_USER_IDS = env.get("ALLOWED_USER_IDS", "").split()
BOT_USERNAME = env.get("TELEGRAM_BOT_USERNAME", "BotFather")
- BOT_TOKEN = env.get("TELEGRAM_BOT_TOKEN", "1234:abcd")
- CHANNEL_ID = int(env.get("TELEGRAM_CHANNEL_ID", -1001234567890))
- SECRET_CODE_LENGTH = int(env.get("SECRET_CODE_LENGTH", 12))
+ BOT_TOKEN = env.get("TELEGRAM_BOT_TOKEN", "1234567:xyz")
+ BOT_WORKERS = env.get("BOT_WORKERS", 10)
+ CHANNEL_ID = int(env.get("TELEGRAM_CHANNEL_ID", -100123456789))
+ SECRET_CODE_LENGTH = int(env.get("SECRET_CODE_LENGTH", 24))
class Server:
BASE_URL = env.get("BASE_URL", "http://127.0.0.1:8080")
@@ -47,6 +48,10 @@ LOGGER_CONFIG_JSON = {
'bot': {
'level': 'INFO',
'handlers': ['file_handler', 'stream_handler']
+ },
+ 'hydrogram': {
+ 'level': 'INFO',
+ 'handlers': ['file_handler', 'stream_handler']
}
}
}
diff --git a/bot/modules/decorators.py b/bot/modules/decorators.py
index 5a34555..d065e3b 100644
--- a/bot/modules/decorators.py
+++ b/bot/modules/decorators.py
@@ -1,20 +1,16 @@
-from telethon.events import NewMessage, CallbackQuery
-from typing import Callable
+from hydrogram import Client
+from hydrogram.types import Message, CallbackQuery
+from typing import Union, Callable
from functools import wraps
from bot.config import Telegram
-def verify_user(private: bool = False):
-
- def decorator(func: Callable):
- @wraps(func)
- async def wrapper(update: NewMessage.Event | CallbackQuery.Event):
- if private and not update.is_private:
- return
+def verify_user(func: Callable):
- chat_id = str(update.chat_id)
+ @wraps(func)
+ async def decorator(client: Client, update: Union[Message, CallbackQuery]):
+ chat_id = str(update.from_user.id if update.from_user else update.chat.id)
- if not Telegram.ALLOWED_USER_IDS or chat_id in Telegram.ALLOWED_USER_IDS:
- return await func(update)
-
- return wrapper
+ if not Telegram.ALLOWED_USER_IDS or chat_id in Telegram.ALLOWED_USER_IDS:
+ return await func(client, update)
+
return decorator
diff --git a/bot/modules/static.py b/bot/modules/static.py
index dfd9865..c93d720 100644
--- a/bot/modules/static.py
+++ b/bot/modules/static.py
@@ -1,35 +1,17 @@
WelcomeText = \
"""
-Hi **%(first_name)s**, send me a file or add me as an admin to any channel to instantly generate file links.
-
-Add me to your channel to instantly generate links for any downloadable media. Once received, I will automatically attach appropriate buttons to the post containing the URL. If you want me to ignore a given post, you can insert `#pass` in the post.
-
-- /start to get this message.
-- /info to get user info.
-- /log to get bot logs. (admin only!)
+Hi **%(first_name)s**, send me a file to instantly generate file links.
"""
-UserInfoText = \
+PrivacyText = \
"""
-**First Name:**
-`{sender.first_name}`
-
-**Last Name:**
-`{sender.last_name}`
-
-**User ID:**
-`{sender.id}`
-
-**Username:**
-`@{sender.username}`
+This bot securely stores your files to deliver its service.
"""
FileLinksText = \
"""
**Download Link:**
`%(dl_link)s`
-**Telegram File:**
-`%(tg_link)s`
"""
MediaLinksText = \
@@ -38,8 +20,6 @@ MediaLinksText = \
`%(dl_link)s`
**Stream Link:**
`%(stream_link)s`
-**Telegram File:**
-`%(tg_link)s`
"""
InvalidQueryText = \
@@ -61,8 +41,3 @@ InvalidPayloadText = \
"""
Invalid payload.
"""
-
-MediaTypeNotSupportedText = \
-"""
-Sorry, this media type is not supported.
-"""
diff --git a/bot/modules/telegram.py b/bot/modules/telegram.py
index 2867d54..f50007c 100644
--- a/bot/modules/telegram.py
+++ b/bot/modules/telegram.py
@@ -1,5 +1,4 @@
-from telethon.events import NewMessage
-from telethon.tl.custom import Message
+from hydrogram.types import Message
from datetime import datetime
from mimetypes import guess_type
from bot import TelegramBot
@@ -10,55 +9,47 @@ async def get_message(message_id: int) -> Message | None:
message = None
try:
- message = await TelegramBot.get_messages(Telegram.CHANNEL_ID, ids=message_id)
+ message = await TelegramBot.get_messages(Telegram.CHANNEL_ID, message_ids=message_id)
+ if message.empty: message = None
except Exception:
pass
return message
-async def send_message(message:Message, send_to:int = Telegram.CHANNEL_ID) -> Message:
- return await TelegramBot.send_message(entity=send_to, message=message)
+async def send_message(msg: Message, send_to: int = Telegram.CHANNEL_ID) -> Message:
+ return await TelegramBot.send_message(entity=send_to, message=msg)
-def filter_files(update: NewMessage.Event | Message):
- return bool(
- (
- update.document
- or update.photo
- or update.video
- or update.video_note
- or update.audio
- or update.gif
- )
- and not update.sticker
+def get_file_properties(msg: Message):
+ attributes = (
+ 'document',
+ 'video',
+ 'audio',
+ 'voice',
+ 'photo',
+ 'video_note'
)
+ for attribute in attributes:
+ media = getattr(msg, attribute, None)
+ if media:
+ file_type = attribute
+ break
-def get_file_properties(message: Message):
- file_name = message.file.name
- file_size = message.file.size or 0
- mime_type = message.file.mime_type
+ if not media: abort(400, 'Unknown file type.')
+
+ file_name = getattr(media, 'file_name', None)
+ file_size = getattr(media, 'file_size', 0)
if not file_name:
- attributes = {
+ file_format = {
'video': 'mp4',
'audio': 'mp3',
'voice': 'ogg',
'photo': 'jpg',
'video_note': 'mp4'
- }
-
- for attribute in attributes:
- media = getattr(message, attribute, None)
- if media:
- file_type, file_format = attribute, attributes[attribute]
- break
-
- if not media:
- abort(400, 'Invalid media type.')
-
+ }.get(file_type)
date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
file_name = f'{file_type}-{date}.{file_format}'
- if not mime_type:
- mime_type = guess_type(file_name)[0] or 'application/octet-stream'
-
+ mime_type = guess_type(file_name)[0] or 'application/octet-stream'
+
return file_name, file_size, mime_type
diff --git a/bot/plugins/callback.py b/bot/plugins/callback.py
index a8e630f..dee638a 100644
--- a/bot/plugins/callback.py
+++ b/bot/plugins/callback.py
@@ -1,24 +1,31 @@
-from telethon.events import CallbackQuery
+from hydrogram.types import CallbackQuery
from bot import TelegramBot
from bot.modules.decorators import verify_user
from bot.modules.static import *
from bot.modules.telegram import get_message
-@TelegramBot.on(CallbackQuery(pattern=r'^rm_'))
-@verify_user(private=True)
-async def delete_file(event: CallbackQuery.Event):
- query_data = event.query.data.decode().split('_')
+@TelegramBot.on_callback_query()
+@verify_user
+async def manage_callback(bot, q: CallbackQuery):
+ query = q.data
- if len(query_data) != 3:
- return await event.answer(InvalidQueryText, alert=True)
+ if query.startswith('rm_'):
+ sq = query.split('_')
- message = await get_message(int(query_data[1]))
+ if len(sq) != 3:
+ return await q.answer(InvalidQueryText, show_alert=True)
- if not message:
- return await event.answer(MessageNotExist, alert=True)
- if query_data[2] != message.raw_text:
- return await event.answer(InvalidQueryText, alert=True)
+ message = await get_message(int(sq[1]))
- await message.delete()
+ if not message:
+ return await q.answer(MessageNotExist, show_alert=True)
+
+ sc = message.caption.split('/')
- return await event.answer(LinkRevokedText, alert=True)
+ if q.from_user.id != int(sc[1]) or sq[2] != sc[0]:
+ return await q.answer(InvalidQueryText, show_alert=True)
+
+ await message.delete()
+ await q.answer(LinkRevokedText, show_alert=True)
+ else:
+ await q.answer(InvalidQueryText, show_alert=True)
diff --git a/bot/plugins/commands.py b/bot/plugins/commands.py
index d00e648..4c76db1 100644
--- a/bot/plugins/commands.py
+++ b/bot/plugins/commands.py
@@ -1,28 +1,18 @@
-from telethon import Button
-from telethon.events import NewMessage
-from telethon.tl.custom.message import Message
+from hydrogram import filters
+from hydrogram.types import Message
from bot import TelegramBot
-from bot.config import Telegram
from bot.modules.static import *
from bot.modules.decorators import verify_user
-@TelegramBot.on(NewMessage(incoming=True, pattern=r'^/start$'))
-@verify_user(private=True)
-async def welcome(event: NewMessage.Event | Message):
- await event.reply(
- message=WelcomeText % {'first_name': event.sender.first_name},
- buttons=[
- [
- Button.url('Add to Channel', f'https://t.me/{Telegram.BOT_USERNAME}?startchannel&admin=post_messages+edit_messages+delete_messages')
- ]
- ]
+@TelegramBot.on_message(filters.command(['start', 'help']) & filters.private)
+@verify_user
+async def start_command(_, msg: Message):
+ await msg.reply(
+ text=WelcomeText % {'first_name': msg.from_user.first_name},
+ quote=True
)
-@TelegramBot.on(NewMessage(incoming=True, pattern=r'^/info$'))
-@verify_user(private=True)
-async def user_info(event: Message):
- await event.reply(UserInfoText.format(sender=event.sender))
-
-@TelegramBot.on(NewMessage(chats=Telegram.OWNER_ID, incoming=True, pattern=r'^/log$'))
-async def send_log(event: NewMessage.Event | Message):
- await event.reply(file='event-log.txt')
+@TelegramBot.on_message(filters.command('privacy') & filters.private)
+@verify_user
+async def privacy_command(_, msg: Message):
+ await msg.reply(text=PrivacyText, quote=True)
diff --git a/bot/plugins/deeplinks.py b/bot/plugins/deeplinks.py
index 9e3c56e..eb6a004 100644
--- a/bot/plugins/deeplinks.py
+++ b/bot/plugins/deeplinks.py
@@ -1,24 +1,25 @@
-from telethon.events import NewMessage
-from telethon.tl.custom import Message
-from bot import TelegramBot
-from bot.modules.decorators import verify_user
-from bot.modules.telegram import get_message, send_message
-from bot.modules.static import *
+# Currently not in use.
-@TelegramBot.on(NewMessage(incoming=True, pattern=r'^/start file_'))
-@verify_user(private=True)
-async def send_file(event: NewMessage.Event | Message):
- payload = event.raw_text.split()[-1].split('_')
+# from hydrogram.types import Message
+# from bot import TelegramBot
+# from bot.modules.decorators import verify_user
+# from bot.modules.telegram import get_message, send_message
+# from bot.modules.static import *
- if len(payload) != 3:
- return await event.reply(InvalidPayloadText)
-
- message = await get_message(int(payload[1]))
+# async def deeplinks(msg: Message, payload: str):
+# if payload.startswith('file_'):
+# sp = payload.split('_')
- if not message:
- return await event.reply(MessageNotExist)
- if payload[2] != message.raw_text:
- return await event.reply(InvalidPayloadText)
-
- message.raw_text = ''
- await send_message(message, send_to=event.chat_id)
+# if len(sp) != 3:
+# return await msg.reply(InvalidPayloadText, quote=True)
+
+# message = await get_message(int(sp[1]))
+
+# if not message:
+# return await msg.reply(MessageNotExist)
+# if sp[2] != message.caption:
+# return await msg.reply(InvalidPayloadText, quote=True)
+
+# await message.copy(chat_id=msg.from_user.id, caption="")
+# else:
+# await msg.reply(InvalidPayloadText, quote=True)
diff --git a/bot/plugins/files.py b/bot/plugins/files.py
index d5a4e95..abd8161 100644
--- a/bot/plugins/files.py
+++ b/bot/plugins/files.py
@@ -1,95 +1,60 @@
-from telethon import Button
-from telethon.events import NewMessage
-from telethon.errors import MessageAuthorRequiredError, MessageNotModifiedError, MessageIdInvalidError
-from telethon.tl.custom import Message
+from hydrogram import filters
+from hydrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
from secrets import token_hex
from bot import TelegramBot
from bot.config import Telegram, Server
from bot.modules.decorators import verify_user
-from bot.modules.telegram import send_message, filter_files
from bot.modules.static import *
-@TelegramBot.on(NewMessage(incoming=True, func=filter_files))
-@verify_user(private=True)
-async def user_file_handler(event: NewMessage.Event | Message):
+@TelegramBot.on_message(
+ filters.private
+ & (
+ filters.document
+ | filters.video
+ | filters.video_note
+ | filters.audio
+ | filters.voice
+ | filters.photo
+ )
+)
+@verify_user
+async def handle_user_file(_, msg: Message):
+ sender_id = msg.from_user.id
secret_code = token_hex(Telegram.SECRET_CODE_LENGTH)
- event.message.text = f'`{secret_code}`'
- message = await send_message(event.message)
- message_id = message.id
+ file = await msg.copy(
+ chat_id=Telegram.CHANNEL_ID,
+ caption=f'||{secret_code}/{sender_id}||'
+ )
+ file_id = file.id
+ dl_link = f'{Server.BASE_URL}/dl/{file_id}?code={secret_code}'
- dl_link = f'{Server.BASE_URL}/dl/{message_id}?code={secret_code}'
- tg_link = f'{Server.BASE_URL}/file/{message_id}?code={secret_code}'
- deep_link = f'https://t.me/{Telegram.BOT_USERNAME}?start=file_{message_id}_{secret_code}'
-
- if (event.document and 'video' in event.document.mime_type) or event.video:
- stream_link = f'{Server.BASE_URL}/stream/{message_id}?code={secret_code}'
- await event.reply(
- message= MediaLinksText % {'dl_link': dl_link, 'tg_link': tg_link, 'tg_link': tg_link, 'stream_link': stream_link},
- buttons=[
+ if (msg.document and 'video' in msg.document.mime_type) or msg.video:
+ stream_link = f'{Server.BASE_URL}/stream/{file_id}?code={secret_code}'
+ await msg.reply(
+ text=MediaLinksText % {'dl_link': dl_link, 'stream_link': stream_link},
+ quote=True,
+ reply_markup=InlineKeyboardMarkup(
[
- Button.url('Download', dl_link),
- Button.url('Stream', stream_link)
- ],
- [
- Button.url('Get File', deep_link),
- Button.inline('Revoke', f'rm_{message_id}_{secret_code}')
- ]
- ]
- )
- else:
- await event.reply(
- message=FileLinksText % {'dl_link': dl_link, 'tg_link': tg_link},
- buttons=[
- [
- Button.url('Download', dl_link),
- Button.url('Get File', deep_link)
- ],
- [
- Button.inline('Revoke', f'rm_{message_id}_{secret_code}')
- ]
- ]
- )
-
-@TelegramBot.on(NewMessage(incoming=True, func=filter_files, forwards=False))
-@verify_user()
-async def channel_file_handler(event: NewMessage.Event | Message):
- if event.raw_text and '#pass' in event.raw_text:
- return
-
- secret_code = token_hex(Telegram.SECRET_CODE_LENGTH)
- event.message.text = f"`{secret_code}`"
- message = await send_message(event.message)
- message_id = message.id
-
- dl_link = f"{Server.BASE_URL}/dl/{message_id}?code={secret_code}"
- tg_link = f"{Server.BASE_URL}/file/{message_id}?code={secret_code}"
-
- if (event.document and "video" in event.document.mime_type) or event.video:
- stream_link = f"{Server.BASE_URL}/stream/{message_id}?code={secret_code}"
-
- try:
- await event.edit(
- buttons=[
- [Button.url("Download", dl_link), Button.url("Stream", stream_link)],
- [Button.url("Get File", tg_link)],
+ [
+ InlineKeyboardButton('Download', url=dl_link),
+ InlineKeyboardButton('Stream', url=stream_link)
+ ],
+ [
+ InlineKeyboardButton('Revoke', callback_data=f'rm_{file_id}_{secret_code}')
+ ]
]
)
- except (
- MessageAuthorRequiredError,
- MessageIdInvalidError,
- MessageNotModifiedError,
- ):
- pass
+ )
else:
- try:
- await event.edit(
- buttons=[
- [Button.url("Download", dl_link), Button.url("Get File", tg_link)]
+ await msg.reply(
+ text=FileLinksText % {'dl_link': dl_link},
+ quote=True,
+ reply_markup=InlineKeyboardMarkup(
+ [
+ [
+ InlineKeyboardButton('Download', url=dl_link),
+ InlineKeyboardButton('Revoke', callback_data=f'rm_{file_id}_{secret_code}')
+ ]
]
)
- except (
- MessageAuthorRequiredError,
- MessageIdInvalidError,
- MessageNotModifiedError,
- ):
- pass
+ )
diff --git a/bot/server/__init__.py b/bot/server/__init__.py
index 13a8f36..ab4e2f7 100644
--- a/bot/server/__init__.py
+++ b/bot/server/__init__.py
@@ -2,12 +2,12 @@ from quart import Quart
from uvicorn import Server as UvicornServer, Config
from logging import getLogger
from bot.config import Server, LOGGER_CONFIG_JSON
-
from . import main, error
logger = getLogger('uvicorn')
instance = Quart(__name__)
instance.config['RESPONSE_TIMEOUT'] = None
+instance.config['MAX_CONTENT_LENGTH'] = 999999999999999
@instance.before_serving
async def before_serve():
diff --git a/bot/server/main.py b/bot/server/main.py
index b736778..d64aeba 100644
--- a/bot/server/main.py
+++ b/bot/server/main.py
@@ -1,8 +1,9 @@
from quart import Blueprint, Response, request, render_template, redirect
+from math import ceil
+from re import match as re_match
from .error import abort
from bot import TelegramBot
from bot.config import Telegram, Server
-from math import ceil, floor
from bot.modules.telegram import get_message, get_file_properties
bp = Blueprint('main', __name__)
@@ -13,73 +14,70 @@ async def home():
@bp.route('/dl/')
async def transmit_file(file_id):
- file = await get_message(message_id=int(file_id)) or abort(404)
+ file = await get_message(file_id) or abort(404)
code = request.args.get('code') or abort(401)
- range_header = request.headers.get('Range', 0)
+ range_header = request.headers.get('Range')
- if code != file.raw_text:
+ if code != file.caption.split('/')[0]:
abort(403)
file_name, file_size, mime_type = get_file_properties(file)
-
+
+ start = 0
+ end = file_size - 1
+ chunk_size = 1 * 1024 * 1024 # 1 MB
+
if range_header:
- from_bytes, until_bytes = range_header.replace("bytes=", "").split("-")
- from_bytes = int(from_bytes)
- until_bytes = int(until_bytes) if until_bytes else file_size - 1
- else:
- from_bytes = 0
- until_bytes = file_size - 1
+ range_match = re_match(r'bytes=(\d+)-(\d*)', range_header)
+ if range_match:
+ start = int(range_match.group(1))
+ end = int(range_match.group(2)) if range_match.group(2) else file_size - 1
+ if start > end or start >= file_size:
+ abort(416, 'Requested range not satisfiable')
+ else:
+ abort(400, 'Invalid Range header')
- if (until_bytes > file_size) or (from_bytes < 0) or (until_bytes < from_bytes):
- abort(416, 'Invalid range.')
+ offset_chunks = start // chunk_size
+ total_bytes_to_stream = end - start + 1
+ chunks_to_stream = ceil(total_bytes_to_stream / chunk_size)
- chunk_size = 1024 * 1024
- until_bytes = min(until_bytes, file_size - 1)
-
- offset = from_bytes - (from_bytes % chunk_size)
- first_part_cut = from_bytes - offset
- last_part_cut = until_bytes % chunk_size + 1
-
- req_length = until_bytes - from_bytes + 1
- part_count = ceil(until_bytes / chunk_size) - floor(offset / chunk_size)
-
+ content_length = total_bytes_to_stream
headers = {
- "Content-Type": f"{mime_type}",
- "Content-Range": f"bytes {from_bytes}-{until_bytes}/{file_size}",
- "Content-Length": str(req_length),
- "Content-Disposition": f'attachment; filename="{file_name}"',
- "Accept-Ranges": "bytes",
- }
+ 'Content-Type': mime_type,
+ 'Content-Disposition': f'attachment; filename={file_name}',
+ 'Content-Range': f'bytes {start}-{end}/{file_size}',
+ 'Accept-Ranges': 'bytes',
+ 'Content-Length': str(content_length),
+ }
+ status_code = 206 if range_header else 200
- async def file_generator():
- current_part = 1
- async for chunk in TelegramBot.iter_download(file, offset=offset, chunk_size=chunk_size, stride=chunk_size, file_size=file_size):
- if not chunk:
- break
- elif part_count == 1:
- yield chunk[first_part_cut:last_part_cut]
- elif current_part == 1:
- yield chunk[first_part_cut:]
- elif current_part == part_count:
- yield chunk[:last_part_cut]
- else:
- yield chunk
+ async def file_stream():
+ bytes_streamed = 0
+ chunk_index = 0
+ async for chunk in TelegramBot.stream_media(
+ file,
+ offset=offset_chunks,
+ limit=chunks_to_stream,
+ ):
+ if chunk_index == 0: # Trim the first chunk if necessary
+ trim_start = start % chunk_size
+ if trim_start > 0:
+ chunk = chunk[trim_start:]
- current_part += 1
-
- if current_part > part_count:
+ remaining_bytes = content_length - bytes_streamed
+ if remaining_bytes <= 0:
break
- return Response(file_generator(), headers=headers, status=206 if range_header else 200)
+ if len(chunk) > remaining_bytes: # Trim the last chunk if necessary
+ chunk = chunk[:remaining_bytes]
+
+ yield chunk
+ bytes_streamed += len(chunk)
+ chunk_index += 1
+
+ return Response(file_stream(), headers=headers, status=status_code)
@bp.route('/stream/')
async def stream_file(file_id):
code = request.args.get('code') or abort(401)
-
return await render_template('player.html', mediaLink=f'{Server.BASE_URL}/dl/{file_id}?code={code}')
-
-@bp.route('/file/')
-async def file_deeplink(file_id):
- code = request.args.get('code') or abort(401)
-
- return redirect(f'https://t.me/{Telegram.BOT_USERNAME}?start=file_{file_id}_{code}')
diff --git a/requirements.txt b/requirements.txt
index 960ce6d..1039ad5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-telethon
-cryptg
+hydrogram
+tgcrypto
quart
uvicorn