From a995905558e2e52cb867f940a9acf80145fd752e Mon Sep 17 00:00:00 2001
From: masoudtestdeploy <95133886+masoudtestdeploy@users.noreply.github.com>
Date: Wed, 29 Jun 2022 22:07:00 +0430
Subject: [PATCH] Initial commit
---
.gitattributes | 2 +
.gitignore | 117 ++++++++++++
.replit | 2 +
Procfile | 1 +
README.md | 138 ++++++++++++++
WebStreamer/__init__.py | 1 +
WebStreamer/__main__.py | 64 +++++++
WebStreamer/bot/__init__.py | 12 ++
WebStreamer/bot/plugins/admin.py | 90 +++++++++
WebStreamer/bot/plugins/start.py | 261 ++++++++++++++++++++++++++
WebStreamer/bot/plugins/stream.py | 134 +++++++++++++
WebStreamer/server/__init__.py | 9 +
WebStreamer/server/stream_routes.py | 79 ++++++++
WebStreamer/utils/__init__.py | 1 +
WebStreamer/utils/broadcast_helper.py | 21 +++
WebStreamer/utils/custom_dl.py | 231 +++++++++++++++++++++++
WebStreamer/utils/database.py | 36 ++++
WebStreamer/utils/human_readable.py | 13 ++
WebStreamer/utils/keepalive.py | 18 ++
WebStreamer/vars.py | 33 ++++
app.json | 98 ++++++++++
requirements.txt | 7 +
22 files changed, 1368 insertions(+)
create mode 100644 .gitattributes
create mode 100644 .gitignore
create mode 100644 .replit
create mode 100644 Procfile
create mode 100644 README.md
create mode 100644 WebStreamer/__init__.py
create mode 100644 WebStreamer/__main__.py
create mode 100644 WebStreamer/bot/__init__.py
create mode 100644 WebStreamer/bot/plugins/admin.py
create mode 100644 WebStreamer/bot/plugins/start.py
create mode 100644 WebStreamer/bot/plugins/stream.py
create mode 100644 WebStreamer/server/__init__.py
create mode 100644 WebStreamer/server/stream_routes.py
create mode 100644 WebStreamer/utils/__init__.py
create mode 100644 WebStreamer/utils/broadcast_helper.py
create mode 100644 WebStreamer/utils/custom_dl.py
create mode 100644 WebStreamer/utils/database.py
create mode 100644 WebStreamer/utils/human_readable.py
create mode 100644 WebStreamer/utils/keepalive.py
create mode 100644 WebStreamer/vars.py
create mode 100644 app.json
create mode 100644 requirements.txt
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..dfe0770
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text=auto
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9b9d0a4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,117 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+#session files
+*.session
\ No newline at end of file
diff --git a/.replit b/.replit
new file mode 100644
index 0000000..75cf641
--- /dev/null
+++ b/.replit
@@ -0,0 +1,2 @@
+language = "bash"
+run = "pip3 install -r requirements.txt; python3 -m WebStreamer"
\ No newline at end of file
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..9a79e65
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: python -m WebStreamer
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b6378b1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,138 @@
+
FileStreamBot
+
+
+
+
+
+
+ A Telegram bot to all media and documents files to web link .
+
+
+
+ Report a Bug
+ |
+ Request Feature
+
+
+
+
+## 🍁 About This Bot :
+
+
+
+
+
+
+
+ This bot will give you stream links for Telegram files without the need of waiting till the download completes
+
+
+
+## ♢ How to make your own :
+
+Either you could locally host or deploy on [Heroku](https://heroku.com)
+
+#### ♢ Click on This Drop-down and get more details
+
+
+
+ Deploy on Heroku :
+
+
+1. Fork This Repo
+2. Click on Deploy Easily
+
+ So Follow Above Steps 👆 and then also deply other wise not work
+
+Press the below button to Fast deploy on Heroku
+
+[](https://heroku.com/deploy)
+
+then goto the variables tab for more info on setting up environmental variables.
+
+
+
+ Host it on VPS Locally :
+
+
+```py
+git clone https://github.com/avipatilpro/FileStreamBot
+cd FileStreamBot
+virtualenv -p /usr/bin/python3 venv
+. ./venv/bin/activate
+pip install -r requirements.txt
+python3 -m WebStreamer
+```
+
+and to stop the whole bot,
+ do CTRL+C
+
+Setting up things
+
+If you're on Heroku, just add these in the Environmental Variables
+or if you're Locally hosting, create a file named `.env` in the root directory and add all the variables there.
+An example of `.env` file:
+
+```py
+API_ID=12345
+API_HASH=esx576f8738x883f3sfzx83
+BOT_TOKEN=55838383:yourtbottokenhere
+BIN_CHANNEL=-100
+PORT=8080
+FQDN=your_server_ip
+OWNER_ID=your_user_id
+DATABASE_URL=mongodb_uri
+```
+
+
+
+ Vars and Details :
+
+`API_ID` : Goto [my.telegram.org](https://my.telegram.org) to obtain this.
+
+`API_HASH` : Goto [my.telegram.org](https://my.telegram.org) to obtain this.
+
+`BOT_TOKEN` : Get the bot token from [@BotFather](https://telegram.dog/BotFather)
+
+`BIN_CHANNEL` : Create a new channel (private/public), add [@missrose_bot](https://telegram.dog/MissRose_bot) as admin to the channel and type /id. Now copy paste the ID into this field.
+
+`OWNER_ID` : Your Telegram User ID
+
+`DATABASE_URL` : MongoDB URI for saving User IDs when they first Start the Bot. We will use that for Broadcasting to them. I will try to add more features related with Database. If you need help to get the URI you can ask in [Me Telegram](https://t.me/Avishkarpatil).
+
+ Option Vars
+
+`UPDATES_CHANNEL` : Put a Public Channel Username, so every user have to Join that channel to use the bot. Must add bot to channel as Admin to work properly.
+
+`BANNED_CHANNELS` : Put IDs of Banned Channels where bot will not work. You can add multiple IDs & separate with Space.
+
+`SLEEP_THRESHOLD` : Set a sleep threshold for flood wait exceptions happening globally in this telegram bot instance, below which any request that raises a flood wait will be automatically invoked again after sleeping for the required amount of time. Flood wait exceptions requiring higher waiting times will be raised. Defaults to 60 seconds.
+
+`WORKERS` : Number of maximum concurrent workers for handling incoming updates. Defaults to `3`
+
+`PORT` : The port that you want your webapp to be listened to. Defaults to `8080`
+
+`WEB_SERVER_BIND_ADDRESS` : Your server bind adress. Defauls to `0.0.0.0`
+
+`NO_PORT` : If you don't want your port to be displayed. You should point your `PORT` to `80` (http) or `443` (https) for the links to work. Ignore this if you're on Heroku.
+
+`FQDN` : A Fully Qualified Domain Name if present. Defaults to `WEB_SERVER_BIND_ADDRESS`
+
+
+ How to Use :
+
+:warning: **Before using the bot, don't forget to add the bot to the `BIN_CHANNEL` as an Admin**
+
+`/start` : To check if the bot is alive or not.
+
+To get an instant stream link, just forward any media to the bot and boom, its fast af.
+
+### Channel Support
+Bot also Supported with Channels. Just add bot Channel as Admin. If any new file comes in Channel it will edit it with **Get Download Link** Button.
+
+
+---
+© 2022 Aνιѕнкαя Pαтιℓ
+
+
+
diff --git a/WebStreamer/__init__.py b/WebStreamer/__init__.py
new file mode 100644
index 0000000..ff0636f
--- /dev/null
+++ b/WebStreamer/__init__.py
@@ -0,0 +1 @@
+# This file is a part of avipatilpro/FileStreamBot
diff --git a/WebStreamer/__main__.py b/WebStreamer/__main__.py
new file mode 100644
index 0000000..b6192a8
--- /dev/null
+++ b/WebStreamer/__main__.py
@@ -0,0 +1,64 @@
+
+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
+from .utils.keepalive import ping_server
+
+ppath = "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.ON_HEROKU 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))
+ if Var.ON_HEROKU:
+ print(' app runnng on =>> {}'.format(Var.FQDN))
+ if Var.ON_HEROKU:
+ print('------------------ Starting Keep Alive Service ------------------')
+ print('\n')
+ await asyncio.create_task(ping_server())
+ print('---------------------------------------------------------------')
+ await idle()
+
+if __name__ == '__main__':
+ try:
+ loop.run_until_complete(start_services())
+ except KeyboardInterrupt:
+ print('----------------------- Service Stopped -----------------------')
diff --git a/WebStreamer/bot/__init__.py b/WebStreamer/bot/__init__.py
new file mode 100644
index 0000000..e5ad8db
--- /dev/null
+++ b/WebStreamer/bot/__init__.py
@@ -0,0 +1,12 @@
+
+from pyrogram import Client
+from ..vars import Var
+
+StreamBot = Client(
+ 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
+)
diff --git a/WebStreamer/bot/plugins/admin.py b/WebStreamer/bot/plugins/admin.py
new file mode 100644
index 0000000..04372ec
--- /dev/null
+++ b/WebStreamer/bot/plugins/admin.py
@@ -0,0 +1,90 @@
+# (c) @Avishkarpatil
+
+import os
+import time
+import string
+import random
+import asyncio
+import aiofiles
+import datetime
+from WebStreamer.utils.broadcast_helper import send_msg
+from WebStreamer.utils.database import Database
+from WebStreamer.bot import StreamBot
+from WebStreamer.vars import Var
+from pyrogram import filters, Client
+from pyrogram.types import Message
+from pyrogram.enums.parse_mode import ParseMode
+db = Database(Var.DATABASE_URL, Var.SESSION_NAME)
+broadcast_ids = {}
+
+
+@StreamBot.on_message(filters.command("status") & filters.private & filters.user(Var.OWNER_ID))
+async def sts(c: Client, m: Message):
+ total_users = await db.total_users_count()
+ await m.reply_text(text=f"**Total Users in DB:** `{total_users}`", parse_mode=ParseMode.MARKDOWN, quote=True)
+
+
+@StreamBot.on_message(filters.command("broadcast") & filters.private & filters.user(Var.OWNER_ID) & filters.reply)
+async def broadcast_(c, m):
+ all_users = await db.get_all_users()
+ broadcast_msg = m.reply_to_message
+ while True:
+ broadcast_id = ''.join([random.choice(string.ascii_letters) for i in range(3)])
+ if not broadcast_ids.get(broadcast_id):
+ break
+ out = await m.reply_text(
+ text=f"Broadcast initiated! You will be notified with log file when all the users are notified."
+ )
+ start_time = time.time()
+ total_users = await db.total_users_count()
+ done = 0
+ failed = 0
+ success = 0
+ broadcast_ids[broadcast_id] = dict(
+ total=total_users,
+ current=done,
+ failed=failed,
+ success=success
+ )
+ async with aiofiles.open('broadcast.txt', 'w') as broadcast_log_file:
+ async for user in all_users:
+ sts, msg = await send_msg(
+ user_id=int(user['id']),
+ message=broadcast_msg
+ )
+ if msg is not None:
+ await broadcast_log_file.write(msg)
+ if sts == 200:
+ success += 1
+ else:
+ failed += 1
+ if sts == 400:
+ await db.delete_user(user['id'])
+ done += 1
+ if broadcast_ids.get(broadcast_id) is None:
+ break
+ else:
+ broadcast_ids[broadcast_id].update(
+ dict(
+ current=done,
+ failed=failed,
+ success=success
+ )
+ )
+ if broadcast_ids.get(broadcast_id):
+ broadcast_ids.pop(broadcast_id)
+ completed_in = datetime.timedelta(seconds=int(time.time() - start_time))
+ await asyncio.sleep(3)
+ await out.delete()
+ if failed == 0:
+ await m.reply_text(
+ text=f"broadcast completed in `{completed_in}`\n\nTotal users {total_users}.\nTotal done {done}, {success} success and {failed} failed.",
+ quote=True
+ )
+ else:
+ await m.reply_document(
+ document='broadcast.txt',
+ caption=f"broadcast completed in `{completed_in}`\n\nTotal users {total_users}.\nTotal done {done}, {success} success and {failed} failed.",
+ quote=True
+ )
+ os.remove('broadcast.txt')
diff --git a/WebStreamer/bot/plugins/start.py b/WebStreamer/bot/plugins/start.py
new file mode 100644
index 0000000..83198f8
--- /dev/null
+++ b/WebStreamer/bot/plugins/start.py
@@ -0,0 +1,261 @@
+import urllib.parse
+from WebStreamer.bot import StreamBot
+from WebStreamer.vars import Var
+from WebStreamer.utils.human_readable import humanbytes
+from WebStreamer.utils.database import Database
+from pyrogram import filters
+from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
+from pyrogram.errors import UserNotParticipant
+from pyrogram.enums.parse_mode import ParseMode
+
+db = Database(Var.DATABASE_URL, Var.SESSION_NAME)
+
+START_TEXT = """
+👋 Hᴇʏ,{}\n
+I'ᴍ Tᴇʟᴇɢʀᴀᴍ Fɪʟᴇs Sᴛʀᴇᴀᴍɪɴɢ Bᴏᴛ ᴀs ᴡᴇʟʟ Dɪʀᴇᴄᴛ Lɪɴᴋs Gᴇɴᴇʀᴀᴛᴇ\n
+Cʟɪᴄᴋ ᴏɴ Hᴇʟᴘ ᴛᴏ ɢᴇᴛ ᴍᴏʀᴇ ɪɴғᴏʀᴍᴀᴛɪᴏɴ\n
+𝗪𝗔𝗥𝗡𝗜𝗡𝗚 🚸
+🔞 Pʀᴏɴ ᴄᴏɴᴛᴇɴᴛꜱ ʟᴇᴀᴅꜱ ᴛᴏ ᴘᴇʀᴍᴀɴᴇɴᴛ ʙᴀɴ ʏᴏᴜ.\n\n
+🍃 Bᴏᴛ Mᴀɪɴᴛᴀɪɴᴇᴅ Bʏ :@AvishkarPatil"""
+
+HELP_TEXT = """
+- Sᴇɴᴅ ᴍᴇ ᴀɴʏ ꜰɪʟᴇ (ᴏʀ) ᴍᴇᴅɪᴀ ꜰʀᴏᴍ ᴛᴇʟᴇɢʀᴀᴍ.
+- I ᴡɪʟʟ ᴘʀᴏᴠɪᴅᴇ ᴇxᴛᴇʀɴᴀʟ ᴅɪʀᴇᴄᴛ ᴅᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ !.
+- Aᴅᴅ Mᴇ ɪɴ ʏᴏᴜʀ Cʜᴀɴɴᴇʟ Fᴏʀ Dɪʀᴇᴄᴛ Dᴏᴡɴʟᴏᴀᴅ Lɪɴᴋs Bᴜᴛᴛᴏɴ
+- Tʜɪs Pᴇʀᴍᴇᴀɴᴛ Lɪɴᴋ Wɪᴛʜ Fᴀsᴛᴇsᴛ Sᴘᴇᴇᴅ\n
+🔸 𝗪𝗔𝗥𝗡𝗜𝗡𝗚 🚸\n
+🔞 Pʀᴏɴ ᴄᴏɴᴛᴇɴᴛꜱ ʟᴇᴀᴅꜱ ᴛᴏ ᴘᴇʀᴍᴀɴᴇɴᴛ ʙᴀɴ ʏᴏᴜ.\n
+Cᴏɴᴛᴀᴄᴛ ᴅᴇᴠᴇʟᴏᴘᴇʀ (ᴏʀ) ʀᴇᴘᴏʀᴛ ʙᴜɢꜱ : [ ᴄʟɪᴄᴋ ʜᴇʀᴇ ]"""
+
+ABOUT_TEXT = """
+⚜ Mʏ ɴᴀᴍᴇ : FileStreamX\n
+🔸Vᴇʀꜱɪᴏɴ : 3.0.1\n
+🔹Sᴏᴜʀᴄᴇ : Cʟɪᴄᴋ Hᴇʀᴇ\n
+🔸GitHub : Fᴏʟʟᴏᴡ\n
+🔹Dᴇᴠᴇʟᴏᴘᴇʀ : Aᴠɪsʜᴋᴀʀ Pᴀᴛɪʟ\n
+🔸Lᴀꜱᴛ ᴜᴘᴅᴀᴛᴇᴅ : [ 26 - ᴊᴜɴᴇ - 2022 ] 03:35 ᴀᴍ"""
+
+START_BUTTONS = InlineKeyboardMarkup(
+ [[
+ InlineKeyboardButton('Hᴇʟᴘ', callback_data='help'),
+ InlineKeyboardButton('Aʙᴏᴜᴛ', callback_data='about'),
+ InlineKeyboardButton('Cʟᴏsᴇ', callback_data='close')
+ ]]
+ )
+HELP_BUTTONS = InlineKeyboardMarkup(
+ [[
+ InlineKeyboardButton('Hᴏᴍᴇ', callback_data='home'),
+ InlineKeyboardButton('Aʙᴏᴜᴛ', callback_data='about'),
+ InlineKeyboardButton('Cʟᴏsᴇ', callback_data='close')
+ ]]
+ )
+ABOUT_BUTTONS = InlineKeyboardMarkup(
+ [[
+ InlineKeyboardButton('Hᴏᴍᴇ', callback_data='home'),
+ InlineKeyboardButton('Hᴇʟᴘ', callback_data='help'),
+ InlineKeyboardButton('Cʟᴏsᴇ', callback_data='close')
+ ]]
+ )
+
+@StreamBot.on_callback_query()
+async def cb_data(bot, update):
+ if update.data == "home":
+ await update.message.edit_text(
+ text=START_TEXT.format(update.from_user.mention),
+ disable_web_page_preview=True,
+ reply_markup=START_BUTTONS
+ )
+ elif update.data == "help":
+ await update.message.edit_text(
+ text=HELP_TEXT,
+ disable_web_page_preview=True,
+ reply_markup=HELP_BUTTONS
+ )
+ elif update.data == "about":
+ await update.message.edit_text(
+ text=ABOUT_TEXT,
+ disable_web_page_preview=True,
+ reply_markup=ABOUT_BUTTONS
+ )
+ else:
+ await update.message.delete()
+
+def get_media_file_size(m):
+ media = m.video or m.audio or m.document
+ if media and media.file_size:
+ return media.file_size
+ else:
+ return None
+
+
+def get_media_file_name(m):
+ media = m.video or m.document or m.audio
+ if media and media.file_name:
+ return urllib.parse.quote_plus(media.file_name)
+ else:
+ return None
+
+
+@StreamBot.on_message(filters.command('start') & filters.private)
+async def start(b, m):
+ if not await db.is_user_exist(m.from_user.id):
+ await db.add_user(m.from_user.id)
+ await b.send_message(
+ Var.BIN_CHANNEL,
+ f"**Nᴇᴡ Usᴇʀ Jᴏɪɴᴇᴅ:** \n\n__Mʏ Nᴇᴡ Fʀɪᴇɴᴅ__ [{m.from_user.first_name}](tg://user?id={m.from_user.id}) __Sᴛᴀʀᴛᴇᴅ Yᴏᴜʀ Bᴏᴛ !!__"
+ )
+ usr_cmd = m.text.split("_")[-1]
+ if usr_cmd == "/start":
+ if Var.UPDATES_CHANNEL != "None":
+ try:
+ user = await b.get_chat_member(Var.UPDATES_CHANNEL, m.chat.id)
+ if user.status == "kicked":
+ await b.send_message(
+ chat_id=m.chat.id,
+ text="__Sᴏʀʀʏ Sɪʀ, Yᴏᴜ ᴀʀᴇ Bᴀɴɴᴇᴅ ᴛᴏ ᴜsᴇ ᴍᴇ. Cᴏɴᴛᴀᴄᴛ ᴛʜᴇ Dᴇᴠᴇʟᴏᴘᴇʀ__\n\n @AvishkarPatil **Tʜᴇʏ Wɪʟʟ Hᴇʟᴘ Yᴏᴜ**",
+ parse_mode=ParseMode.MARKDOWN,
+ disable_web_page_preview=True
+ )
+ return
+ except UserNotParticipant:
+ await b.send_message(
+ chat_id=m.chat.id,
+ text="Jᴏɪɴ ᴍʏ ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ ᴛᴏ ᴜsᴇ ᴍᴇ 🔐",
+ reply_markup=InlineKeyboardMarkup(
+ [[
+ InlineKeyboardButton("Jᴏɪɴ ɴᴏᴡ 🔓", url=f"https://t.me/{Var.UPDATES_CHANNEL}")
+ ]]
+ ),
+ parse_mode=ParseMode.HTML
+ )
+ return
+ except Exception:
+ await b.send_message(
+ chat_id=m.chat.id,
+ text="Sᴏᴍᴇᴛʜɪɴɢ ᴡʀᴏɴɢ ᴄᴏɴᴛᴀᴄᴛ ᴍʏ ᴅᴇᴠᴇʟᴏᴘᴇʀ [ ᴄʟɪᴄᴋ ʜᴇʀᴇ ]",
+ parse_mode=ParseMode.HTML,
+ disable_web_page_preview=True)
+ return
+ await m.reply_text(
+ text=START_TEXT.format(m.from_user.mention),
+ parse_mode=ParseMode.HTML,
+ disable_web_page_preview=True,
+ reply_markup=START_BUTTONS
+ )
+
+
+ else:
+ if Var.UPDATES_CHANNEL != "None":
+ try:
+ user = await b.get_chat_member(Var.UPDATES_CHANNEL, m.chat.id)
+ if user.status == "kicked":
+ await b.send_message(
+ chat_id=m.chat.id,
+ text="**Sᴏʀʀʏ Sɪʀ, Yᴏᴜ ᴀʀᴇ Bᴀɴɴᴇᴅ ᴛᴏ ᴜsᴇ ᴍᴇ. Qᴜɪᴄᴋʟʏ ᴄᴏɴᴛᴀᴄᴛ** @Avishkarpatil",
+ parse_mode=ParseMode.MARKDOWN,
+ disable_web_page_preview=True
+ )
+ return
+ except UserNotParticipant:
+ await b.send_message(
+ chat_id=m.chat.id,
+ text="**Pʟᴇᴀsᴇ Jᴏɪɴ Mʏ Uᴘᴅᴀᴛᴇs Cʜᴀɴɴᴇʟ ᴛᴏ ᴜsᴇ ᴛʜɪs Bᴏᴛ**!\n\n**Dᴜᴇ ᴛᴏ Oᴠᴇʀʟᴏᴀᴅ, Oɴʟʏ Cʜᴀɴɴᴇʟ Sᴜʙsᴄʀɪʙᴇʀs ᴄᴀɴ ᴜsᴇ ᴛʜᴇ Bᴏᴛ**!",
+ reply_markup=InlineKeyboardMarkup(
+ [[
+ InlineKeyboardButton("🤖 Jᴏɪɴ Uᴘᴅᴀᴛᴇs Cʜᴀɴɴᴇʟ", url=f"https://t.me/{Var.UPDATES_CHANNEL}")],
+ [InlineKeyboardButton("🔄 Refresh / Try Again", url=f"https://t.me/{(await b.get_me()).username}?start=AvishkarPatil_{usr_cmd}")
+
+ ]]
+ ),
+ parse_mode=ParseMode.MARKDOWN
+ )
+ return
+ except Exception:
+ await b.send_message(
+ chat_id=m.chat.id,
+ text="**Sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ Wʀᴏɴɢ. Cᴏɴᴛᴀᴄᴛ ᴍᴇ** [Aᴠɪsʜᴋᴀʀ Pᴀᴛɪʟ](https://t.me/Avishkarpatil).",
+ parse_mode=ParseMode.MARKDOWN,
+ disable_web_page_preview=True)
+ return
+
+ get_msg = await b.get_messages(chat_id=Var.BIN_CHANNEL, message_ids=int(usr_cmd))
+ file_name = get_media_file_name(get_msg)
+ file_size = humanbytes(get_media_file_size(get_msg))
+
+ stream_link = "https://{}/{}/{}".format(Var.FQDN, get_msg.id, file_name) if Var.ON_HEROKU or Var.NO_PORT else \
+ "http://{}:{}/{}/{}".format(Var.FQDN,
+ Var.PORT,
+ get_msg.id,
+ file_name)
+
+ msg_text ="""
+𝗬𝗼𝘂𝗿 𝗟𝗶𝗻𝗸 𝗚𝗲𝗻𝗲𝗿𝗮𝘁𝗲𝗱 !\n
+📂 Fɪʟᴇ ɴᴀᴍᴇ : {}\n
+📦 Fɪʟᴇ ꜱɪᴢᴇ : {}\n
+📥 Dᴏᴡɴʟᴏᴀᴅ : {}\n
+🚸 Nᴏᴛᴇ : Lɪɴᴋ ᴇxᴘɪʀᴇᴅ ɪɴ 24 ʜᴏᴜʀꜱ\n
+🍃 Bᴏᴛ Mᴀɪɴᴛᴀɪɴᴇᴅ Bʏ : @AvishkarPatil
+"""
+
+ await m.reply_text(
+ text=msg_text.format(file_name, file_size, stream_link),
+ parse_mode=ParseMode.HTML,
+ reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("Dᴏᴡɴʟᴏᴀᴅ ɴᴏᴡ 📥", url=stream_link)]])
+ )
+
+
+
+@StreamBot.on_message(filters.private & filters.command(["about"]))
+async def start(bot, update):
+ await update.reply_text(
+ text=ABOUT_TEXT.format(update.from_user.mention),
+ disable_web_page_preview=True,
+ reply_markup=ABOUT_BUTTONS
+ )
+
+
+@StreamBot.on_message(filters.command('help') & filters.private)
+async def help_handler(bot, message):
+ if not await db.is_user_exist(message.from_user.id):
+ await db.add_user(message.from_user.id)
+ await bot.send_message(
+ Var.BIN_CHANNEL,
+ f"**Nᴇᴡ Usᴇʀ Jᴏɪɴᴇᴅ **\n\n__Mʏ Nᴇᴡ Fʀɪᴇɴᴅ__ [{message.from_user.first_name}](tg://user?id={message.from_user.id}) __Started Your Bot !!__"
+ )
+ if Var.UPDATES_CHANNEL is not None:
+ try:
+ user = await bot.get_chat_member(Var.UPDATES_CHANNEL, message.chat.id)
+ if user.status == "kicked":
+ await bot.send_message(
+ chat_id=message.chat.id,
+ text="Sᴏʀʀʏ Sɪʀ, Yᴏᴜ ᴀʀᴇ Bᴀɴɴᴇᴅ ᴛᴏ ᴜsᴇ ᴍᴇ. Cᴏɴᴛᴀᴄᴛ ᴛʜᴇ Dᴇᴠᴇʟᴏᴘᴇʀ",
+ parse_mode=ParseMode.HTML,
+ disable_web_page_preview=True
+ )
+ return
+ except UserNotParticipant:
+ await bot.send_message(
+ chat_id=message.chat.id,
+ text="**Pʟᴇᴀsᴇ Jᴏɪɴ Mʏ Uᴘᴅᴀᴛᴇs Cʜᴀɴɴᴇʟ ᴛᴏ ᴜsᴇ ᴛʜɪs Bᴏᴛ!**\n\n__Dᴜᴇ ᴛᴏ Oᴠᴇʀʟᴏᴀᴅ, Oɴʟʏ Cʜᴀɴɴᴇʟ Sᴜʙsᴄʀɪʙᴇʀs ᴄᴀɴ ᴜsᴇ ᴛʜᴇ Bᴏᴛ!__",
+ reply_markup=InlineKeyboardMarkup(
+ [[
+ InlineKeyboardButton("🤖 Jᴏɪɴ Uᴘᴅᴀᴛᴇs Cʜᴀɴɴᴇʟ", url=f"https://t.me/{Var.UPDATES_CHANNEL}")
+ ]]
+ ),
+ parse_mode=ParseMode.MARKDOWN
+ )
+ return
+ except Exception:
+ await bot.send_message(
+ chat_id=message.chat.id,
+ text="__Sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ Wʀᴏɴɢ. Cᴏɴᴛᴀᴄᴛ ᴍᴇ__ [Aᴠɪsʜᴋᴀʀ Pᴀᴛɪʟ](https://t.me/Avishkarpatil).",
+ parse_mode=ParseMode.MARKDOWN,
+ disable_web_page_preview=True)
+ return
+ await message.reply_text(
+ text=HELP_TEXT,
+ parse_mode=ParseMode.HTML,
+ disable_web_page_preview=True,
+ reply_markup=HELP_BUTTONS
+ )
+
diff --git a/WebStreamer/bot/plugins/stream.py b/WebStreamer/bot/plugins/stream.py
new file mode 100644
index 0000000..e374e21
--- /dev/null
+++ b/WebStreamer/bot/plugins/stream.py
@@ -0,0 +1,134 @@
+
+# (c) @Avishkarpatil
+
+
+import asyncio
+import urllib.parse
+from WebStreamer.bot import StreamBot
+from WebStreamer.utils.database import Database
+from WebStreamer.utils.human_readable import humanbytes
+from WebStreamer.vars import Var
+from pyrogram import filters, Client
+from pyrogram.errors import FloodWait, UserNotParticipant
+from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
+from pyrogram.enums.parse_mode import ParseMode
+db = Database(Var.DATABASE_URL, Var.SESSION_NAME)
+
+
+def get_media_file_size(m):
+ media = m.video or m.audio or m.document
+ if media and media.file_size:
+ return media.file_size
+ else:
+ return None
+
+
+def get_media_file_name(m):
+ media = m.video or m.document or m.audio
+ if media and media.file_name:
+ return urllib.parse.quote_plus(media.file_name)
+ else:
+ return None
+
+
+@StreamBot.on_message(filters.private & (filters.document | filters.video | filters.audio), group=4)
+async def private_receive_handler(c: Client, m: Message):
+ if not await db.is_user_exist(m.from_user.id):
+ await db.add_user(m.from_user.id)
+ await c.send_message(
+ Var.BIN_CHANNEL,
+ f"Nᴇᴡ Usᴇʀ Jᴏɪɴᴇᴅ : \n\nNᴀᴍᴇ : [{m.from_user.first_name}](tg://user?id={m.from_user.id}) Sᴛᴀʀᴛᴇᴅ Yᴏᴜʀ Bᴏᴛ !!"
+ )
+ if Var.UPDATES_CHANNEL != "None":
+ try:
+ user = await c.get_chat_member(Var.UPDATES_CHANNEL, m.chat.id)
+ if user.status == "kicked":
+ await c.send_message(
+ chat_id=m.chat.id,
+ text="__Sᴏʀʀʏ Sɪʀ, Yᴏᴜ ᴀʀᴇ Bᴀɴɴᴇᴅ ᴛᴏ ᴜsᴇ ᴍᴇ.__\n\n **Cᴏɴᴛᴀᴄᴛ Dᴇᴠᴇʟᴏᴘᴇʀ @Avishkarpatil Tʜᴇʏ Wɪʟʟ Hᴇʟᴘ Yᴏᴜ**",
+ parse_mode=ParseMode.MARKDOWN,
+ disable_web_page_preview=True
+ )
+ return
+ except UserNotParticipant:
+ await c.send_message(
+ chat_id=m.chat.id,
+ text="""Jᴏɪɴ ᴍʏ ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟ ᴛᴏ ᴜꜱᴇ ᴍᴇ 🔐""",
+ reply_markup=InlineKeyboardMarkup(
+ [[ InlineKeyboardButton("Jᴏɪɴ ɴᴏᴡ 🔓", url=f"https://t.me/{Var.UPDATES_CHANNEL}") ]]
+ ),
+ parse_mode=ParseMode.HTML
+ )
+ return
+ except Exception:
+ await c.send_message(
+ chat_id=m.chat.id,
+ text="**Sᴏᴍᴇᴛʜɪɴɢ ᴡᴇɴᴛ Wʀᴏɴɢ. Cᴏɴᴛᴀᴄᴛ ᴍʏ ʙᴏss** @Avishkarpatil",
+ parse_mode=ParseMode.MARKDOWN,
+ disable_web_page_preview=True)
+ return
+ try:
+ log_msg = await m.forward(chat_id=Var.BIN_CHANNEL)
+ file_name = get_media_file_name(m)
+ file_size = humanbytes(get_media_file_size(m))
+ stream_link = "https://{}/{}/{}".format(Var.FQDN, log_msg.id, file_name) if Var.ON_HEROKU or Var.NO_PORT else \
+ "http://{}:{}/{}/{}".format(Var.FQDN,
+ Var.PORT,
+ log_msg.id,
+ file_name)
+
+ msg_text ="""
+𝗬𝗼𝘂𝗿 𝗟𝗶𝗻𝗸 𝗚𝗲𝗻𝗲𝗿𝗮𝘁𝗲𝗱 !\n
+📂 Fɪʟᴇ ɴᴀᴍᴇ : {}\n
+📦 Fɪʟᴇ ꜱɪᴢᴇ : {}\n
+📥 Dᴏᴡɴʟᴏᴀᴅ : {}\n
+🚸 Nᴏᴛᴇ : Tʜɪs ᴘᴇʀᴍᴀɴᴇɴᴛ Lɪɴᴋ, Nᴏᴛ Exᴘɪʀᴇᴅ\n
+© @AvishkarPatil """
+
+ await log_msg.reply_text(text=f"**RᴇQᴜᴇꜱᴛᴇᴅ ʙʏ :** [{m.from_user.first_name}](tg://user?id={m.from_user.id})\n**Uꜱᴇʀ ɪᴅ :** `{m.from_user.id}`\n**Dᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ :** {stream_link}", disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN, quote=True)
+ await m.reply_text(
+ text=msg_text.format(file_name, file_size, stream_link),
+ parse_mode=ParseMode.HTML,
+ disable_web_page_preview=True,
+ reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("Dᴏᴡɴʟᴏᴀᴅ ɴᴏᴡ 📥", url=stream_link)]]),
+ quote=True
+ )
+ except FloodWait as e:
+ print(f"Sleeping for {str(e.x)}s")
+ await asyncio.sleep(e.x)
+ await c.send_message(chat_id=Var.BIN_CHANNEL, text=f"Gᴏᴛ FʟᴏᴏᴅWᴀɪᴛ ᴏғ {str(e.x)}s from [{m.from_user.first_name}](tg://user?id={m.from_user.id})\n\n**𝚄𝚜𝚎𝚛 𝙸𝙳 :** `{str(m.from_user.id)}`", disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN)
+
+
+@StreamBot.on_message(filters.channel & (filters.document | filters.video), group=-1)
+async def channel_receive_handler(bot, broadcast):
+ if int(broadcast.chat.id) in Var.BANNED_CHANNELS:
+ await bot.leave_chat(broadcast.chat.id)
+ return
+ try:
+ log_msg = await broadcast.forward(chat_id=Var.BIN_CHANNEL)
+ stream_link = "https://{}/{}".format(Var.FQDN, log_msg.id) if Var.ON_HEROKU or Var.NO_PORT else \
+ "http://{}:{}/{}".format(Var.FQDN,
+ Var.PORT,
+ log_msg.id)
+ await log_msg.reply_text(
+ text=f"**Cʜᴀɴɴᴇʟ Nᴀᴍᴇ:** `{broadcast.chat.title}`\n**Cʜᴀɴɴᴇʟ ID:** `{broadcast.chat.id}`\n**Rᴇǫᴜᴇsᴛ ᴜʀʟ:** https://t.me/{(await bot.get_me()).username}?start=AvishkarPatil_{str(log_msg.id)}",
+ # text=f"**Cʜᴀɴɴᴇʟ Nᴀᴍᴇ:** `{broadcast.chat.title}`\n**Cʜᴀɴɴᴇʟ ID:** `{broadcast.chat.id}`\n**Rᴇǫᴜᴇsᴛ ᴜʀʟ:** https://t.me/FxStreamBot?start=AvishkarPatil_{str(log_msg.id)}",
+ quote=True,
+ parse_mode=ParseMode.MARKDOWN
+ )
+ await bot.edit_message_reply_markup(
+ chat_id=broadcast.chat.id,
+ message_id=broadcast.id,
+ reply_markup=InlineKeyboardMarkup(
+ [[InlineKeyboardButton("Dᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ 📥", url=f"https://t.me/{(await bot.get_me()).username}?start=AvishkarPatil_{str(log_msg.id)}")]])
+ # [[InlineKeyboardButton("Dᴏᴡɴʟᴏᴀᴅ ʟɪɴᴋ 📥", url=f"https://t.me/FxStreamBot?start=AvishkarPatil_{str(log_msg.id)}")]])
+ )
+ except FloodWait as w:
+ print(f"Sleeping for {str(w.x)}s")
+ await asyncio.sleep(w.x)
+ await bot.send_message(chat_id=Var.BIN_CHANNEL,
+ text=f"Gᴏᴛ FʟᴏᴏᴅWᴀɪᴛ ᴏғ {str(w.x)}s from {broadcast.chat.title}\n\n**Cʜᴀɴɴᴇʟ ID:** `{str(broadcast.chat.id)}`",
+ disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN)
+ except Exception as e:
+ await bot.send_message(chat_id=Var.BIN_CHANNEL, text=f"**#ᴇʀʀᴏʀ_ᴛʀᴀᴄᴇʙᴀᴄᴋ:** `{e}`", disable_web_page_preview=True, parse_mode=ParseMode.MARKDOWN)
+ print(f"Cᴀɴ'ᴛ Eᴅɪᴛ Bʀᴏᴀᴅᴄᴀsᴛ Mᴇssᴀɢᴇ!\nEʀʀᴏʀ: {e}")
diff --git a/WebStreamer/server/__init__.py b/WebStreamer/server/__init__.py
new file mode 100644
index 0000000..a344be9
--- /dev/null
+++ b/WebStreamer/server/__init__.py
@@ -0,0 +1,9 @@
+
+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
diff --git a/WebStreamer/server/stream_routes.py b/WebStreamer/server/stream_routes.py
new file mode 100644
index 0000000..e3a4638
--- /dev/null
+++ b/WebStreamer/server/stream_routes.py
@@ -0,0 +1,79 @@
+# Avishkar Patil | AbirHasan2005
+
+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("/", allow_head=True)
+async def root_route_handler(request):
+ bot_details = await StreamBot.get_me()
+ return web.json_response({"status": "running",
+ "maintained_by": "Avishkar_Patil",
+ "server_permission": "Open",
+ "Telegram_Bot": '@'+bot_details.username})
+
+
+@routes.get("/{message_id}")
+@routes.get("/{message_id}/")
+@routes.get(r"/{message_id:\d+}/{name}")
+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
diff --git a/WebStreamer/utils/__init__.py b/WebStreamer/utils/__init__.py
new file mode 100644
index 0000000..ff0636f
--- /dev/null
+++ b/WebStreamer/utils/__init__.py
@@ -0,0 +1 @@
+# This file is a part of avipatilpro/FileStreamBot
diff --git a/WebStreamer/utils/broadcast_helper.py b/WebStreamer/utils/broadcast_helper.py
new file mode 100644
index 0000000..b629741
--- /dev/null
+++ b/WebStreamer/utils/broadcast_helper.py
@@ -0,0 +1,21 @@
+
+import asyncio
+import traceback
+from pyrogram.errors import FloodWait, InputUserDeactivated, UserIsBlocked, PeerIdInvalid
+
+
+async def send_msg(user_id, message):
+ try:
+ await message.forward(chat_id=user_id)
+ return 200, None
+ except FloodWait as e:
+ await asyncio.sleep(e.x)
+ return send_msg(user_id, message)
+ except InputUserDeactivated:
+ return 400, f"{user_id} : deactivated\n"
+ except UserIsBlocked:
+ return 400, f"{user_id} : blocked the bot\n"
+ except PeerIdInvalid:
+ return 400, f"{user_id} : user id invalid\n"
+ except Exception as e:
+ return 500, f"{user_id} : {traceback.format_exc()}\n"
diff --git a/WebStreamer/utils/custom_dl.py b/WebStreamer/utils/custom_dl.py
new file mode 100644
index 0000000..b375838
--- /dev/null
+++ b/WebStreamer/utils/custom_dl.py
@@ -0,0 +1,231 @@
+
+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.invoke(
+ raw.functions.auth.ExportAuthorization(
+ dc_id=data.dc_id
+ )
+ )
+
+ try:
+ await media_session.invoke(
+ 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.invoke(
+ 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.invoke(
+ 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.invoke(
+ 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.invoke(
+ raw.functions.upload.GetFile(
+ location=location,
+ offset=offset,
+ limit=limit
+ )
+ )
+
+ return m_file
diff --git a/WebStreamer/utils/database.py b/WebStreamer/utils/database.py
new file mode 100644
index 0000000..cf72f8d
--- /dev/null
+++ b/WebStreamer/utils/database.py
@@ -0,0 +1,36 @@
+
+
+import datetime
+import motor.motor_asyncio
+
+
+class Database:
+ def __init__(self, uri, database_name):
+ self._client = motor.motor_asyncio.AsyncIOMotorClient(uri)
+ self.db = self._client[database_name]
+ self.col = self.db.users
+
+ def new_user(self, id):
+ return dict(
+ id=id,
+ join_date=datetime.date.today().isoformat()
+ )
+
+ async def add_user(self, id):
+ user = self.new_user(id)
+ await self.col.insert_one(user)
+
+ async def is_user_exist(self, id):
+ user = await self.col.find_one({'id': int(id)})
+ return True if user else False
+
+ async def total_users_count(self):
+ count = await self.col.count_documents({})
+ return count
+
+ async def get_all_users(self):
+ all_users = self.col.find({})
+ return all_users
+
+ async def delete_user(self, user_id):
+ await self.col.delete_many({'id': int(user_id)})
diff --git a/WebStreamer/utils/human_readable.py b/WebStreamer/utils/human_readable.py
new file mode 100644
index 0000000..c7f2fdb
--- /dev/null
+++ b/WebStreamer/utils/human_readable.py
@@ -0,0 +1,13 @@
+
+def humanbytes(size):
+ # https://stackoverflow.com/a/49361727/4723940
+ # 2**10 = 1024
+ if not size:
+ return ""
+ power = 2**10
+ n = 0
+ Dic_powerN = {0: ' ', 1: 'Ki', 2: 'Mi', 3: 'Gi', 4: 'Ti'}
+ while size > power:
+ size /= power
+ n += 1
+ return str(round(size, 2)) + " " + Dic_powerN[n] + 'B'
diff --git a/WebStreamer/utils/keepalive.py b/WebStreamer/utils/keepalive.py
new file mode 100644
index 0000000..f6601a8
--- /dev/null
+++ b/WebStreamer/utils/keepalive.py
@@ -0,0 +1,18 @@
+import asyncio
+import logging
+import aiohttp
+import traceback
+from WebStreamer.vars import Var
+
+async def ping_server():
+ sleep_time = Var.PING_INTERVAL
+ while True:
+ await asyncio.sleep(sleep_time)
+ try:
+ async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:
+ async with session.get(Var.URL) as resp:
+ logging.info("Pinged server with response: {}".format(resp.status))
+ except TimeoutError:
+ logging.warning("Couldn't connect to the site URL..!")
+ except Exception:
+ traceback.print_exc()
diff --git a/WebStreamer/vars.py b/WebStreamer/vars.py
new file mode 100644
index 0000000..130c47f
--- /dev/null
+++ b/WebStreamer/vars.py
@@ -0,0 +1,33 @@
+# (c) @AvishkarPatil | @EverythingSuckz
+
+from os import getenv, environ
+from dotenv import load_dotenv
+
+load_dotenv()
+
+
+class Var(object):
+ API_ID = int(getenv('API_ID'))
+ API_HASH = str(getenv('API_HASH'))
+ BOT_TOKEN = str(getenv('BOT_TOKEN'))
+ SESSION_NAME = str(getenv('SESSION_NAME', 'AviStreamBot'))
+ SLEEP_THRESHOLD = int(getenv('SLEEP_THRESHOLD', '60'))
+ WORKERS = int(getenv('WORKERS', '4'))
+ BIN_CHANNEL = int(getenv('BIN_CHANNEL'))
+ PORT = int(getenv('PORT', 8080))
+ BIND_ADRESS = str(getenv('WEB_SERVER_BIND_ADDRESS', '0.0.0.0'))
+ OWNER_ID = int(getenv('OWNER_ID', '797848243'))
+ NO_PORT = bool(getenv('NO_PORT', False))
+ APP_NAME = None
+ if 'DYNO' in environ:
+ ON_HEROKU = True
+ APP_NAME = str(getenv('APP_NAME'))
+ else:
+ ON_HEROKU = False
+ FQDN = str(getenv('FQDN', BIND_ADRESS)) if not ON_HEROKU or getenv('FQDN') else APP_NAME+'.herokuapp.com'
+ URL = "https://{}/".format(FQDN) if ON_HEROKU or NO_PORT else \
+ "http://{}:{}/".format(FQDN, PORT)
+ DATABASE_URL = str(getenv('DATABASE_URL'))
+ PING_INTERVAL = int(getenv('PING_INTERVAL', '500'))
+ UPDATES_CHANNEL = str(getenv('UPDATES_CHANNEL', None))
+ BANNED_CHANNELS = list(set(int(x) for x in str(getenv("BANNED_CHANNELS", "-1001296894100")).split()))
diff --git a/app.json b/app.json
new file mode 100644
index 0000000..8054b3b
--- /dev/null
+++ b/app.json
@@ -0,0 +1,98 @@
+{
+ "name": "Avi-FileStreamBot",
+ "description": "A Pyrogram Telegram bot to Stream Telegram files to web. @Avishkarpatil",
+ "keywords": [
+ "telegram",
+ "stream",
+ "web",
+ "pyrogram",
+ "aiohttp",
+ "python",
+ "plugin",
+ "modular",
+ "media"
+ ],
+ "repository": "https://github.com/avipatilpro/FileStreamBot/",
+ "success_url": "/",
+ "logo": "https://i.ibb.co/ZJzJ9Hq/link-3x.png",
+ "website": "avipatilweb.ml",
+ "env": {
+ "ENV": {
+ "description": "Set this to True if you don't want to crash the bot",
+ "value": "True"
+ },
+ "APP_NAME": {
+ "description": "Copy-Paste the app name that you just typed above."
+ },
+ "API_ID": {
+ "description": "Get this value from https://my.telegram.org"
+ },
+ "API_HASH": {
+ "description": "Get this value from https://my.telegram.org"
+ },
+ "BOT_TOKEN": {
+ "description": "Get this value from @BotFather"
+ },
+ "BIN_CHANNEL": {
+ "description": "The BIN Channel ID. Read the readme for more info about this var"
+
+ },
+ "PING_INTERVAL": {
+ "description": "Add Ping Interval Default Is 500",
+ "required": false
+ },
+ "DATABASE_URL": {
+ "description": "MongoDB URI for saving User IDs when they first Start the Bot. We will use that for Broadcasting to them. I will try to add more features related with Database. If you need help to get the URI you can ask in Support Group: https://t.me/linux_repo"
+ },
+ "OWNER_ID": {
+ "description": "Your Telegram User ID"
+ },
+ "BANNED_CHANNELS": {
+ "description": "Put IDs of Banned Channels where bot will not work. You can add multiple IDs & separate with Space.",
+ "required": false
+ },
+ "UPDATES_CHANNEL": {
+ "description": "Put a Public Channel Username, so every user have to Join that channel to use the bot. Must add bot to channel as Admin to work properly.",
+ "required": false
+
+ },
+ "SLEEP_THRESHOLD": {
+ "description": "Floodwait Sleep timer. Read the readme for more info about this var",
+ "required": false
+ },
+ "WORKERS": {
+ "description": "No. of workers that is to be assigned. Read the readme for more info about this var",
+ "required": false
+ },
+ "PORT": {
+ "description": "Port that you want your webapp to be listened to. Read the readme for more info about this var",
+ "required": false
+ },
+ "NO_PORT": {
+ "description": "If you don't want your port to be displayed. Read the readme for more info about this var",
+ "value": "False",
+ "required": false
+ },
+ "BIND_ADRESS": {
+ "description": "Read the readme for more info about this var",
+ "required": false
+ },
+ "FQDN": {
+ "description": "Read the readme for more info about this var",
+ "required": false
+ },
+ "SESSION_NAME": {
+ "description": " Session Name for Bot [ Add AvishkarPatil ]",
+ "required": false
+ }
+ },
+ "buildpacks": [{
+ "url": "heroku/python"
+ }],
+ "formation": {
+ "web": {
+ "quantity": 1,
+ "size": "free"
+ }
+ }
+}
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..6460be1
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,7 @@
+pyrogram>=2.0.0,<=2.0.30
+tgcrypto<=1.2.3
+aiohttp<=3.8.1
+python-dotenv<=0.20.0
+motor
+aiofiles
+dnspython