This commit is contained in:
Wrench
2021-04-16 19:37:44 +05:30
parent bf196f6476
commit b5fdac83fe
12 changed files with 468 additions and 661 deletions

2
WebStreamer/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
# This file is a part of TG-FileStreamBot
# Coding : Jyothis Jayanth [@EverythingSuckz]

59
WebStreamer/__main__.py Normal file
View File

@@ -0,0 +1,59 @@
# This file is a part of TG-FileStreamBot
# Coding : Jyothis Jayanth [@EverythingSuckz]
import os
import sys
import glob
import asyncio
import logging
import importlib
from pathlib import Path
from pyrogram import idle
from .bot import StreamBot
from .vars import Var
from aiohttp import web
from .server import web_server
ppath = f"WebStreamer/bot/plugins/*.py"
files = glob.glob(ppath)
loop = asyncio.get_event_loop()
async def start_services():
print('\n')
print('------------------- Initalizing Telegram Bot -------------------')
await StreamBot.start()
print('\n')
print('---------------------- DONE ----------------------')
print('\n')
print('------------------- Importing -------------------')
for name in files:
with open(name) as a:
patt = Path(a.name)
plugin_name = patt.stem.replace(".py", "")
plugins_dir = Path(f"WebStreamer/bot/plugins/{plugin_name}.py")
import_path = ".plugins.{}".format(plugin_name)
spec = importlib.util.spec_from_file_location(import_path, plugins_dir)
load = importlib.util.module_from_spec(spec)
spec.loader.exec_module(load)
sys.modules["WebStreamer.bot.plugins." + plugin_name] = load
print("Imported => " + plugin_name)
print('\n')
print('------------------- Initalizing Web Server -------------------')
app = web.AppRunner(await web_server())
await app.setup()
bind_address = "0.0.0.0" if Var.ENV else Var.FQDN
await web.TCPSite(app, bind_address, Var.PORT).start()
print('\n')
print('----------------------- Service Started -----------------------')
print(' bot =>> {}'.format((await StreamBot.get_me()).first_name))
print(' server ip =>> {}:{}'.format(bind_address, Var.PORT))
print('---------------------------------------------------------------')
await idle()
if __name__ == '__main__':
try:
loop.run_until_complete(start_services())
except KeyboardInterrupt:
print('----------------------- Service Stopped -----------------------')

View File

@@ -0,0 +1,14 @@
# This file is a part of TG-FileStreamBot
# Coding : Jyothis Jayanth [@EverythingSuckz]
from pyrogram import Client
from ..vars import Var
StreamBot = Client(
session_name= 'Web Streamer',
api_id=Var.API_ID,
api_hash=Var.API_HASH,
bot_token=Var.BOT_TOKEN,
sleep_threshold=Var.SLEEP_THRESHOLD,
workers=Var.WORKERS
)

View File

@@ -0,0 +1,21 @@
# This file is a part of TG-FileStreamBot
# Coding : Jyothis Jayanth [@EverythingSuckz]
from WebStreamer.bot import StreamBot
from WebStreamer.vars import Var
from pyrogram import filters, emoji
from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
@StreamBot.on_message(filters.command(['start', 'help']))
async def start(b, m):
await m.reply('Hi, Send me a file to get an instant stream link.',
reply_markup=InlineKeyboardMarkup(
[
[
InlineKeyboardButton(
f'{emoji.STAR} Source {emoji.STAR}',
url='https://github.com/EverythingSuckz/TG-FileStreamBot'
)
]
]
))

View File

@@ -0,0 +1,19 @@
# This file is a part of TG-FileStreamBot
# Coding : Jyothis Jayanth [@EverythingSuckz]
from WebStreamer.bot import StreamBot
from WebStreamer.vars import Var
from pyrogram import filters, Client, emoji
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
@StreamBot.on_message(filters.private & (filters.document | filters.video | filters.audio), group=4)
async def media_receive_handler(c: Client, m: Message):
log_msg = await m.copy(chat_id=Var.BIN_CHANNEL)
stream_link = "https://{}/{}".format(Var.FQDN, log_msg.message_id) if Var.ON_HEROKU else \
"http://{}:{}/{}".format(Var.FQDN,
Var.PORT,
log_msg.message_id)
await m.reply_text(
text="`{}`".format(stream_link)
)

View File

@@ -0,0 +1,13 @@
# Taken from megadlbot_oss <https://github.com/eyaadh/megadlbot_oss/blob/master/mega/webserver/__init__.py>
# Thanks to Eyaadh <https://github.com/eyaadh>
# This file is a part of TG-FileStreamBot
# Coding : Jyothis Jayanth [@EverythingSuckz]
from aiohttp import web
from .stream_routes import routes
async def web_server():
web_app = web.Application(client_max_size=30000000)
web_app.add_routes(routes)
return web_app

View File

@@ -0,0 +1,77 @@
# Taken from megadlbot_oss <https://github.com/eyaadh/megadlbot_oss/blob/master/mega/webserver/routes.py>
# Thanks to Eyaadh <https://github.com/eyaadh>
import math
import logging
import secrets
import mimetypes
from ..vars import Var
from aiohttp import web
from ..bot import StreamBot
from ..utils.custom_dl import TGCustomYield, chunk_size, offset_fix
routes = web.RouteTableDef()
@routes.get("/")
async def root_route_handler(request):
bot_details = await StreamBot.get_me()
return web.json_response({"status": "running",
"server_permission": "Open",
"bot_associated_w": bot_details.username})
@routes.get("/{message_id}")
async def stream_handler(request):
try:
message_id = int(request.match_info['message_id'])
return await media_streamer(request, message_id)
except ValueError as e:
logging.error(e)
raise web.HTTPNotFound
async def media_streamer(request, message_id: int):
range_header = request.headers.get('Range', 0)
media_msg = await StreamBot.get_messages(Var.BIN_CHANNEL, message_id)
file_properties = await TGCustomYield().generate_file_properties(media_msg)
file_size = file_properties.file_size
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 = request.http_range.start or 0
until_bytes = request.http_range.stop or file_size - 1
req_length = until_bytes - from_bytes
new_chunk_size = await chunk_size(req_length)
offset = await offset_fix(from_bytes, new_chunk_size)
first_part_cut = from_bytes - offset
last_part_cut = (until_bytes % new_chunk_size) + 1
part_count = math.ceil(req_length / new_chunk_size)
body = TGCustomYield().yield_file(media_msg, offset, first_part_cut, last_part_cut, part_count,
new_chunk_size)
file_name = file_properties.file_name if file_properties.file_name \
else f"{secrets.token_hex(2)}.jpeg"
mime_type = file_properties.mime_type if file_properties.mime_type \
else f"{mimetypes.guess_type(file_name)}"
return_resp = web.Response(
status=206 if range_header else 200,
body=body,
headers={
"Content-Type": mime_type,
"Content-Range": f"bytes {from_bytes}-{until_bytes}/{file_size}",
"Content-Disposition": f'attachment; filename="{file_name}"',
"Accept-Ranges": "bytes",
}
)
if return_resp.status == 200:
return_resp.headers.add("Content-Length", str(file_size))
return return_resp

View File

@@ -0,0 +1,2 @@
# This file is a part of TG-FileStreamBot
# Coding : Jyothis Jayanth [@EverythingSuckz]

View File

@@ -0,0 +1,233 @@
# Taken from megadlbot_oss <https://github.com/eyaadh/megadlbot_oss/blob/master/mega/telegram/utils/custom_download.py>
# Thanks to Eyaadh <https://github.com/eyaadh>
import math
from typing import Union
from pyrogram.types import Message
from ..bot import StreamBot
from pyrogram import Client, utils, raw
from pyrogram.session import Session, Auth
from pyrogram.errors import AuthBytesInvalid
from pyrogram.file_id import FileId, FileType, ThumbnailSource
async def chunk_size(length):
return 2 ** max(min(math.ceil(math.log2(length / 1024)), 10), 2) * 1024
async def offset_fix(offset, chunksize):
offset -= offset % chunksize
return offset
class TGCustomYield:
def __init__(self):
""" A custom method to stream files from telegram.
functions:
generate_file_properties: returns the properties for a media on a specific message contained in FileId class.
generate_media_session: returns the media session for the DC that contains the media file on the message.
yield_file: yield a file from telegram servers for streaming.
"""
self.main_bot = StreamBot
@staticmethod
async def generate_file_properties(msg: Message):
error_message = "This message doesn't contain any downloadable media"
available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note")
if isinstance(msg, Message):
for kind in available_media:
media = getattr(msg, kind, None)
if media is not None:
break
else:
raise ValueError(error_message)
else:
media = msg
if isinstance(media, str):
file_id_str = media
else:
file_id_str = media.file_id
file_id_obj = FileId.decode(file_id_str)
# The below lines are added to avoid a break in routes.py
setattr(file_id_obj, "file_size", getattr(media, "file_size", 0))
setattr(file_id_obj, "mime_type", getattr(media, "mime_type", ""))
setattr(file_id_obj, "file_name", getattr(media, "file_name", ""))
return file_id_obj
async def generate_media_session(self, client: Client, msg: Message):
data = await self.generate_file_properties(msg)
media_session = client.media_sessions.get(data.dc_id, None)
if media_session is None:
if data.dc_id != await client.storage.dc_id():
media_session = Session(
client, data.dc_id, await Auth(client, data.dc_id, await client.storage.test_mode()).create(),
await client.storage.test_mode(), is_media=True
)
await media_session.start()
for _ in range(3):
exported_auth = await client.send(
raw.functions.auth.ExportAuthorization(
dc_id=data.dc_id
)
)
try:
await media_session.send(
raw.functions.auth.ImportAuthorization(
id=exported_auth.id,
bytes=exported_auth.bytes
)
)
except AuthBytesInvalid:
continue
else:
break
else:
await media_session.stop()
raise AuthBytesInvalid
else:
media_session = Session(
client, data.dc_id, await client.storage.auth_key(),
await client.storage.test_mode(), is_media=True
)
await media_session.start()
client.media_sessions[data.dc_id] = media_session
return media_session
@staticmethod
async def get_location(file_id: FileId):
file_type = file_id.file_type
if file_type == FileType.CHAT_PHOTO:
if file_id.chat_id > 0:
peer = raw.types.InputPeerUser(
user_id=file_id.chat_id,
access_hash=file_id.chat_access_hash
)
else:
if file_id.chat_access_hash == 0:
peer = raw.types.InputPeerChat(
chat_id=-file_id.chat_id
)
else:
peer = raw.types.InputPeerChannel(
channel_id=utils.get_channel_id(file_id.chat_id),
access_hash=file_id.chat_access_hash
)
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
)
elif file_type == FileType.PHOTO:
location = raw.types.InputPhotoFileLocation(
id=file_id.media_id,
access_hash=file_id.access_hash,
file_reference=file_id.file_reference,
thumb_size=file_id.thumbnail_size
)
else:
location = raw.types.InputDocumentFileLocation(
id=file_id.media_id,
access_hash=file_id.access_hash,
file_reference=file_id.file_reference,
thumb_size=file_id.thumbnail_size
)
return location
async def yield_file(self, media_msg: Message, offset: int, first_part_cut: int,
last_part_cut: int, part_count: int, chunk_size: int) -> Union[str, None]: #pylint: disable=unsubscriptable-object
client = self.main_bot
data = await self.generate_file_properties(media_msg)
media_session = await self.generate_media_session(client, media_msg)
current_part = 1
location = await self.get_location(data)
r = await media_session.send(
raw.functions.upload.GetFile(
location=location,
offset=offset,
limit=chunk_size
),
)
if isinstance(r, raw.types.upload.File):
while current_part <= part_count:
chunk = r.bytes
if not chunk:
break
offset += chunk_size
if part_count == 1:
yield chunk[first_part_cut:last_part_cut]
break
if current_part == 1:
yield chunk[first_part_cut:]
if 1 < current_part <= part_count:
yield chunk
r = await media_session.send(
raw.functions.upload.GetFile(
location=location,
offset=offset,
limit=chunk_size
),
)
current_part += 1
async def download_as_bytesio(self, media_msg: Message):
client = self.main_bot
data = await self.generate_file_properties(media_msg)
media_session = await self.generate_media_session(client, media_msg)
location = await self.get_location(data)
limit = 1024 * 1024
offset = 0
r = await media_session.send(
raw.functions.upload.GetFile(
location=location,
offset=offset,
limit=limit
)
)
if isinstance(r, raw.types.upload.File):
m_file = []
# m_file.name = file_name
while True:
chunk = r.bytes
if not chunk:
break
m_file.append(chunk)
offset += limit
r = await media_session.send(
raw.functions.upload.GetFile(
location=location,
offset=offset,
limit=limit
)
)
return m_file

25
WebStreamer/vars.py Normal file
View File

@@ -0,0 +1,25 @@
# This file is a part of TG-FileStreamBot
# Coding : Jyothis Jayanth [@EverythingSuckz]
from os import getenv, environ
from dotenv import load_dotenv
load_dotenv()
class Var(object):
ENV = bool(getenv('ENV', False))
API_ID = int(getenv('API_ID'))
API_HASH = str(getenv('API_HASH'))
BOT_TOKEN = str(getenv('BOT_TOKEN'))
SLEEP_THRESHOLD = int(getenv('SLEEP_THRESHOLD', '300'))
WORKERS = int(getenv('WORKERS', '3'))
BIN_CHANNEL = int(getenv('BIN_CHANNEL', None))
FQDN = str(getenv('FQDN', 'localhost'))
PORT = int(getenv('PORT', 8080))
BIND_ADRESS = str(getenv('BIND_ADRESS', '0.0.0.0'))
CACHE_DIR = str(getenv('CACHE_DIR', 'WebStreamer/bot/cache'))
OWNER_ID = int(getenv('OWNER_ID'))
if 'DYNO' in environ:
ON_HEROKU = True
else:
ON_HEROKU = False