mirror of
https://github.com/avipatilpro/FileStreamBot.git
synced 2026-01-15 22:32:53 -03:00
push
This commit is contained in:
2
WebStreamer/__init__.py
Normal file
2
WebStreamer/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# This file is a part of TG-FileStreamBot
|
||||
# Coding : Jyothis Jayanth [@EverythingSuckz]
|
||||
59
WebStreamer/__main__.py
Normal file
59
WebStreamer/__main__.py
Normal 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 -----------------------')
|
||||
14
WebStreamer/bot/__init__.py
Normal file
14
WebStreamer/bot/__init__.py
Normal 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
|
||||
)
|
||||
21
WebStreamer/bot/plugins/start.py
Normal file
21
WebStreamer/bot/plugins/start.py
Normal 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'
|
||||
)
|
||||
]
|
||||
]
|
||||
))
|
||||
19
WebStreamer/bot/plugins/stream.py
Normal file
19
WebStreamer/bot/plugins/stream.py
Normal 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)
|
||||
)
|
||||
13
WebStreamer/server/__init__.py
Normal file
13
WebStreamer/server/__init__.py
Normal 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
|
||||
77
WebStreamer/server/stream_routes.py
Normal file
77
WebStreamer/server/stream_routes.py
Normal 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
|
||||
2
WebStreamer/utils/__init__.py
Normal file
2
WebStreamer/utils/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# This file is a part of TG-FileStreamBot
|
||||
# Coding : Jyothis Jayanth [@EverythingSuckz]
|
||||
233
WebStreamer/utils/custom_dl.py
Normal file
233
WebStreamer/utils/custom_dl.py
Normal 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
25
WebStreamer/vars.py
Normal 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
|
||||
Reference in New Issue
Block a user