Spaces:
Sleeping
Sleeping
Dr.Caduceus commited on
Commit ·
af2b875
unverified ·
0
Parent(s):
Initial commit.
Browse files- Dockerfile +8 -0
- Procfile +1 -0
- README.md +115 -0
- bot/__init__.py +19 -0
- bot/__main__.py +7 -0
- bot/config.py +53 -0
- bot/modules/decorators.py +16 -0
- bot/modules/static.py +48 -0
- bot/modules/telegram.py +56 -0
- bot/plugins/callback.py +27 -0
- bot/plugins/commands.py +45 -0
- bot/plugins/deeplinks.py +21 -0
- bot/plugins/files.py +126 -0
- bot/server/__init__.py +30 -0
- bot/server/error.py +31 -0
- bot/server/main.py +41 -0
- bot/server/templates/player.html +155 -0
- requirements.txt +5 -0
- runtime.txt +1 -0
Dockerfile
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
COPY . /app
|
| 5 |
+
|
| 6 |
+
RUN pip install -r requirements.txt
|
| 7 |
+
|
| 8 |
+
CMD ["python", "-m", "bot"]
|
Procfile
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
web: python -m bot
|
README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<div align="center"><h1>🌐File Stream Bot</h1>
|
| 2 |
+
<b>An open-source Python Telegram bot to transmit Telegram files over HTTP.</b>
|
| 3 |
+
|
| 4 |
+
<a href="https://t.me/DrFileStreamBot"><b>Demo Bot</b></a>
|
| 5 |
+
</div><br>
|
| 6 |
+
|
| 7 |
+
## **📑 INDEX**
|
| 8 |
+
|
| 9 |
+
* [**⚙️ Installation**](#installation)
|
| 10 |
+
* [Python & Git](#i-1)
|
| 11 |
+
* [Download](#i-2)
|
| 12 |
+
* [Requirements](#i-3)
|
| 13 |
+
* [**📝 Variables**](#variables)
|
| 14 |
+
* [**🕹 Deployment**](#deployment)
|
| 15 |
+
* [Locally](#d-1)
|
| 16 |
+
* [Docker](#d-2)
|
| 17 |
+
* [**⛑️ Need help!**](#help)
|
| 18 |
+
* [**❤️ Credits & Thanks**](#credits)
|
| 19 |
+
|
| 20 |
+
<a name="installation"></a>
|
| 21 |
+
|
| 22 |
+
## ⚙️ Installation
|
| 23 |
+
|
| 24 |
+
<a name="i-1"></a>
|
| 25 |
+
|
| 26 |
+
**1.Install Python & Git:**
|
| 27 |
+
|
| 28 |
+
For Windows:
|
| 29 |
+
```
|
| 30 |
+
winget install Python.Python.3.11
|
| 31 |
+
winget install Git.Git
|
| 32 |
+
```
|
| 33 |
+
For Linux:
|
| 34 |
+
```
|
| 35 |
+
sudo apt-get update && sudo apt-get install -y python3.11 git pip
|
| 36 |
+
```
|
| 37 |
+
For macOS:
|
| 38 |
+
```
|
| 39 |
+
brew install python@3.11 git
|
| 40 |
+
```
|
| 41 |
+
For Termux:
|
| 42 |
+
```
|
| 43 |
+
pkg install python -y
|
| 44 |
+
pkg install git -y
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
<a name="i-2"></a>
|
| 48 |
+
|
| 49 |
+
**2.Download repository:**
|
| 50 |
+
```
|
| 51 |
+
git clone https://github.com/TheCaduceus/FileStreamBot.git
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
**3.Change Directory:**
|
| 55 |
+
|
| 56 |
+
```
|
| 57 |
+
cd FileStreamBot
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
<a name="i-3"></a>
|
| 61 |
+
|
| 62 |
+
**4.Install requirements:**
|
| 63 |
+
|
| 64 |
+
```
|
| 65 |
+
pip install -r requirements.txt
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
<a name="variables"></a>
|
| 69 |
+
|
| 70 |
+
## 📝 Variables
|
| 71 |
+
**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.**
|
| 72 |
+
* `API_ID`|`TELEGRAM_API_ID`: API ID of your Telegram account, can be obtained from [My Telegram](https://my.telegram.org). `int`
|
| 73 |
+
* `API_HASH`|`TELEGRAM_API_HASH`: API hash of your Telegram account, can be obtained from [My Telegram](https://my.telegram.org). `str`
|
| 74 |
+
* `OWNER_ID`: ID of your Telegram account, can be obtained by sending **/info** to [@DrFileStreamBot](https://t.me/DrFileStreamBot). `int`
|
| 75 |
+
* `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`
|
| 76 |
+
* `BOT_USERNAME`|`TELEGRAM_BOT_USERNAME`: Username of your Telegram bot, create one using [@BotFather](https://t.me/BotFather). `str`
|
| 77 |
+
* `BOT_TOKEN`|`TELEGRAM_BOT_TOKEN`: Telegram API token of your bot, can be obtained from [@BotFather](https://t.me/BotFather). `str`
|
| 78 |
+
* `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`
|
| 79 |
+
* `BOT_WORKERS`: Number of updates bot should process from Telegram at once, by default to 10 updates. `int`
|
| 80 |
+
* `SECRET_CODE_LENGTH`: Number of characters that file code should contain, by default to 12 characters. `int`
|
| 81 |
+
* `BASE_URL`: Base URL that bot should use while generating file links, can be FQDN and by default to `127.0.0.1`. `str`
|
| 82 |
+
* `BIND_ADDRESS`: Bind address for web server, by default to `0.0.0.0` to run on all possible addresses. `str`
|
| 83 |
+
* `PORT`: Port for web server to run on, by default to `8080`. `int`
|
| 84 |
+
|
| 85 |
+
## 🕹 Deployment
|
| 86 |
+
|
| 87 |
+
<a name="d-1"></a>
|
| 88 |
+
|
| 89 |
+
**1.Running locally:**
|
| 90 |
+
```
|
| 91 |
+
python -m bot
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
<a name="d-2"></a>
|
| 95 |
+
|
| 96 |
+
**2.Using Docker:** *(Recommended)*
|
| 97 |
+
* Build own Docker image:
|
| 98 |
+
```
|
| 99 |
+
docker build -t file-stream-bot .
|
| 100 |
+
```
|
| 101 |
+
* Run the Docker container:
|
| 102 |
+
```
|
| 103 |
+
docker run -p 8080:8080 file-stream-bot
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
<a name="help"></a>
|
| 107 |
+
|
| 108 |
+
## ⛑️ Need help!
|
| 109 |
+
- Ask questions or doubts [here](https://t.me/DrDiscussion).
|
| 110 |
+
|
| 111 |
+
<a name="credits"></a>
|
| 112 |
+
|
| 113 |
+
## ❤️ Credits & Thanks
|
| 114 |
+
|
| 115 |
+
[**Dr.Caduceus**](https://github.com/TheCaduceus): Owner & developer of Microsoft E5 Auto Renewal Tool.<br>
|
bot/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pyrogram import Client
|
| 2 |
+
from logging import getLogger
|
| 3 |
+
from logging.config import dictConfig
|
| 4 |
+
from .config import Telegram, LOGGER_CONFIG_JSON
|
| 5 |
+
|
| 6 |
+
dictConfig(LOGGER_CONFIG_JSON)
|
| 7 |
+
|
| 8 |
+
version = 1.0
|
| 9 |
+
logger = getLogger('bot')
|
| 10 |
+
|
| 11 |
+
TelegramBot = Client(
|
| 12 |
+
name="bot",
|
| 13 |
+
api_id = Telegram.API_ID,
|
| 14 |
+
api_hash = Telegram.API_HASH,
|
| 15 |
+
bot_token = Telegram.BOT_TOKEN,
|
| 16 |
+
plugins={"root": "bot/plugins"},
|
| 17 |
+
workers = Telegram.BOT_WORKERS,
|
| 18 |
+
max_concurrent_transmissions=1000
|
| 19 |
+
)
|
bot/__main__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from bot import TelegramBot, logger
|
| 2 |
+
from bot.server import server
|
| 3 |
+
|
| 4 |
+
if __name__ == '__main__':
|
| 5 |
+
logger.info('Initializing...')
|
| 6 |
+
TelegramBot.loop.create_task(server.serve())
|
| 7 |
+
TelegramBot.run()
|
bot/config.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from os import environ as env
|
| 2 |
+
|
| 3 |
+
class Telegram:
|
| 4 |
+
API_ID = env.get("TELEGRAM_API_ID", 1234)
|
| 5 |
+
API_HASH = env.get("TELEGRAM_API_HASH", "xyz")
|
| 6 |
+
OWNER_ID = int(env.get("OWNER_ID", 1234567890))
|
| 7 |
+
ALLOWED_USER_IDS = env.get("ALLOWED_USER_IDS", "").split()
|
| 8 |
+
BOT_USERNAME = env.get("TELEGRAM_BOT_USERNAME", "BotFather")
|
| 9 |
+
BOT_TOKEN = env.get("TELEGRAM_BOT_TOKEN", "1234:abcd")
|
| 10 |
+
CHANNEL_ID = int(env.get("TELEGRAM_CHANNEL_ID", -1001234567890))
|
| 11 |
+
BOT_WORKERS = int(env.get("BOT_WORKERS", 10))
|
| 12 |
+
SECRET_CODE_LENGTH = int(env.get("SECRET_CODE_LENGTH", 12))
|
| 13 |
+
|
| 14 |
+
class Server:
|
| 15 |
+
BASE_URL = env.get("BASE_URL", "http://127.0.0.1:8080")
|
| 16 |
+
BIND_ADDRESS = env.get("BIND_ADDRESS", "0.0.0.0")
|
| 17 |
+
PORT = int(env.get("PORT", 8080))
|
| 18 |
+
|
| 19 |
+
# LOGGING CONFIGURATION
|
| 20 |
+
LOGGER_CONFIG_JSON = {
|
| 21 |
+
'version': 1,
|
| 22 |
+
'formatters': {
|
| 23 |
+
'default': {
|
| 24 |
+
'format': '[%(asctime)s][%(name)s][%(levelname)s] -> %(message)s',
|
| 25 |
+
'datefmt': '%d/%m/%Y %H:%M:%S'
|
| 26 |
+
},
|
| 27 |
+
},
|
| 28 |
+
'handlers': {
|
| 29 |
+
'file_handler': {
|
| 30 |
+
'class': 'logging.FileHandler',
|
| 31 |
+
'filename': 'event-log.txt',
|
| 32 |
+
'formatter': 'default'
|
| 33 |
+
},
|
| 34 |
+
'stream_handler': {
|
| 35 |
+
'class': 'logging.StreamHandler',
|
| 36 |
+
'formatter': 'default'
|
| 37 |
+
}
|
| 38 |
+
},
|
| 39 |
+
'loggers': {
|
| 40 |
+
'uvicorn': {
|
| 41 |
+
'level': 'INFO',
|
| 42 |
+
'handlers': ['file_handler', 'stream_handler']
|
| 43 |
+
},
|
| 44 |
+
'uvicorn.error': {
|
| 45 |
+
'level': 'WARNING',
|
| 46 |
+
'handlers': ['file_handler', 'stream_handler']
|
| 47 |
+
},
|
| 48 |
+
'bot': {
|
| 49 |
+
'level': 'INFO',
|
| 50 |
+
'handlers': ['file_handler', 'stream_handler']
|
| 51 |
+
}
|
| 52 |
+
}
|
| 53 |
+
}
|
bot/modules/decorators.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pyrogram import Client
|
| 2 |
+
from pyrogram.types import Message, CallbackQuery
|
| 3 |
+
from typing import Union, Callable
|
| 4 |
+
from functools import wraps
|
| 5 |
+
from bot.config import Telegram
|
| 6 |
+
|
| 7 |
+
def verify_user(func: Callable):
|
| 8 |
+
|
| 9 |
+
@wraps(func)
|
| 10 |
+
async def decorator(client: Client, update: Union[Message, CallbackQuery]):
|
| 11 |
+
user_id = str(update.from_user.id)
|
| 12 |
+
|
| 13 |
+
if not Telegram.ALLOWED_USER_IDS or user_id in Telegram.ALLOWED_USER_IDS:
|
| 14 |
+
return await func(client, update)
|
| 15 |
+
|
| 16 |
+
return decorator
|
bot/modules/static.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
WelcomeText = \
|
| 2 |
+
"""
|
| 3 |
+
Hi **%(first_name)s**, send me a file or add me as an admin to any channel to instantly generate file links.
|
| 4 |
+
|
| 5 |
+
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.
|
| 6 |
+
|
| 7 |
+
- /start to get this message.
|
| 8 |
+
- /info to get user info.
|
| 9 |
+
- /log to get bot logs. (admin only!)
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
FileLinksText = \
|
| 13 |
+
"""
|
| 14 |
+
**Download Link:**
|
| 15 |
+
`%(dl_link)s`
|
| 16 |
+
**Telegram File:**
|
| 17 |
+
`%(tg_link)s`
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
MediaLinksText = \
|
| 21 |
+
"""
|
| 22 |
+
**Download Link:**
|
| 23 |
+
`%(dl_link)s`
|
| 24 |
+
**Stream Link:**
|
| 25 |
+
`%(stream_link)s`
|
| 26 |
+
**Telegram File:**
|
| 27 |
+
`%(tg_link)s`
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
InvalidQueryText = \
|
| 31 |
+
"""
|
| 32 |
+
Query data mismatched.
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
MessageNotExist = \
|
| 36 |
+
"""
|
| 37 |
+
File revoked or not exist.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
LinkRevokedText = \
|
| 41 |
+
"""
|
| 42 |
+
The link has been revoked. It may take some time for the changes to take effect.
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
InvalidPayloadText = \
|
| 46 |
+
"""
|
| 47 |
+
Invalid payload.
|
| 48 |
+
"""
|
bot/modules/telegram.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pyrogram.types import Message
|
| 2 |
+
from datetime import datetime
|
| 3 |
+
from mimetypes import guess_type
|
| 4 |
+
from bot import TelegramBot
|
| 5 |
+
from bot.config import Telegram
|
| 6 |
+
from bot.server.error import abort
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
async def get_message(message_id: int):
|
| 10 |
+
message = None
|
| 11 |
+
|
| 12 |
+
try:
|
| 13 |
+
message = await TelegramBot.get_messages(
|
| 14 |
+
chat_id=Telegram.CHANNEL_ID,
|
| 15 |
+
message_ids=message_id
|
| 16 |
+
)
|
| 17 |
+
if message.empty: message = None
|
| 18 |
+
except Exception:
|
| 19 |
+
pass
|
| 20 |
+
|
| 21 |
+
return message
|
| 22 |
+
|
| 23 |
+
async def get_file_properties(msg: Message):
|
| 24 |
+
attributes = (
|
| 25 |
+
'document',
|
| 26 |
+
'video',
|
| 27 |
+
'audio',
|
| 28 |
+
'voice',
|
| 29 |
+
'photo',
|
| 30 |
+
'video_note'
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
for attribute in attributes:
|
| 34 |
+
media = getattr(msg, attribute, None)
|
| 35 |
+
if media:
|
| 36 |
+
file_type = attribute
|
| 37 |
+
break
|
| 38 |
+
|
| 39 |
+
if not media: abort(400, 'Unknown file type.')
|
| 40 |
+
|
| 41 |
+
file_name = getattr(media, 'file_name', None)
|
| 42 |
+
|
| 43 |
+
if not file_name:
|
| 44 |
+
file_format = {
|
| 45 |
+
'video': 'mp4',
|
| 46 |
+
'audio': 'mp3',
|
| 47 |
+
'voice': 'ogg',
|
| 48 |
+
'photo': 'jpg',
|
| 49 |
+
'video_note': 'mp4'
|
| 50 |
+
}.get(attribute)
|
| 51 |
+
date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
| 52 |
+
file_name = f'{file_type}-{date}.{file_format}'
|
| 53 |
+
|
| 54 |
+
mime_type = guess_type(file_name)[0] or 'application/octet-stream'
|
| 55 |
+
|
| 56 |
+
return file_name, mime_type
|
bot/plugins/callback.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pyrogram.types import CallbackQuery
|
| 2 |
+
from bot import TelegramBot
|
| 3 |
+
from bot.modules.static import *
|
| 4 |
+
from bot.modules.decorators import verify_user
|
| 5 |
+
from bot.modules.telegram import get_message
|
| 6 |
+
|
| 7 |
+
@TelegramBot.on_callback_query()
|
| 8 |
+
@verify_user
|
| 9 |
+
async def manage_callback(bot, q: CallbackQuery):
|
| 10 |
+
query = q.data
|
| 11 |
+
if query.startswith('rm_'):
|
| 12 |
+
sq = query.split('_')
|
| 13 |
+
|
| 14 |
+
if len(sq) != 3:
|
| 15 |
+
return await q.answer(InvalidQueryText, show_alert=True)
|
| 16 |
+
|
| 17 |
+
message = await get_message(int(sq[1]))
|
| 18 |
+
|
| 19 |
+
if not message:
|
| 20 |
+
return await q.answer(MessageNotExist, show_alert=True)
|
| 21 |
+
if sq[2] != message.caption:
|
| 22 |
+
return await q.answer(InvalidQueryText, show_alert=True)
|
| 23 |
+
|
| 24 |
+
await message.delete()
|
| 25 |
+
await q.answer(LinkRevokedText, show_alert=True)
|
| 26 |
+
else:
|
| 27 |
+
await q.answer(InvalidQueryText, show_alert=True)
|
bot/plugins/commands.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pyrogram import filters
|
| 2 |
+
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
|
| 3 |
+
from aiofiles import open as async_open
|
| 4 |
+
from aiofiles.os import remove as async_rm
|
| 5 |
+
from bot import TelegramBot, logger
|
| 6 |
+
from bot.config import Telegram
|
| 7 |
+
from bot.modules.static import *
|
| 8 |
+
from .deeplinks import deeplinks
|
| 9 |
+
from bot.modules.decorators import verify_user
|
| 10 |
+
|
| 11 |
+
@TelegramBot.on_message(filters.command('start') & filters.private)
|
| 12 |
+
@verify_user
|
| 13 |
+
async def start(_, msg: Message):
|
| 14 |
+
if len(msg.command) != 1:
|
| 15 |
+
return await deeplinks(msg, msg.command[1])
|
| 16 |
+
|
| 17 |
+
await msg.reply(
|
| 18 |
+
text=WelcomeText % {'first_name': msg.from_user.first_name},
|
| 19 |
+
quote=True,
|
| 20 |
+
reply_markup=InlineKeyboardMarkup(
|
| 21 |
+
[
|
| 22 |
+
[
|
| 23 |
+
InlineKeyboardButton('Add to Channel', url=f'https://t.me/{Telegram.BOT_USERNAME}?startchannel&admin=post_messages+edit_messages+delete_messages')
|
| 24 |
+
]
|
| 25 |
+
]
|
| 26 |
+
)
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
@TelegramBot.on_message(filters.command('info') & filters.private)
|
| 30 |
+
@verify_user
|
| 31 |
+
async def user_info(_, msg: Message):
|
| 32 |
+
await msg.reply(text=f'`{msg.from_user}`', quote=True)
|
| 33 |
+
|
| 34 |
+
filename = f'{msg.from_user.id}.json'
|
| 35 |
+
async with async_open(filename, "w") as file:
|
| 36 |
+
await file.write(f'{msg.from_user}')
|
| 37 |
+
|
| 38 |
+
await msg.reply_document(filename)
|
| 39 |
+
await async_rm(filename)
|
| 40 |
+
|
| 41 |
+
@TelegramBot.on_message(filters.private & filters.command('log') & filters.user(Telegram.OWNER_ID))
|
| 42 |
+
async def send_log(_, msg: Message):
|
| 43 |
+
await msg.reply_document('event-log.txt', quote=True)
|
| 44 |
+
|
| 45 |
+
logger.info('Bot is now started!')
|
bot/plugins/deeplinks.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pyrogram.types import Message
|
| 2 |
+
from bot.modules.static import *
|
| 3 |
+
from bot.modules.telegram import get_message
|
| 4 |
+
|
| 5 |
+
async def deeplinks(msg: Message, payload: str):
|
| 6 |
+
if payload.startswith('file_'):
|
| 7 |
+
sp = payload.split('_')
|
| 8 |
+
|
| 9 |
+
if len(sp) != 3:
|
| 10 |
+
return await msg.reply(InvalidPayloadText, quote=True)
|
| 11 |
+
|
| 12 |
+
message = await get_message(int(sp[1]))
|
| 13 |
+
|
| 14 |
+
if not message:
|
| 15 |
+
return await msg.reply(MessageNotExist)
|
| 16 |
+
if sp[2] != message.caption:
|
| 17 |
+
return await msg.reply(InvalidPayloadText, quote=True)
|
| 18 |
+
|
| 19 |
+
await message.copy(chat_id=msg.from_user.id, caption="")
|
| 20 |
+
else:
|
| 21 |
+
await msg.reply(InvalidPayloadText, quote=True)
|
bot/plugins/files.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pyrogram import filters, errors
|
| 2 |
+
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
|
| 3 |
+
from secrets import token_hex
|
| 4 |
+
from bot import TelegramBot
|
| 5 |
+
from bot.config import Telegram, Server
|
| 6 |
+
from bot.modules.decorators import verify_user
|
| 7 |
+
from bot.modules.static import *
|
| 8 |
+
|
| 9 |
+
@TelegramBot.on_message(
|
| 10 |
+
filters.private
|
| 11 |
+
& (
|
| 12 |
+
filters.document
|
| 13 |
+
| filters.video
|
| 14 |
+
| filters.video_note
|
| 15 |
+
| filters.audio
|
| 16 |
+
| filters.voice
|
| 17 |
+
| filters.photo
|
| 18 |
+
)
|
| 19 |
+
)
|
| 20 |
+
@verify_user
|
| 21 |
+
async def handle_user_file(_, msg: Message):
|
| 22 |
+
secret_code = token_hex(Telegram.SECRET_CODE_LENGTH)
|
| 23 |
+
file = await msg.copy(
|
| 24 |
+
chat_id=Telegram.CHANNEL_ID,
|
| 25 |
+
caption=f'`{secret_code}`'
|
| 26 |
+
)
|
| 27 |
+
file_id = file.id
|
| 28 |
+
|
| 29 |
+
dl_link = f'{Server.BASE_URL}/dl/{file_id}?code={secret_code}'
|
| 30 |
+
tg_link = f'{Server.BASE_URL}/file/{file_id}?code={secret_code}'
|
| 31 |
+
deep_link = f'https://t.me/{Telegram.BOT_USERNAME}?start=file_{file_id}_{secret_code}'
|
| 32 |
+
|
| 33 |
+
if (msg.document and 'video' in msg.document.mime_type) or msg.video:
|
| 34 |
+
stream_link = f'{Server.BASE_URL}/stream/{file_id}?code={secret_code}'
|
| 35 |
+
await msg.reply(
|
| 36 |
+
text=MediaLinksText % {'dl_link': dl_link, 'tg_link': tg_link, 'stream_link': stream_link},
|
| 37 |
+
quote=True,
|
| 38 |
+
reply_markup=InlineKeyboardMarkup(
|
| 39 |
+
[
|
| 40 |
+
[
|
| 41 |
+
InlineKeyboardButton('Download', url=dl_link),
|
| 42 |
+
InlineKeyboardButton('Stream', url=stream_link)
|
| 43 |
+
],
|
| 44 |
+
[
|
| 45 |
+
InlineKeyboardButton('Get File', url=deep_link),
|
| 46 |
+
InlineKeyboardButton('Revoke', callback_data=f'rm_{file_id}_{secret_code}')
|
| 47 |
+
]
|
| 48 |
+
]
|
| 49 |
+
)
|
| 50 |
+
)
|
| 51 |
+
else:
|
| 52 |
+
await msg.reply(
|
| 53 |
+
text=FileLinksText % {'dl_link': dl_link, 'tg_link': tg_link},
|
| 54 |
+
quote=True,
|
| 55 |
+
reply_markup=InlineKeyboardMarkup(
|
| 56 |
+
[
|
| 57 |
+
[
|
| 58 |
+
InlineKeyboardButton('Download', url=dl_link),
|
| 59 |
+
InlineKeyboardButton('Get File', url=deep_link)
|
| 60 |
+
],
|
| 61 |
+
[
|
| 62 |
+
InlineKeyboardButton('Revoke', callback_data=f'rm_{file_id}_{secret_code}')
|
| 63 |
+
]
|
| 64 |
+
]
|
| 65 |
+
)
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
@TelegramBot.on_message(
|
| 69 |
+
filters.channel
|
| 70 |
+
& ~filters.forwarded
|
| 71 |
+
& ~filters.media_group
|
| 72 |
+
& (
|
| 73 |
+
filters.document
|
| 74 |
+
| filters.video
|
| 75 |
+
| filters.video_note
|
| 76 |
+
| filters.audio
|
| 77 |
+
| filters.voice
|
| 78 |
+
| filters.photo
|
| 79 |
+
)
|
| 80 |
+
)
|
| 81 |
+
@verify_user
|
| 82 |
+
async def handle_channel_file(_, msg: Message):
|
| 83 |
+
if msg.caption and '#pass' in msg.caption:
|
| 84 |
+
return
|
| 85 |
+
|
| 86 |
+
secret_code = token_hex(Telegram.SECRET_CODE_LENGTH)
|
| 87 |
+
|
| 88 |
+
try:
|
| 89 |
+
file = await msg.copy(
|
| 90 |
+
chat_id=Telegram.CHANNEL_ID,
|
| 91 |
+
caption=f'`{secret_code}`'
|
| 92 |
+
)
|
| 93 |
+
except (errors.ChatForwardsRestricted, errors.MessageIdInvalid, errors.ChannelPrivate):
|
| 94 |
+
return
|
| 95 |
+
|
| 96 |
+
file_id = file.id
|
| 97 |
+
|
| 98 |
+
dl_link = f'{Server.BASE_URL}/dl/{file_id}?code={secret_code}'
|
| 99 |
+
tg_link = f'{Server.BASE_URL}/file/{file_id}?code={secret_code}'
|
| 100 |
+
|
| 101 |
+
if (msg.document and 'video' in msg.document.mime_type) or msg.video:
|
| 102 |
+
stream_link = f'{Server.BASE_URL}/stream/{file_id}?code={secret_code}'
|
| 103 |
+
await msg.edit_reply_markup(
|
| 104 |
+
InlineKeyboardMarkup(
|
| 105 |
+
[
|
| 106 |
+
[
|
| 107 |
+
InlineKeyboardButton('Download', url=dl_link),
|
| 108 |
+
InlineKeyboardButton('Stream', url=stream_link)
|
| 109 |
+
],
|
| 110 |
+
[
|
| 111 |
+
InlineKeyboardButton('Get File', url=tg_link)
|
| 112 |
+
]
|
| 113 |
+
]
|
| 114 |
+
)
|
| 115 |
+
)
|
| 116 |
+
else:
|
| 117 |
+
await msg.edit_reply_markup(
|
| 118 |
+
InlineKeyboardMarkup(
|
| 119 |
+
[
|
| 120 |
+
[
|
| 121 |
+
InlineKeyboardButton('Download', url=dl_link),
|
| 122 |
+
InlineKeyboardButton('Get File', url=tg_link)
|
| 123 |
+
]
|
| 124 |
+
]
|
| 125 |
+
)
|
| 126 |
+
)
|
bot/server/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from quart import Quart
|
| 2 |
+
from uvicorn import Config, Server as UvicornServer
|
| 3 |
+
from logging import getLogger
|
| 4 |
+
from bot.config import Server, LOGGER_CONFIG_JSON
|
| 5 |
+
|
| 6 |
+
from . import main, error
|
| 7 |
+
|
| 8 |
+
logger = getLogger('uvicorn')
|
| 9 |
+
instance = Quart(__name__)
|
| 10 |
+
|
| 11 |
+
@instance.before_serving
|
| 12 |
+
async def before_serve():
|
| 13 |
+
logger.info('Web server is started!')
|
| 14 |
+
logger.info(f'Server running on {Server.BIND_ADDRESS}:{Server.PORT}')
|
| 15 |
+
|
| 16 |
+
instance.register_blueprint(main.bp)
|
| 17 |
+
|
| 18 |
+
instance.register_error_handler(400, error.invalid_request)
|
| 19 |
+
instance.register_error_handler(404, error.not_found)
|
| 20 |
+
instance.register_error_handler(405, error.invalid_method)
|
| 21 |
+
instance.register_error_handler(error.HTTPError, error.http_error)
|
| 22 |
+
|
| 23 |
+
server = UvicornServer(
|
| 24 |
+
Config(
|
| 25 |
+
app = instance,
|
| 26 |
+
host = Server.BIND_ADDRESS,
|
| 27 |
+
port = Server.PORT,
|
| 28 |
+
log_config = LOGGER_CONFIG_JSON
|
| 29 |
+
)
|
| 30 |
+
)
|
bot/server/error.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class HTTPError(Exception):
|
| 2 |
+
status_code:int = None
|
| 3 |
+
description:str = None
|
| 4 |
+
def __init__(self, status_code, description):
|
| 5 |
+
self.status_code = status_code
|
| 6 |
+
self.description = description
|
| 7 |
+
super().__init__(self.status_code, self.description)
|
| 8 |
+
|
| 9 |
+
error_messages = {
|
| 10 |
+
400: 'Invalid request.',
|
| 11 |
+
401: 'File code is required to download the file.',
|
| 12 |
+
403: 'Invalid file code.',
|
| 13 |
+
404: 'File not found.',
|
| 14 |
+
500: 'Internal server error.'
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
async def invalid_request(_):
|
| 18 |
+
return 'Invalid request.', 400
|
| 19 |
+
|
| 20 |
+
async def not_found(_):
|
| 21 |
+
return 'Resource not found.', 404
|
| 22 |
+
|
| 23 |
+
async def invalid_method(_):
|
| 24 |
+
return 'Invalid request method.', 405
|
| 25 |
+
|
| 26 |
+
async def http_error(error: HTTPError):
|
| 27 |
+
error_message = error_messages.get(error.status_code)
|
| 28 |
+
return error.description or error_message, error.status_code
|
| 29 |
+
|
| 30 |
+
def abort(status_code: int = 500, description: str = None):
|
| 31 |
+
raise HTTPError(status_code, description)
|
bot/server/main.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from quart import Blueprint, Response, request, render_template, redirect
|
| 2 |
+
from .error import abort
|
| 3 |
+
from bot import version, TelegramBot
|
| 4 |
+
from bot.config import Telegram, Server
|
| 5 |
+
from bot.modules.telegram import get_message, get_file_properties
|
| 6 |
+
|
| 7 |
+
bp = Blueprint('main', __name__)
|
| 8 |
+
|
| 9 |
+
@bp.route('/')
|
| 10 |
+
async def home():
|
| 11 |
+
return redirect(f'https://t.me/{Telegram.BOT_USERNAME}')
|
| 12 |
+
|
| 13 |
+
@bp.route('/dl/<int:file_id>')
|
| 14 |
+
async def transmit_file(file_id):
|
| 15 |
+
file = await get_message(int(file_id)) or abort(404)
|
| 16 |
+
code = request.args.get('code') or abort(401)
|
| 17 |
+
|
| 18 |
+
if code != file.caption:
|
| 19 |
+
abort(403)
|
| 20 |
+
|
| 21 |
+
file_name, mime_type = await get_file_properties(file)
|
| 22 |
+
headers = {
|
| 23 |
+
'Content-Type': mime_type,
|
| 24 |
+
'Content-Disposition': f'attachment; filename="{file_name}"'
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
file_stream = TelegramBot.stream_media(file)
|
| 28 |
+
|
| 29 |
+
return Response(file_stream, headers=headers)
|
| 30 |
+
|
| 31 |
+
@bp.route('/stream/<int:file_id>')
|
| 32 |
+
async def stream_file(file_id):
|
| 33 |
+
code = request.args.get('code') or abort(401)
|
| 34 |
+
|
| 35 |
+
return await render_template('player.html', mediaLink=f'{Server.BASE_URL}/dl/{file_id}?code={code}')
|
| 36 |
+
|
| 37 |
+
@bp.route('/file/<int:file_id>')
|
| 38 |
+
async def file_deeplink(file_id):
|
| 39 |
+
code = request.args.get('code') or abort(401)
|
| 40 |
+
|
| 41 |
+
return redirect(f'https://t.me/{Telegram.BOT_USERNAME}?start=file_{file_id}_{code}')
|
bot/server/templates/player.html
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
<!DOCTYPE html>
|
| 3 |
+
<html lang="en">
|
| 4 |
+
<head>
|
| 5 |
+
<title>Play Files</title>
|
| 6 |
+
|
| 7 |
+
<meta charset="UTF-8">
|
| 8 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 9 |
+
<meta http-equiv="X-Frame-Options" content="deny">
|
| 10 |
+
|
| 11 |
+
<link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
|
| 12 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
| 13 |
+
|
| 14 |
+
<script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script>
|
| 15 |
+
|
| 16 |
+
<style>
|
| 17 |
+
html, body {
|
| 18 |
+
margin: 0;
|
| 19 |
+
height: 100%;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
#stream-media {
|
| 23 |
+
height: 100%;
|
| 24 |
+
width: 100%;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
#error-message {
|
| 28 |
+
color: red;
|
| 29 |
+
font-size: 24px;
|
| 30 |
+
text-align: center;
|
| 31 |
+
margin-top: 20px;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.plyr__video-wrapper .plyr-download-button{
|
| 35 |
+
position: absolute;
|
| 36 |
+
top: 10px;
|
| 37 |
+
left: 10px;
|
| 38 |
+
width: 30px;
|
| 39 |
+
height: 30px;
|
| 40 |
+
background-color: rgba(0, 0, 0, 0.7);
|
| 41 |
+
border-radius: 50%;
|
| 42 |
+
text-align: center;
|
| 43 |
+
line-height: 30px;
|
| 44 |
+
color: white;
|
| 45 |
+
z-index: 10;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
.plyr__volume {
|
| 49 |
+
max-width: initial;
|
| 50 |
+
min-width: initial;
|
| 51 |
+
width: auto;
|
| 52 |
+
position: relative;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
.plyr__video-wrapper .plyr-share-button{
|
| 57 |
+
position: absolute;
|
| 58 |
+
top: 50px;
|
| 59 |
+
left: 10px;
|
| 60 |
+
width: 30px;
|
| 61 |
+
height: 30px;
|
| 62 |
+
background-color: rgba(0, 0, 0, 0.7);
|
| 63 |
+
border-radius: 50%;
|
| 64 |
+
text-align: center;
|
| 65 |
+
line-height: 30px;
|
| 66 |
+
color: white;
|
| 67 |
+
z-index: 10;
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
.plyr__video-wrapper .plyr-download-button:hover,
|
| 71 |
+
.plyr__video-wrapper .plyr-share-button:hover{
|
| 72 |
+
background-color: rgba(255, 255, 255, 0.7);
|
| 73 |
+
color: black;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
.plyr__video-wrapper .plyr-download-button:before {
|
| 77 |
+
font-family: "Font Awesome 5 Free";
|
| 78 |
+
content: "\f019";
|
| 79 |
+
font-weight: bold;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.plyr__video-wrapper .plyr-share-button:before {
|
| 83 |
+
font-family: "Font Awesome 5 Free";
|
| 84 |
+
content: "\f064";
|
| 85 |
+
font-weight: bold;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
.plyr, .plyr__video-wrapper, .plyr__video-embed iframe {
|
| 89 |
+
height: 100%;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
</style>
|
| 93 |
+
</head>
|
| 94 |
+
|
| 95 |
+
<body>
|
| 96 |
+
<video id="stream-media" controls preload="auto">
|
| 97 |
+
<source src="" type="">
|
| 98 |
+
<p class="vjs-no-js">
|
| 99 |
+
To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video
|
| 100 |
+
</p>
|
| 101 |
+
</video>
|
| 102 |
+
|
| 103 |
+
<div id="error-message"></div>
|
| 104 |
+
|
| 105 |
+
<script>
|
| 106 |
+
var player = new Plyr('#stream-media', {
|
| 107 |
+
controls:['play-large', 'rewind', 'play', 'fast-forward', 'progress', 'current-time', 'mute', 'settings', 'pip', 'fullscreen'],
|
| 108 |
+
settings:['speed','loop'],
|
| 109 |
+
speed:{selected:1,options:[0.25,0.5,0.75,1,1.25,1.5,1.75,2]},
|
| 110 |
+
seek: 10,
|
| 111 |
+
keyboard: { focused: true, global: true },
|
| 112 |
+
});
|
| 113 |
+
|
| 114 |
+
var mediaLink = "{{ mediaLink }}";
|
| 115 |
+
|
| 116 |
+
if (mediaLink) {
|
| 117 |
+
document.querySelector('#stream-media source').setAttribute('src', mediaLink);
|
| 118 |
+
player.restart();
|
| 119 |
+
|
| 120 |
+
var downloadButton = document.createElement('div');
|
| 121 |
+
downloadButton.className = 'plyr-download-button';
|
| 122 |
+
|
| 123 |
+
downloadButton.onclick = function() {
|
| 124 |
+
event.stopPropagation();
|
| 125 |
+
var link = document.createElement('a');
|
| 126 |
+
link.href = mediaLink;
|
| 127 |
+
document.body.appendChild(link);
|
| 128 |
+
link.click();
|
| 129 |
+
document.body.removeChild(link);
|
| 130 |
+
};
|
| 131 |
+
|
| 132 |
+
player.elements.container.querySelector('.plyr__video-wrapper').appendChild(downloadButton);
|
| 133 |
+
|
| 134 |
+
var shareButton = document.createElement('div');
|
| 135 |
+
shareButton.className = 'plyr-share-button';
|
| 136 |
+
|
| 137 |
+
shareButton.onclick = function() {
|
| 138 |
+
event.stopPropagation();
|
| 139 |
+
if (navigator.share) {
|
| 140 |
+
navigator.share({
|
| 141 |
+
title: "Play",
|
| 142 |
+
url: window.location.href
|
| 143 |
+
});
|
| 144 |
+
}
|
| 145 |
+
};
|
| 146 |
+
|
| 147 |
+
player.elements.container.querySelector('.plyr__video-wrapper').appendChild(shareButton);
|
| 148 |
+
|
| 149 |
+
} else {
|
| 150 |
+
document.getElementById('error-message').textContent = 'Error: Media URL not provided';
|
| 151 |
+
}
|
| 152 |
+
</script>
|
| 153 |
+
|
| 154 |
+
</body>
|
| 155 |
+
</html>
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pyrogram
|
| 2 |
+
tgcrypto
|
| 3 |
+
quart
|
| 4 |
+
uvicorn
|
| 5 |
+
aiofiles
|
runtime.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
python-3.11.6
|