Commit ·
e1a2a92
0
Parent(s):
Initial commit
Browse files- .gitattributes +2 -0
- .gitignore +8 -0
- Dockerfile +18 -0
- LICENSE +21 -0
- README.md +170 -0
- config.py +60 -0
- docker_start.sh +3 -0
- main.py +356 -0
- render.yaml +24 -0
- requirements.txt +11 -0
- runtime.txt +1 -0
- sample.env +7 -0
- start_main.py +3 -0
- utils/bot_mode.py +197 -0
- utils/clients.py +113 -0
- utils/directoryHandler.py +387 -0
- utils/downloader.py +85 -0
- utils/extra.py +124 -0
- utils/logger.py +48 -0
- utils/streamer/__init__.py +82 -0
- utils/streamer/custom_dl.py +199 -0
- utils/streamer/file_properties.py +84 -0
- utils/uploader.py +69 -0
- website/VideoPlayer.html +123 -0
- website/home.html +158 -0
- website/static/assets/file-icon.svg +1 -0
- website/static/assets/folder-icon.svg +1 -0
- website/static/assets/folder-solid-icon.svg +1 -0
- website/static/assets/home-icon.svg +1 -0
- website/static/assets/info-icon-small.svg +1 -0
- website/static/assets/link-icon.svg +1 -0
- website/static/assets/load-icon.svg +1 -0
- website/static/assets/more-icon.svg +1 -0
- website/static/assets/pencil-icon.svg +1 -0
- website/static/assets/plus-icon.svg +1 -0
- website/static/assets/profile-icon.svg +1 -0
- website/static/assets/search-icon.svg +1 -0
- website/static/assets/share-icon.svg +1 -0
- website/static/assets/trash-icon.svg +1 -0
- website/static/assets/upload-icon.svg +1 -0
- website/static/home.css +527 -0
- website/static/js/apiHandler.js +347 -0
- website/static/js/extra.js +119 -0
- website/static/js/fileClickHandler.js +214 -0
- website/static/js/main.js +95 -0
- website/static/js/sidebar.js +102 -0
.gitattributes
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Auto detect text files and perform LF normalization
|
| 2 |
+
* text=auto
|
.gitignore
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.pyc
|
| 2 |
+
*.data
|
| 3 |
+
t.py
|
| 4 |
+
t2.py
|
| 5 |
+
cache
|
| 6 |
+
config copy.py
|
| 7 |
+
logs.txt
|
| 8 |
+
.env
|
Dockerfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use the official Python base image
|
| 2 |
+
FROM python:3.11.7-slim AS base
|
| 3 |
+
|
| 4 |
+
# Set the working directory inside the container
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Copy the requirements file to the working directory and install dependencies
|
| 8 |
+
COPY requirements.txt .
|
| 9 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 10 |
+
|
| 11 |
+
# Copy the application code to the working directory
|
| 12 |
+
COPY . .
|
| 13 |
+
|
| 14 |
+
# Expose the port on which the application will run
|
| 15 |
+
EXPOSE 8080
|
| 16 |
+
|
| 17 |
+
# Run the FastAPI application using uvicorn server
|
| 18 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2024 TechShreyash
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TGDrive - A Google Drive Clone with Telegram Storage
|
| 2 |
+
|
| 3 |
+
Welcome to TGDrive! This web application replicates Google Drive's functionalities using Telegram as its storage backend. Manage folders and files, perform actions like uploading, renaming, and deleting, utilize trash/bin support, enable permanent deletion, and share public links. The application offers admin login and automatically backs up the database to Telegram.
|
| 4 |
+
|
| 5 |
+
**Check out the [TGDrive Personal](https://github.com/TechShreyash/TGDrivePersonal) project for local desktop backup support.**
|
| 6 |
+
|
| 7 |
+
## Features
|
| 8 |
+
|
| 9 |
+
- **File Management:** Upload, rename, and delete files with integrated trash/bin functionality and permanent deletion support.
|
| 10 |
+
- **Folder Management:** Easily create, rename, and delete folders.
|
| 11 |
+
- **Sharing:** Seamlessly share public links for files and folders.
|
| 12 |
+
- **Admin Support:** Secure admin login for efficient management.
|
| 13 |
+
- **Automatic Backups:** Automated database backups sent directly to Telegram.
|
| 14 |
+
- **Multiple Bots/Clients:** Support for multiple bots/clients for file operations and streaming from Telegram.
|
| 15 |
+
- **Large File Support:** Upload files up to 4GB using Telegram Premium accounts.
|
| 16 |
+
- **Auto Pinger:** Built-in feature to keep the website active by preventing idle timeouts.
|
| 17 |
+
- **URL Upload Support:** Upload files directly to TG Drive from any direct download link of a file.
|
| 18 |
+
- **Bot Mode:** Upload files directly to any folder in TG Drive by sending the file to the bot on Telegram ([Know More](#tg-drives-bot-mode))
|
| 19 |
+
|
| 20 |
+
## Tech Stack
|
| 21 |
+
|
| 22 |
+
- **Backend:** Python, FastAPI
|
| 23 |
+
- **Frontend:** HTML, CSS, JavaScript
|
| 24 |
+
- **Database:** Local storage as a class object, saved to a file using the pickle module.
|
| 25 |
+
- **Storage:** Telegram
|
| 26 |
+
|
| 27 |
+
### Environment Variables
|
| 28 |
+
|
| 29 |
+
#### Required Variables
|
| 30 |
+
|
| 31 |
+
| Variable Name | Type | Example | Description |
|
| 32 |
+
| ------------------------ | ------- | ------------------------- | -------------------------------------------------------------------- |
|
| 33 |
+
| `API_ID` | integer | 123456 | Your Telegram API ID |
|
| 34 |
+
| `API_HASH` | string | dagsjdhgjfsahgjfh | Your Telegram API Hash |
|
| 35 |
+
| `BOT_TOKENS` | string | 21413535:gkdshajfhjfakhjf | List of Telegram bot tokens for file operations, separated by commas |
|
| 36 |
+
| `STORAGE_CHANNEL` | integer | -100123456789 | Chat ID of the Telegram storage channel |
|
| 37 |
+
| `DATABASE_BACKUP_MSG_ID` | integer | 123 | Message ID of a file in the storage channel for database backups |
|
| 38 |
+
|
| 39 |
+
> Note: All bots mentioned in the `BOT_TOKENS` variable must be added as admins in your `STORAGE_CHANNEL`.
|
| 40 |
+
|
| 41 |
+
> Note: `DATABASE_BACKUP_MSG_ID` should be the message ID of a file (document) in the `STORAGE_CHANNEL`.
|
| 42 |
+
|
| 43 |
+
#### Optional Variables
|
| 44 |
+
|
| 45 |
+
| Variable Name | Type | Default | Description |
|
| 46 |
+
| ---------------------- | -------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
|
| 47 |
+
| `ADMIN_PASSWORD` | string | admin | Password for accessing the admin panel |
|
| 48 |
+
| `STRING_SESSIONS` | string | None | List of Premium Telegram Account Pyrogram String Sessions for file operations |
|
| 49 |
+
| `SLEEP_THRESHOLD` | integer (in seconds) | 60 | Delay in seconds before retrying after a Telegram API floodwait error |
|
| 50 |
+
| `DATABASE_BACKUP_TIME` | integer (in seconds) | 60 | Interval in seconds for database backups to the storage channel |
|
| 51 |
+
| `MAX_FILE_SIZE` | float (in GBs) | 1.98 (3.98 if `STRING_SESSIONS` are added) | Maximum file size (in GBs) allowed for uploading to Telegram |
|
| 52 |
+
| `WEBSITE_URL` | string | None | Website URL (with https/http) to auto-ping to keep the website active |
|
| 53 |
+
| `MAIN_BOT_TOKEN` | string | None | Your Main Bot Token to use [TG Drive's Bot Mode](#tg-drives-bot-mode) |
|
| 54 |
+
| `TELEGRAM_ADMIN_IDS` | string | None | List of Telegram User IDs of admins who can access the [bot mode](#tg-drives-bot-mode), separated by commas |
|
| 55 |
+
|
| 56 |
+
> Note: Premium Client (`STRING_SESSIONS`) will be used only to upload files when file size is greater than 2GB.
|
| 57 |
+
|
| 58 |
+
> Note: File streaming/downloads will be handled by bots (`BOT_TOKENS`).
|
| 59 |
+
|
| 60 |
+
> Note: Read more about TG Drive's Bot Mode [here](#tg-drives-bot-mode).
|
| 61 |
+
|
| 62 |
+
## Deploying Your Own TG Drive Application
|
| 63 |
+
|
| 64 |
+
### 1. Clone the Repository
|
| 65 |
+
|
| 66 |
+
First, clone the repository and navigate into the project directory:
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
git clone https://github.com/TechShreyash/TGDrive
|
| 70 |
+
cd TGDrive
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
### 2. Set Up Your Environment Variables
|
| 74 |
+
|
| 75 |
+
Create a `.env` file in the root directory and add the necessary [environment variables](#environment-variables).
|
| 76 |
+
|
| 77 |
+
> **Note:** Some hosting services allow you to set environment variables directly through their interface, which may eliminate the need for a `.env` file.
|
| 78 |
+
|
| 79 |
+
### 3. Running TG Drive
|
| 80 |
+
|
| 81 |
+
#### Deploying Locally
|
| 82 |
+
|
| 83 |
+
1. Install the required Python packages:
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
pip install -U -r requirements.txt
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
2. Start the TG Drive application using Uvicorn:
|
| 90 |
+
|
| 91 |
+
```bash
|
| 92 |
+
uvicorn main:app --host 0.0.0.0 --port 8000
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
#### Deploying Using Docker
|
| 96 |
+
|
| 97 |
+
1. Build the Docker image:
|
| 98 |
+
|
| 99 |
+
```bash
|
| 100 |
+
docker build -t tgdrive .
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
2. Run the Docker container:
|
| 104 |
+
|
| 105 |
+
```bash
|
| 106 |
+
docker run -d -p 8000:8000 tgdrive
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
Access the application at `http://127.0.0.1:8000` or `http://your_ip:8000`.
|
| 110 |
+
|
| 111 |
+
> **Note:** For more detailed information on deploying FastAPI applications, refer to online guides and resources.
|
| 112 |
+
|
| 113 |
+
## Deploy Tutorials
|
| 114 |
+
|
| 115 |
+
**Deploy To Render.com For Free :** https://youtu.be/S5OIi5Ur3c0
|
| 116 |
+
|
| 117 |
+
<div align="center">
|
| 118 |
+
|
| 119 |
+
[](https://render.com/deploy?repo=https://github.com/TechShreyash/TGDrive)
|
| 120 |
+
|
| 121 |
+
</div>
|
| 122 |
+
|
| 123 |
+
> **Note:** After updating the TG Drive code, clear your browser's cache to ensure the latest JavaScript files are loaded and run correctly.
|
| 124 |
+
|
| 125 |
+
## TG Drive's Bot Mode
|
| 126 |
+
|
| 127 |
+
TG Drive's Bot Mode is a new feature that allows you to upload files directly to your TG Drive website from a Telegram bot. Simply send or forward any file to the bot, and it will be uploaded to your TG Drive. You can also specify the folder where you want the files to be uploaded.
|
| 128 |
+
|
| 129 |
+
To use this feature, you need to set the configuration variables `MAIN_BOT_TOKEN` and `TELEGRAM_ADMIN_IDS`. More information about these variables can be found in the [optional variables section](#optional-variables).
|
| 130 |
+
|
| 131 |
+
Once these variables are set, users whose IDs are listed in `TELEGRAM_ADMIN_IDS` will have access to the bot.
|
| 132 |
+
|
| 133 |
+
### Bot Commands
|
| 134 |
+
|
| 135 |
+
- `/set_folder` - Set the folder for file uploads
|
| 136 |
+
- `/current_folder` - Check the current folder
|
| 137 |
+
|
| 138 |
+
### Quick Demo
|
| 139 |
+
|
| 140 |
+
Bot Mode - Youtube Video Tutorial : https://youtu.be/XSeY2XcHdGI
|
| 141 |
+
|
| 142 |
+
#### Uploading Files
|
| 143 |
+
|
| 144 |
+
1. Open your main bot in Telegram.
|
| 145 |
+
2. Send or forward a file to this bot, and it will be uploaded. By default, the file will be uploaded to the root folder (home page).
|
| 146 |
+
|
| 147 |
+
#### Changing Folder for Uploading
|
| 148 |
+
|
| 149 |
+
1. Send the `/set_folder` command and follow the instructions provided by the bot.
|
| 150 |
+
|
| 151 |
+
## Important Posts Regarding TG Drive
|
| 152 |
+
|
| 153 |
+
Stay informed by joining our updates channel on Telegram: [@TechZBots](https://telegram.me/TechZBots). We post updates, guides, and tips about TG Drive there.
|
| 154 |
+
|
| 155 |
+
- https://telegram.me/TechZBots/891
|
| 156 |
+
- https://telegram.me/TechZBots/876
|
| 157 |
+
- https://telegram.me/TechZBots/874
|
| 158 |
+
- https://telegram.me/TechZBots/870
|
| 159 |
+
|
| 160 |
+
## Contributing
|
| 161 |
+
|
| 162 |
+
Contributions are welcome! Fork the repository, make your changes, and create a pull request.
|
| 163 |
+
|
| 164 |
+
## License
|
| 165 |
+
|
| 166 |
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
| 167 |
+
|
| 168 |
+
## Support
|
| 169 |
+
|
| 170 |
+
For inquiries or support, join our [Telegram Support Group](https://telegram.me/TechZBots_Support) or email [techshreyash123@gmail.com](mailto:techshreyash123@gmail.com).
|
config.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dotenv import load_dotenv
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
# Load environment variables from the .env file, if present
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
# Telegram API credentials obtained from https://my.telegram.org/auth
|
| 8 |
+
API_ID = int(os.getenv("API_ID")) # Your Telegram API ID
|
| 9 |
+
API_HASH = os.getenv("API_HASH") # Your Telegram API Hash
|
| 10 |
+
|
| 11 |
+
# List of Telegram bot tokens used for file upload/download operations
|
| 12 |
+
BOT_TOKENS = os.getenv("BOT_TOKENS", "").strip(", ").split(",")
|
| 13 |
+
BOT_TOKENS = [token.strip() for token in BOT_TOKENS if token.strip() != ""]
|
| 14 |
+
|
| 15 |
+
# List of Premium Telegram Account Pyrogram String Sessions used for file upload/download operations
|
| 16 |
+
STRING_SESSIONS = os.getenv("STRING_SESSIONS", "").strip(", ").split(",")
|
| 17 |
+
STRING_SESSIONS = [
|
| 18 |
+
session.strip() for session in STRING_SESSIONS if session.strip() != ""
|
| 19 |
+
]
|
| 20 |
+
|
| 21 |
+
# Chat ID of the Telegram storage channel where files will be stored
|
| 22 |
+
STORAGE_CHANNEL = int(os.getenv("STORAGE_CHANNEL")) # Your storage channel's chat ID
|
| 23 |
+
|
| 24 |
+
# Message ID of a file in the storage channel used for storing database backups
|
| 25 |
+
DATABASE_BACKUP_MSG_ID = int(
|
| 26 |
+
os.getenv("DATABASE_BACKUP_MSG_ID")
|
| 27 |
+
) # Message ID for database backup
|
| 28 |
+
|
| 29 |
+
# Password used to access the website's admin panel
|
| 30 |
+
ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "admin") # Default to "admin" if not set
|
| 31 |
+
|
| 32 |
+
# Determine the maximum file size (in bytes) allowed for uploading to Telegram
|
| 33 |
+
# 1.98 GB if no premium sessions are provided, otherwise 3.98 GB
|
| 34 |
+
if len(STRING_SESSIONS) == 0:
|
| 35 |
+
MAX_FILE_SIZE = 1.98 * 1024 * 1024 * 1024 # 2 GB in bytes
|
| 36 |
+
else:
|
| 37 |
+
MAX_FILE_SIZE = 3.98 * 1024 * 1024 * 1024 # 4 GB in bytes
|
| 38 |
+
|
| 39 |
+
# Database backup interval in seconds. Backups will be sent to the storage channel at this interval
|
| 40 |
+
DATABASE_BACKUP_TIME = int(
|
| 41 |
+
os.getenv("DATABASE_BACKUP_TIME", 60)
|
| 42 |
+
) # Default to 60 seconds
|
| 43 |
+
|
| 44 |
+
# Time delay in seconds before retrying after a Telegram API floodwait error
|
| 45 |
+
SLEEP_THRESHOLD = int(os.getenv("SLEEP_THRESHOLD", 60)) # Default to 60 seconds
|
| 46 |
+
|
| 47 |
+
# Domain to auto-ping and keep the website active
|
| 48 |
+
WEBSITE_URL = os.getenv("WEBSITE_URL", None)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
# For Using TG Drive's Bot Mode
|
| 52 |
+
|
| 53 |
+
# Main Bot Token for TG Drive's Bot Mode
|
| 54 |
+
MAIN_BOT_TOKEN = os.getenv("MAIN_BOT_TOKEN", "")
|
| 55 |
+
if MAIN_BOT_TOKEN.strip() == "":
|
| 56 |
+
MAIN_BOT_TOKEN = None
|
| 57 |
+
|
| 58 |
+
# List of Telegram User IDs who have admin access to the bot mode
|
| 59 |
+
TELEGRAM_ADMIN_IDS = os.getenv("TELEGRAM_ADMIN_IDS", "").strip(", ").split(",")
|
| 60 |
+
TELEGRAM_ADMIN_IDS = [int(id) for id in TELEGRAM_ADMIN_IDS if id.strip() != ""]
|
docker_start.sh
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
docker stop tgdrive
|
| 2 |
+
docker build -t tgdrive .
|
| 3 |
+
docker run -d --name tgdrive -p 80:80 tgdrive
|
main.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from utils.downloader import (
|
| 2 |
+
download_file,
|
| 3 |
+
get_file_info_from_url,
|
| 4 |
+
)
|
| 5 |
+
import asyncio
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from contextlib import asynccontextmanager
|
| 8 |
+
import aiofiles
|
| 9 |
+
from fastapi import FastAPI, HTTPException, Request, File, UploadFile, Form, Response
|
| 10 |
+
from fastapi.responses import FileResponse, JSONResponse
|
| 11 |
+
from config import ADMIN_PASSWORD, MAX_FILE_SIZE, STORAGE_CHANNEL
|
| 12 |
+
from utils.clients import initialize_clients
|
| 13 |
+
from utils.directoryHandler import getRandomID
|
| 14 |
+
from utils.extra import auto_ping_website, convert_class_to_dict, reset_cache_dir
|
| 15 |
+
from utils.streamer import media_streamer
|
| 16 |
+
from utils.uploader import start_file_uploader
|
| 17 |
+
from utils.logger import Logger
|
| 18 |
+
import urllib.parse
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# Startup Event
|
| 22 |
+
@asynccontextmanager
|
| 23 |
+
async def lifespan(app: FastAPI):
|
| 24 |
+
# Reset the cache directory, delete cache files
|
| 25 |
+
reset_cache_dir()
|
| 26 |
+
|
| 27 |
+
# Initialize the clients
|
| 28 |
+
await initialize_clients()
|
| 29 |
+
|
| 30 |
+
# Start the website auto ping task
|
| 31 |
+
asyncio.create_task(auto_ping_website())
|
| 32 |
+
|
| 33 |
+
yield
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
app = FastAPI(docs_url=None, redoc_url=None, lifespan=lifespan)
|
| 37 |
+
logger = Logger(__name__)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@app.get("/")
|
| 41 |
+
async def home_page():
|
| 42 |
+
return FileResponse("website/home.html")
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@app.get("/stream")
|
| 46 |
+
async def home_page():
|
| 47 |
+
return FileResponse("website/VideoPlayer.html")
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
@app.get("/static/{file_path:path}")
|
| 51 |
+
async def static_files(file_path):
|
| 52 |
+
if "apiHandler.js" in file_path:
|
| 53 |
+
with open(Path("website/static/js/apiHandler.js")) as f:
|
| 54 |
+
content = f.read()
|
| 55 |
+
content = content.replace("MAX_FILE_SIZE__SDGJDG", str(MAX_FILE_SIZE))
|
| 56 |
+
return Response(content=content, media_type="application/javascript")
|
| 57 |
+
return FileResponse(f"website/static/{file_path}")
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
@app.get("/file")
|
| 61 |
+
async def dl_file(request: Request):
|
| 62 |
+
from utils.directoryHandler import DRIVE_DATA
|
| 63 |
+
|
| 64 |
+
path = request.query_params["path"]
|
| 65 |
+
file = DRIVE_DATA.get_file(path)
|
| 66 |
+
return await media_streamer(STORAGE_CHANNEL, file.file_id, file.name, request)
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
# Api Routes
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@app.post("/api/checkPassword")
|
| 73 |
+
async def check_password(request: Request):
|
| 74 |
+
data = await request.json()
|
| 75 |
+
if data["pass"] == ADMIN_PASSWORD:
|
| 76 |
+
return JSONResponse({"status": "ok"})
|
| 77 |
+
return JSONResponse({"status": "Invalid password"})
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
@app.post("/api/createNewFolder")
|
| 81 |
+
async def api_new_folder(request: Request):
|
| 82 |
+
from utils.directoryHandler import DRIVE_DATA
|
| 83 |
+
|
| 84 |
+
data = await request.json()
|
| 85 |
+
|
| 86 |
+
if data["password"] != ADMIN_PASSWORD:
|
| 87 |
+
return JSONResponse({"status": "Invalid password"})
|
| 88 |
+
|
| 89 |
+
logger.info(f"createNewFolder {data}")
|
| 90 |
+
folder_data = DRIVE_DATA.get_directory(data["path"]).contents
|
| 91 |
+
for id in folder_data:
|
| 92 |
+
f = folder_data[id]
|
| 93 |
+
if f.type == "folder":
|
| 94 |
+
if f.name == data["name"]:
|
| 95 |
+
return JSONResponse(
|
| 96 |
+
{
|
| 97 |
+
"status": "Folder with the name already exist in current directory"
|
| 98 |
+
}
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
DRIVE_DATA.new_folder(data["path"], data["name"])
|
| 102 |
+
return JSONResponse({"status": "ok"})
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
@app.post("/api/getDirectory")
|
| 106 |
+
async def api_get_directory(request: Request):
|
| 107 |
+
from utils.directoryHandler import DRIVE_DATA
|
| 108 |
+
|
| 109 |
+
data = await request.json()
|
| 110 |
+
|
| 111 |
+
if data["password"] == ADMIN_PASSWORD:
|
| 112 |
+
is_admin = True
|
| 113 |
+
else:
|
| 114 |
+
is_admin = False
|
| 115 |
+
|
| 116 |
+
auth = data.get("auth")
|
| 117 |
+
|
| 118 |
+
logger.info(f"getFolder {data}")
|
| 119 |
+
|
| 120 |
+
if data["path"] == "/trash":
|
| 121 |
+
data = {"contents": DRIVE_DATA.get_trashed_files_folders()}
|
| 122 |
+
folder_data = convert_class_to_dict(data, isObject=False, showtrash=True)
|
| 123 |
+
|
| 124 |
+
elif "/search_" in data["path"]:
|
| 125 |
+
query = urllib.parse.unquote(data["path"].split("_", 1)[1])
|
| 126 |
+
print(query)
|
| 127 |
+
data = {"contents": DRIVE_DATA.search_file_folder(query)}
|
| 128 |
+
print(data)
|
| 129 |
+
folder_data = convert_class_to_dict(data, isObject=False, showtrash=False)
|
| 130 |
+
print(folder_data)
|
| 131 |
+
|
| 132 |
+
elif "/share_" in data["path"]:
|
| 133 |
+
path = data["path"].split("_", 1)[1]
|
| 134 |
+
folder_data, auth_home_path = DRIVE_DATA.get_directory(path, is_admin, auth)
|
| 135 |
+
auth_home_path= auth_home_path.replace("//", "/") if auth_home_path else None
|
| 136 |
+
folder_data = convert_class_to_dict(folder_data, isObject=True, showtrash=False)
|
| 137 |
+
return JSONResponse(
|
| 138 |
+
{"status": "ok", "data": folder_data, "auth_home_path": auth_home_path}
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
else:
|
| 142 |
+
folder_data = DRIVE_DATA.get_directory(data["path"])
|
| 143 |
+
folder_data = convert_class_to_dict(folder_data, isObject=True, showtrash=False)
|
| 144 |
+
return JSONResponse({"status": "ok", "data": folder_data, "auth_home_path": None})
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
SAVE_PROGRESS = {}
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
@app.post("/api/upload")
|
| 151 |
+
async def upload_file(
|
| 152 |
+
file: UploadFile = File(...),
|
| 153 |
+
path: str = Form(...),
|
| 154 |
+
password: str = Form(...),
|
| 155 |
+
id: str = Form(...),
|
| 156 |
+
total_size: str = Form(...),
|
| 157 |
+
):
|
| 158 |
+
global SAVE_PROGRESS
|
| 159 |
+
|
| 160 |
+
if password != ADMIN_PASSWORD:
|
| 161 |
+
return JSONResponse({"status": "Invalid password"})
|
| 162 |
+
|
| 163 |
+
total_size = int(total_size)
|
| 164 |
+
SAVE_PROGRESS[id] = ("running", 0, total_size)
|
| 165 |
+
|
| 166 |
+
ext = file.filename.lower().split(".")[-1]
|
| 167 |
+
|
| 168 |
+
cache_dir = Path("./cache")
|
| 169 |
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
| 170 |
+
file_location = cache_dir / f"{id}.{ext}"
|
| 171 |
+
|
| 172 |
+
file_size = 0
|
| 173 |
+
|
| 174 |
+
async with aiofiles.open(file_location, "wb") as buffer:
|
| 175 |
+
while chunk := await file.read(1024 * 1024): # Read file in chunks of 1MB
|
| 176 |
+
SAVE_PROGRESS[id] = ("running", file_size, total_size)
|
| 177 |
+
file_size += len(chunk)
|
| 178 |
+
if file_size > MAX_FILE_SIZE:
|
| 179 |
+
await buffer.close()
|
| 180 |
+
file_location.unlink() # Delete the partially written file
|
| 181 |
+
raise HTTPException(
|
| 182 |
+
status_code=400,
|
| 183 |
+
detail=f"File size exceeds {MAX_FILE_SIZE} bytes limit",
|
| 184 |
+
)
|
| 185 |
+
await buffer.write(chunk)
|
| 186 |
+
|
| 187 |
+
SAVE_PROGRESS[id] = ("completed", file_size, file_size)
|
| 188 |
+
|
| 189 |
+
asyncio.create_task(
|
| 190 |
+
start_file_uploader(file_location, id, path, file.filename, file_size)
|
| 191 |
+
)
|
| 192 |
+
|
| 193 |
+
return JSONResponse({"id": id, "status": "ok"})
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
@app.post("/api/getSaveProgress")
|
| 197 |
+
async def get_save_progress(request: Request):
|
| 198 |
+
global SAVE_PROGRESS
|
| 199 |
+
|
| 200 |
+
data = await request.json()
|
| 201 |
+
|
| 202 |
+
if data["password"] != ADMIN_PASSWORD:
|
| 203 |
+
return JSONResponse({"status": "Invalid password"})
|
| 204 |
+
|
| 205 |
+
logger.info(f"getUploadProgress {data}")
|
| 206 |
+
try:
|
| 207 |
+
progress = SAVE_PROGRESS[data["id"]]
|
| 208 |
+
return JSONResponse({"status": "ok", "data": progress})
|
| 209 |
+
except:
|
| 210 |
+
return JSONResponse({"status": "not found"})
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
@app.post("/api/getUploadProgress")
|
| 214 |
+
async def get_upload_progress(request: Request):
|
| 215 |
+
from utils.uploader import PROGRESS_CACHE
|
| 216 |
+
|
| 217 |
+
data = await request.json()
|
| 218 |
+
|
| 219 |
+
if data["password"] != ADMIN_PASSWORD:
|
| 220 |
+
return JSONResponse({"status": "Invalid password"})
|
| 221 |
+
|
| 222 |
+
logger.info(f"getUploadProgress {data}")
|
| 223 |
+
|
| 224 |
+
try:
|
| 225 |
+
progress = PROGRESS_CACHE[data["id"]]
|
| 226 |
+
return JSONResponse({"status": "ok", "data": progress})
|
| 227 |
+
except:
|
| 228 |
+
return JSONResponse({"status": "not found"})
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
@app.post("/api/cancelUpload")
|
| 232 |
+
async def cancel_upload(request: Request):
|
| 233 |
+
from utils.uploader import STOP_TRANSMISSION
|
| 234 |
+
from utils.downloader import STOP_DOWNLOAD
|
| 235 |
+
|
| 236 |
+
data = await request.json()
|
| 237 |
+
|
| 238 |
+
if data["password"] != ADMIN_PASSWORD:
|
| 239 |
+
return JSONResponse({"status": "Invalid password"})
|
| 240 |
+
|
| 241 |
+
logger.info(f"cancelUpload {data}")
|
| 242 |
+
STOP_TRANSMISSION.append(data["id"])
|
| 243 |
+
STOP_DOWNLOAD.append(data["id"])
|
| 244 |
+
return JSONResponse({"status": "ok"})
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
@app.post("/api/renameFileFolder")
|
| 248 |
+
async def rename_file_folder(request: Request):
|
| 249 |
+
from utils.directoryHandler import DRIVE_DATA
|
| 250 |
+
|
| 251 |
+
data = await request.json()
|
| 252 |
+
|
| 253 |
+
if data["password"] != ADMIN_PASSWORD:
|
| 254 |
+
return JSONResponse({"status": "Invalid password"})
|
| 255 |
+
|
| 256 |
+
logger.info(f"renameFileFolder {data}")
|
| 257 |
+
DRIVE_DATA.rename_file_folder(data["path"], data["name"])
|
| 258 |
+
return JSONResponse({"status": "ok"})
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
@app.post("/api/trashFileFolder")
|
| 262 |
+
async def trash_file_folder(request: Request):
|
| 263 |
+
from utils.directoryHandler import DRIVE_DATA
|
| 264 |
+
|
| 265 |
+
data = await request.json()
|
| 266 |
+
|
| 267 |
+
if data["password"] != ADMIN_PASSWORD:
|
| 268 |
+
return JSONResponse({"status": "Invalid password"})
|
| 269 |
+
|
| 270 |
+
logger.info(f"trashFileFolder {data}")
|
| 271 |
+
DRIVE_DATA.trash_file_folder(data["path"], data["trash"])
|
| 272 |
+
return JSONResponse({"status": "ok"})
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
@app.post("/api/deleteFileFolder")
|
| 276 |
+
async def delete_file_folder(request: Request):
|
| 277 |
+
from utils.directoryHandler import DRIVE_DATA
|
| 278 |
+
|
| 279 |
+
data = await request.json()
|
| 280 |
+
|
| 281 |
+
if data["password"] != ADMIN_PASSWORD:
|
| 282 |
+
return JSONResponse({"status": "Invalid password"})
|
| 283 |
+
|
| 284 |
+
logger.info(f"deleteFileFolder {data}")
|
| 285 |
+
DRIVE_DATA.delete_file_folder(data["path"])
|
| 286 |
+
return JSONResponse({"status": "ok"})
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
@app.post("/api/getFileInfoFromUrl")
|
| 290 |
+
async def getFileInfoFromUrl(request: Request):
|
| 291 |
+
|
| 292 |
+
data = await request.json()
|
| 293 |
+
|
| 294 |
+
if data["password"] != ADMIN_PASSWORD:
|
| 295 |
+
return JSONResponse({"status": "Invalid password"})
|
| 296 |
+
|
| 297 |
+
logger.info(f"getFileInfoFromUrl {data}")
|
| 298 |
+
try:
|
| 299 |
+
file_info = await get_file_info_from_url(data["url"])
|
| 300 |
+
return JSONResponse({"status": "ok", "data": file_info})
|
| 301 |
+
except Exception as e:
|
| 302 |
+
return JSONResponse({"status": str(e)})
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
@app.post("/api/startFileDownloadFromUrl")
|
| 306 |
+
async def startFileDownloadFromUrl(request: Request):
|
| 307 |
+
data = await request.json()
|
| 308 |
+
|
| 309 |
+
if data["password"] != ADMIN_PASSWORD:
|
| 310 |
+
return JSONResponse({"status": "Invalid password"})
|
| 311 |
+
|
| 312 |
+
logger.info(f"startFileDownloadFromUrl {data}")
|
| 313 |
+
try:
|
| 314 |
+
id = getRandomID()
|
| 315 |
+
asyncio.create_task(
|
| 316 |
+
download_file(data["url"], id, data["path"], data["filename"], data["singleThreaded"])
|
| 317 |
+
)
|
| 318 |
+
return JSONResponse({"status": "ok", "id": id})
|
| 319 |
+
except Exception as e:
|
| 320 |
+
return JSONResponse({"status": str(e)})
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
@app.post("/api/getFileDownloadProgress")
|
| 324 |
+
async def getFileDownloadProgress(request: Request):
|
| 325 |
+
from utils.downloader import DOWNLOAD_PROGRESS
|
| 326 |
+
|
| 327 |
+
data = await request.json()
|
| 328 |
+
|
| 329 |
+
if data["password"] != ADMIN_PASSWORD:
|
| 330 |
+
return JSONResponse({"status": "Invalid password"})
|
| 331 |
+
|
| 332 |
+
logger.info(f"getFileDownloadProgress {data}")
|
| 333 |
+
|
| 334 |
+
try:
|
| 335 |
+
progress = DOWNLOAD_PROGRESS[data["id"]]
|
| 336 |
+
return JSONResponse({"status": "ok", "data": progress})
|
| 337 |
+
except:
|
| 338 |
+
return JSONResponse({"status": "not found"})
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
@app.post("/api/getFolderShareAuth")
|
| 342 |
+
async def getFolderShareAuth(request: Request):
|
| 343 |
+
from utils.directoryHandler import DRIVE_DATA
|
| 344 |
+
|
| 345 |
+
data = await request.json()
|
| 346 |
+
|
| 347 |
+
if data["password"] != ADMIN_PASSWORD:
|
| 348 |
+
return JSONResponse({"status": "Invalid password"})
|
| 349 |
+
|
| 350 |
+
logger.info(f"getFolderShareAuth {data}")
|
| 351 |
+
|
| 352 |
+
try:
|
| 353 |
+
auth = DRIVE_DATA.get_folder_auth(data["path"])
|
| 354 |
+
return JSONResponse({"status": "ok", "auth": auth})
|
| 355 |
+
except:
|
| 356 |
+
return JSONResponse({"status": "not found"})
|
render.yaml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
# A Docker web service
|
| 3 |
+
- type: web
|
| 4 |
+
name: tgdrive
|
| 5 |
+
repo: https://github.com/TechShreyash/TGDrive
|
| 6 |
+
runtime: python
|
| 7 |
+
branch: main
|
| 8 |
+
plan: free
|
| 9 |
+
autoDeploy: false
|
| 10 |
+
buildCommand: pip install -r requirements.txt
|
| 11 |
+
startCommand: uvicorn main:app --host 0.0.0.0 --port $PORT
|
| 12 |
+
envVars:
|
| 13 |
+
- key: ADMIN_PASSWORD
|
| 14 |
+
sync: false
|
| 15 |
+
- key: API_ID
|
| 16 |
+
sync: false
|
| 17 |
+
- key: API_HASH
|
| 18 |
+
sync: false
|
| 19 |
+
- key: BOT_TOKENS
|
| 20 |
+
sync: false
|
| 21 |
+
- key: STORAGE_CHANNEL
|
| 22 |
+
sync: false
|
| 23 |
+
- key: DATABASE_BACKUP_MSG_ID
|
| 24 |
+
sync: false
|
requirements.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
https://github.com/KurimuzonAkuma/pyrogram/archive/dev.zip
|
| 2 |
+
tgcrypto
|
| 3 |
+
uvicorn
|
| 4 |
+
fastapi[all]
|
| 5 |
+
python-dotenv
|
| 6 |
+
aiofiles
|
| 7 |
+
aiohttp
|
| 8 |
+
curl_cffi
|
| 9 |
+
techzdl>=1.2.6
|
| 10 |
+
tqdm
|
| 11 |
+
dill
|
runtime.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
python-3.11
|
sample.env
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Required Vars
|
| 2 |
+
|
| 3 |
+
API_ID=123456
|
| 4 |
+
API_HASH=dagsjdhgjfsahgjfh
|
| 5 |
+
BOT_TOKENS=21413535:gkdshajfhjfakhjf
|
| 6 |
+
STORAGE_CHANNEL=-100123456789
|
| 7 |
+
DATABASE_BACKUP_MSG_ID=123
|
start_main.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
os.system("uvicorn main:app --reload")
|
utils/bot_mode.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
from pyrogram import Client, filters
|
| 3 |
+
from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton
|
| 4 |
+
import config
|
| 5 |
+
from utils.logger import Logger
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
logger = Logger(__name__)
|
| 9 |
+
|
| 10 |
+
START_CMD = """🚀 **Welcome To TG Drive's Bot Mode**
|
| 11 |
+
|
| 12 |
+
You can use this bot to upload files to your TG Drive website directly instead of doing it from website.
|
| 13 |
+
|
| 14 |
+
🗄 **Commands:**
|
| 15 |
+
/set_folder - Set folder for file uploads
|
| 16 |
+
/current_folder - Check current folder
|
| 17 |
+
|
| 18 |
+
📤 **How To Upload Files:** Send a file to this bot and it will be uploaded to your TG Drive website. You can also set a folder for file uploads using /set_folder command.
|
| 19 |
+
|
| 20 |
+
Read more about [TG Drive's Bot Mode](https://github.com/TechShreyash/TGDrive#tg-drives-bot-mode)
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
SET_FOLDER_PATH_CACHE = {} # Cache to store folder path for each folder id
|
| 24 |
+
DRIVE_DATA = None
|
| 25 |
+
BOT_MODE = None
|
| 26 |
+
|
| 27 |
+
session_cache_path = Path(f"./cache")
|
| 28 |
+
session_cache_path.parent.mkdir(parents=True, exist_ok=True)
|
| 29 |
+
|
| 30 |
+
main_bot = Client(
|
| 31 |
+
name="main_bot",
|
| 32 |
+
api_id=config.API_ID,
|
| 33 |
+
api_hash=config.API_HASH,
|
| 34 |
+
bot_token=config.MAIN_BOT_TOKEN,
|
| 35 |
+
sleep_threshold=config.SLEEP_THRESHOLD,
|
| 36 |
+
workdir=session_cache_path,
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@main_bot.on_message(
|
| 41 |
+
filters.command(["start", "help"])
|
| 42 |
+
& filters.private
|
| 43 |
+
& filters.user(config.TELEGRAM_ADMIN_IDS),
|
| 44 |
+
)
|
| 45 |
+
async def start_handler(client: Client, message: Message):
|
| 46 |
+
await message.reply_text(START_CMD)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
@main_bot.on_message(
|
| 50 |
+
filters.command("set_folder")
|
| 51 |
+
& filters.private
|
| 52 |
+
& filters.user(config.TELEGRAM_ADMIN_IDS),
|
| 53 |
+
)
|
| 54 |
+
async def set_folder_handler(client: Client, message: Message):
|
| 55 |
+
global SET_FOLDER_PATH_CACHE, DRIVE_DATA
|
| 56 |
+
|
| 57 |
+
while True:
|
| 58 |
+
try:
|
| 59 |
+
folder_name = await message.ask(
|
| 60 |
+
"Send the folder name where you want to upload files\n\n/cancel to cancel",
|
| 61 |
+
timeout=60,
|
| 62 |
+
filters=filters.text,
|
| 63 |
+
)
|
| 64 |
+
except asyncio.TimeoutError:
|
| 65 |
+
await message.reply_text("Timeout\n\nUse /set_folder to set folder again")
|
| 66 |
+
return
|
| 67 |
+
|
| 68 |
+
if folder_name.text.lower() == "/cancel":
|
| 69 |
+
await message.reply_text("Cancelled")
|
| 70 |
+
return
|
| 71 |
+
|
| 72 |
+
folder_name = folder_name.text.strip()
|
| 73 |
+
search_result = DRIVE_DATA.search_file_folder(folder_name)
|
| 74 |
+
|
| 75 |
+
# Get folders from search result
|
| 76 |
+
folders = {}
|
| 77 |
+
for item in search_result.values():
|
| 78 |
+
if item.type == "folder":
|
| 79 |
+
folders[item.id] = item
|
| 80 |
+
|
| 81 |
+
if len(folders) == 0:
|
| 82 |
+
await message.reply_text(f"No Folder found with name {folder_name}")
|
| 83 |
+
else:
|
| 84 |
+
break
|
| 85 |
+
|
| 86 |
+
buttons = []
|
| 87 |
+
folder_cache = {}
|
| 88 |
+
folder_cache_id = len(SET_FOLDER_PATH_CACHE) + 1
|
| 89 |
+
|
| 90 |
+
for folder in search_result.values():
|
| 91 |
+
path = folder.path.strip("/")
|
| 92 |
+
folder_path = "/" + ("/" + path + "/" + folder.id).strip("/")
|
| 93 |
+
folder_cache[folder.id] = (folder_path, folder.name)
|
| 94 |
+
buttons.append(
|
| 95 |
+
[
|
| 96 |
+
InlineKeyboardButton(
|
| 97 |
+
folder.name,
|
| 98 |
+
callback_data=f"set_folder_{folder_cache_id}_{folder.id}",
|
| 99 |
+
)
|
| 100 |
+
]
|
| 101 |
+
)
|
| 102 |
+
SET_FOLDER_PATH_CACHE[folder_cache_id] = folder_cache
|
| 103 |
+
|
| 104 |
+
await message.reply_text(
|
| 105 |
+
"Select the folder where you want to upload files",
|
| 106 |
+
reply_markup=InlineKeyboardMarkup(buttons),
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
@main_bot.on_callback_query(
|
| 111 |
+
filters.user(config.TELEGRAM_ADMIN_IDS) & filters.regex(r"set_folder_")
|
| 112 |
+
)
|
| 113 |
+
async def set_folder_callback(client: Client, callback_query: Message):
|
| 114 |
+
global SET_FOLDER_PATH_CACHE, BOT_MODE
|
| 115 |
+
|
| 116 |
+
folder_cache_id, folder_id = callback_query.data.split("_")[2:]
|
| 117 |
+
|
| 118 |
+
folder_path_cache = SET_FOLDER_PATH_CACHE.get(int(folder_cache_id))
|
| 119 |
+
if folder_path_cache is None:
|
| 120 |
+
await callback_query.answer("Request Expired, Send /set_folder again")
|
| 121 |
+
await callback_query.message.delete()
|
| 122 |
+
return
|
| 123 |
+
|
| 124 |
+
folder_path, name = folder_path_cache.get(folder_id)
|
| 125 |
+
del SET_FOLDER_PATH_CACHE[int(folder_cache_id)]
|
| 126 |
+
BOT_MODE.set_folder(folder_path, name)
|
| 127 |
+
|
| 128 |
+
await callback_query.answer(f"Folder Set Successfully To : {name}")
|
| 129 |
+
await callback_query.message.edit(
|
| 130 |
+
f"Folder Set Successfully To : {name}\n\nNow you can send / forward files to me and it will be uploaded to this folder."
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
@main_bot.on_message(
|
| 135 |
+
filters.command("current_folder")
|
| 136 |
+
& filters.private
|
| 137 |
+
& filters.user(config.TELEGRAM_ADMIN_IDS),
|
| 138 |
+
)
|
| 139 |
+
async def current_folder_handler(client: Client, message: Message):
|
| 140 |
+
global BOT_MODE
|
| 141 |
+
|
| 142 |
+
await message.reply_text(f"Current Folder: {BOT_MODE.current_folder_name}")
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
# Handling when any file is sent to the bot
|
| 146 |
+
@main_bot.on_message(
|
| 147 |
+
filters.private
|
| 148 |
+
& filters.user(config.TELEGRAM_ADMIN_IDS)
|
| 149 |
+
& (
|
| 150 |
+
filters.document
|
| 151 |
+
| filters.video
|
| 152 |
+
| filters.audio
|
| 153 |
+
| filters.photo
|
| 154 |
+
| filters.sticker
|
| 155 |
+
)
|
| 156 |
+
)
|
| 157 |
+
async def file_handler(client: Client, message: Message):
|
| 158 |
+
global BOT_MODE, DRIVE_DATA
|
| 159 |
+
|
| 160 |
+
copied_message = await message.copy(config.STORAGE_CHANNEL)
|
| 161 |
+
file = (
|
| 162 |
+
copied_message.document
|
| 163 |
+
or copied_message.video
|
| 164 |
+
or copied_message.audio
|
| 165 |
+
or copied_message.photo
|
| 166 |
+
or copied_message.sticker
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
DRIVE_DATA.new_file(
|
| 170 |
+
BOT_MODE.current_folder,
|
| 171 |
+
file.file_name,
|
| 172 |
+
copied_message.id,
|
| 173 |
+
file.file_size,
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
await message.reply_text(
|
| 177 |
+
f"""✅ File Uploaded Successfully To Your TG Drive Website
|
| 178 |
+
|
| 179 |
+
**File Name:** {file.file_name}
|
| 180 |
+
**Folder:** {BOT_MODE.current_folder_name}
|
| 181 |
+
"""
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
async def start_bot_mode(d, b):
|
| 186 |
+
global DRIVE_DATA, BOT_MODE
|
| 187 |
+
DRIVE_DATA = d
|
| 188 |
+
BOT_MODE = b
|
| 189 |
+
|
| 190 |
+
logger.info("Starting Main Bot")
|
| 191 |
+
await main_bot.start()
|
| 192 |
+
|
| 193 |
+
await main_bot.send_message(
|
| 194 |
+
config.STORAGE_CHANNEL, "Main Bot Started -> TG Drive's Bot Mode Enabled"
|
| 195 |
+
)
|
| 196 |
+
logger.info("Main Bot Started")
|
| 197 |
+
logger.info("TG Drive's Bot Mode Enabled")
|
utils/clients.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio, config
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from pyrogram import Client
|
| 4 |
+
from utils.directoryHandler import backup_drive_data, loadDriveData
|
| 5 |
+
from utils.logger import Logger
|
| 6 |
+
import os
|
| 7 |
+
import signal
|
| 8 |
+
|
| 9 |
+
logger = Logger(__name__)
|
| 10 |
+
|
| 11 |
+
multi_clients = {}
|
| 12 |
+
premium_clients = {}
|
| 13 |
+
work_loads = {}
|
| 14 |
+
premium_work_loads = {}
|
| 15 |
+
main_bot = None
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
async def initialize_clients():
|
| 19 |
+
global multi_clients, work_loads, premium_clients, premium_work_loads
|
| 20 |
+
logger.info("Initializing Clients")
|
| 21 |
+
|
| 22 |
+
session_cache_path = Path(f"./cache")
|
| 23 |
+
session_cache_path.parent.mkdir(parents=True, exist_ok=True)
|
| 24 |
+
|
| 25 |
+
all_tokens = dict((i, t) for i, t in enumerate(config.BOT_TOKENS, start=1))
|
| 26 |
+
all_sessions = dict(
|
| 27 |
+
(i, s) for i, s in enumerate(config.STRING_SESSIONS, start=len(all_tokens) + 1)
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
async def start_client(client_id, token, type):
|
| 31 |
+
try:
|
| 32 |
+
logger.info(f"Starting - {type.title()} Client {client_id}")
|
| 33 |
+
|
| 34 |
+
if type == "bot":
|
| 35 |
+
client = Client(
|
| 36 |
+
name=str(client_id),
|
| 37 |
+
api_id=config.API_ID,
|
| 38 |
+
api_hash=config.API_HASH,
|
| 39 |
+
bot_token=token,
|
| 40 |
+
workdir=session_cache_path,
|
| 41 |
+
)
|
| 42 |
+
client.loop = asyncio.get_running_loop()
|
| 43 |
+
await client.start()
|
| 44 |
+
await client.send_message(
|
| 45 |
+
config.STORAGE_CHANNEL,
|
| 46 |
+
f"Started - {type.title()} Client {client_id}",
|
| 47 |
+
)
|
| 48 |
+
multi_clients[client_id] = client
|
| 49 |
+
work_loads[client_id] = 0
|
| 50 |
+
elif type == "user":
|
| 51 |
+
client = await Client(
|
| 52 |
+
name=str(client_id),
|
| 53 |
+
api_id=config.API_ID,
|
| 54 |
+
api_hash=config.API_HASH,
|
| 55 |
+
session_string=token,
|
| 56 |
+
sleep_threshold=config.SLEEP_THRESHOLD,
|
| 57 |
+
workdir=session_cache_path,
|
| 58 |
+
no_updates=True,
|
| 59 |
+
).start()
|
| 60 |
+
await client.send_message(
|
| 61 |
+
config.STORAGE_CHANNEL,
|
| 62 |
+
f"Started - {type.title()} Client {client_id}",
|
| 63 |
+
)
|
| 64 |
+
premium_clients[client_id] = client
|
| 65 |
+
premium_work_loads[client_id] = 0
|
| 66 |
+
|
| 67 |
+
logger.info(f"Started - {type.title()} Client {client_id}")
|
| 68 |
+
except Exception as e:
|
| 69 |
+
logger.error(
|
| 70 |
+
f"Failed To Start {type.title()} Client - {client_id} Error: {e}"
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
await asyncio.gather(
|
| 74 |
+
*(
|
| 75 |
+
[
|
| 76 |
+
start_client(client_id, client, "bot")
|
| 77 |
+
for client_id, client in all_tokens.items()
|
| 78 |
+
]
|
| 79 |
+
+ [
|
| 80 |
+
start_client(client_id, client, "user")
|
| 81 |
+
for client_id, client in all_sessions.items()
|
| 82 |
+
]
|
| 83 |
+
)
|
| 84 |
+
)
|
| 85 |
+
if len(multi_clients) == 0:
|
| 86 |
+
logger.error("No Clients Were Initialized")
|
| 87 |
+
|
| 88 |
+
# Forcefully terminates the program immediately
|
| 89 |
+
os.kill(os.getpid(), signal.SIGKILL)
|
| 90 |
+
|
| 91 |
+
if len(premium_clients) == 0:
|
| 92 |
+
logger.info("No Premium Clients Were Initialized")
|
| 93 |
+
|
| 94 |
+
logger.info("Clients Initialized")
|
| 95 |
+
|
| 96 |
+
# Load the drive data
|
| 97 |
+
await loadDriveData()
|
| 98 |
+
|
| 99 |
+
# Start the backup drive data task
|
| 100 |
+
asyncio.create_task(backup_drive_data())
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def get_client(premium_required=False) -> Client:
|
| 104 |
+
global multi_clients, work_loads, premium_clients, premium_work_loads
|
| 105 |
+
|
| 106 |
+
if premium_required:
|
| 107 |
+
index = min(premium_work_loads, key=premium_work_loads.get)
|
| 108 |
+
premium_work_loads[index] += 1
|
| 109 |
+
return premium_clients[index]
|
| 110 |
+
|
| 111 |
+
index = min(work_loads, key=work_loads.get)
|
| 112 |
+
work_loads[index] += 1
|
| 113 |
+
return multi_clients[index]
|
utils/directoryHandler.py
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
import sys
|
| 3 |
+
import config, dill
|
| 4 |
+
from pyrogram.types import InputMediaDocument, Message
|
| 5 |
+
import os, random, string, asyncio
|
| 6 |
+
from utils.logger import Logger
|
| 7 |
+
from datetime import datetime, timezone
|
| 8 |
+
import os
|
| 9 |
+
import signal
|
| 10 |
+
|
| 11 |
+
logger = Logger(__name__)
|
| 12 |
+
|
| 13 |
+
cache_dir = Path("./cache")
|
| 14 |
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
| 15 |
+
drive_cache_path = cache_dir / "drive.data"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def getRandomID():
|
| 19 |
+
global DRIVE_DATA
|
| 20 |
+
while True:
|
| 21 |
+
id = "".join(random.choices(string.ascii_uppercase + string.digits, k=6))
|
| 22 |
+
if not DRIVE_DATA:
|
| 23 |
+
return id
|
| 24 |
+
if id not in DRIVE_DATA.used_ids:
|
| 25 |
+
DRIVE_DATA.used_ids.append(id)
|
| 26 |
+
return id
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def get_current_utc_time():
|
| 30 |
+
return datetime.now(timezone.utc).strftime("Date - %Y-%m-%d | Time - %H:%M:%S")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class Folder:
|
| 34 |
+
def __init__(self, name: str, path: str) -> None:
|
| 35 |
+
self.name = name
|
| 36 |
+
self.contents = {}
|
| 37 |
+
if name == "/":
|
| 38 |
+
self.id = "root"
|
| 39 |
+
else:
|
| 40 |
+
self.id = getRandomID()
|
| 41 |
+
self.type = "folder"
|
| 42 |
+
self.trash = False
|
| 43 |
+
self.path = ("/" + path.strip("/") + "/").replace("//", "/")
|
| 44 |
+
self.upload_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 45 |
+
self.auth_hashes = []
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class File:
|
| 49 |
+
def __init__(
|
| 50 |
+
self,
|
| 51 |
+
name: str,
|
| 52 |
+
file_id: int,
|
| 53 |
+
size: int,
|
| 54 |
+
path: str,
|
| 55 |
+
) -> None:
|
| 56 |
+
self.name = name
|
| 57 |
+
self.file_id = file_id
|
| 58 |
+
self.id = getRandomID()
|
| 59 |
+
self.size = size
|
| 60 |
+
self.type = "file"
|
| 61 |
+
self.trash = False
|
| 62 |
+
self.path = path[:-1] if path[-1] == "/" else path
|
| 63 |
+
self.upload_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class NewDriveData:
|
| 67 |
+
def __init__(self, contents: dict, used_ids: list) -> None:
|
| 68 |
+
self.contents = contents
|
| 69 |
+
self.used_ids = used_ids
|
| 70 |
+
self.isUpdated = False
|
| 71 |
+
|
| 72 |
+
def save(self) -> None:
|
| 73 |
+
with open(drive_cache_path, "wb") as f:
|
| 74 |
+
dill.dump(self, f)
|
| 75 |
+
self.isUpdated = True
|
| 76 |
+
logger.info("Drive data saved successfully.")
|
| 77 |
+
|
| 78 |
+
def new_folder(self, path: str, name: str) -> None:
|
| 79 |
+
logger.info(f"Creating new folder '{name}' in path '{path}'.")
|
| 80 |
+
|
| 81 |
+
folder = Folder(name, path)
|
| 82 |
+
if path == "/":
|
| 83 |
+
directory_folder: Folder = self.contents[path]
|
| 84 |
+
directory_folder.contents[folder.id] = folder
|
| 85 |
+
else:
|
| 86 |
+
paths = path.strip("/").split("/")
|
| 87 |
+
directory_folder: Folder = self.contents["/"]
|
| 88 |
+
for path in paths:
|
| 89 |
+
directory_folder = directory_folder.contents[path]
|
| 90 |
+
directory_folder.contents[folder.id] = folder
|
| 91 |
+
|
| 92 |
+
self.save()
|
| 93 |
+
return folder.path + folder.id
|
| 94 |
+
|
| 95 |
+
def new_file(self, path: str, name: str, file_id: int, size: int) -> None:
|
| 96 |
+
logger.info(f"Creating new file '{name}' in path '{path}'.")
|
| 97 |
+
|
| 98 |
+
file = File(name, file_id, size, path)
|
| 99 |
+
if path == "/":
|
| 100 |
+
directory_folder: Folder = self.contents[path]
|
| 101 |
+
directory_folder.contents[file.id] = file
|
| 102 |
+
else:
|
| 103 |
+
paths = path.strip("/").split("/")
|
| 104 |
+
directory_folder: Folder = self.contents["/"]
|
| 105 |
+
for path in paths:
|
| 106 |
+
directory_folder = directory_folder.contents[path]
|
| 107 |
+
directory_folder.contents[file.id] = file
|
| 108 |
+
|
| 109 |
+
self.save()
|
| 110 |
+
|
| 111 |
+
def get_directory(
|
| 112 |
+
self, path: str, is_admin: bool = True, auth: str = None
|
| 113 |
+
) -> Folder:
|
| 114 |
+
folder_data: Folder = self.contents["/"]
|
| 115 |
+
auth_success = False
|
| 116 |
+
auth_home_path = None
|
| 117 |
+
|
| 118 |
+
if path != "/":
|
| 119 |
+
path = path.strip("/")
|
| 120 |
+
|
| 121 |
+
if "/" in path:
|
| 122 |
+
path = path.split("/")
|
| 123 |
+
else:
|
| 124 |
+
path = [path]
|
| 125 |
+
|
| 126 |
+
for folder in path:
|
| 127 |
+
folder_data = folder_data.contents[folder]
|
| 128 |
+
|
| 129 |
+
if auth in folder_data.auth_hashes:
|
| 130 |
+
auth_success = True
|
| 131 |
+
auth_home_path = (
|
| 132 |
+
"/" + folder_data.path.strip("/") + "/" + folder_data.id
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
if not is_admin and not auth_success:
|
| 136 |
+
logger.warning(f"Unauthorized access attempt to path '{path}'.")
|
| 137 |
+
return None
|
| 138 |
+
|
| 139 |
+
if auth_success:
|
| 140 |
+
logger.info(f"Authorization successful for path '{path}'.")
|
| 141 |
+
return folder_data, auth_home_path
|
| 142 |
+
|
| 143 |
+
return folder_data
|
| 144 |
+
|
| 145 |
+
def get_folder_auth(self, path: str) -> None:
|
| 146 |
+
auth = getRandomID()
|
| 147 |
+
folder_data: Folder = self.contents["/"]
|
| 148 |
+
|
| 149 |
+
if path != "/":
|
| 150 |
+
path = path.strip("/")
|
| 151 |
+
|
| 152 |
+
if "/" in path:
|
| 153 |
+
path = path.split("/")
|
| 154 |
+
else:
|
| 155 |
+
path = [path]
|
| 156 |
+
|
| 157 |
+
for folder in path:
|
| 158 |
+
folder_data = folder_data.contents[folder]
|
| 159 |
+
|
| 160 |
+
folder_data.auth_hashes.append(auth)
|
| 161 |
+
self.save()
|
| 162 |
+
logger.info(f"Authorization hash generated for path '{path}'.")
|
| 163 |
+
return auth
|
| 164 |
+
|
| 165 |
+
def get_file(self, path) -> File:
|
| 166 |
+
if len(path.strip("/").split("/")) > 0:
|
| 167 |
+
folder_path = "/" + "/".join(path.strip("/").split("/")[:-1])
|
| 168 |
+
file_id = path.strip("/").split("/")[-1]
|
| 169 |
+
else:
|
| 170 |
+
folder_path = "/"
|
| 171 |
+
file_id = path.strip("/")
|
| 172 |
+
|
| 173 |
+
folder_data = self.get_directory(folder_path)
|
| 174 |
+
return folder_data.contents[file_id]
|
| 175 |
+
|
| 176 |
+
def rename_file_folder(self, path: str, new_name: str) -> None:
|
| 177 |
+
if len(path.strip("/").split("/")) > 0:
|
| 178 |
+
folder_path = "/" + "/".join(path.strip("/").split("/")[:-1])
|
| 179 |
+
file_id = path.strip("/").split("/")[-1]
|
| 180 |
+
else:
|
| 181 |
+
folder_path = "/"
|
| 182 |
+
file_id = path.strip("/")
|
| 183 |
+
folder_data = self.get_directory(folder_path)
|
| 184 |
+
folder_data.contents[file_id].name = new_name
|
| 185 |
+
self.save()
|
| 186 |
+
logger.info(f"Item at path '{path}' renamed to '{new_name}'.")
|
| 187 |
+
|
| 188 |
+
def trash_file_folder(self, path: str, trash: bool) -> None:
|
| 189 |
+
action = "Trashing" if trash else "Restoring"
|
| 190 |
+
|
| 191 |
+
if len(path.strip("/").split("/")) > 0:
|
| 192 |
+
folder_path = "/" + "/".join(path.strip("/").split("/")[:-1])
|
| 193 |
+
file_id = path.strip("/").split("/")[-1]
|
| 194 |
+
else:
|
| 195 |
+
folder_path = "/"
|
| 196 |
+
file_id = path.strip("/")
|
| 197 |
+
folder_data = self.get_directory(folder_path)
|
| 198 |
+
folder_data.contents[file_id].trash = trash
|
| 199 |
+
self.save()
|
| 200 |
+
logger.info(f"Item at path '{path}' {action.lower()} successfully.")
|
| 201 |
+
|
| 202 |
+
def get_trashed_files_folders(self):
|
| 203 |
+
root_dir = self.get_directory("/")
|
| 204 |
+
trash_data = {}
|
| 205 |
+
|
| 206 |
+
def traverse_directory(folder):
|
| 207 |
+
for item in folder.contents.values():
|
| 208 |
+
if item.type == "folder":
|
| 209 |
+
if item.trash:
|
| 210 |
+
trash_data[item.id] = item
|
| 211 |
+
else:
|
| 212 |
+
# Recursively traverse the subfolder
|
| 213 |
+
traverse_directory(item)
|
| 214 |
+
elif item.type == "file":
|
| 215 |
+
if item.trash:
|
| 216 |
+
trash_data[item.id] = item
|
| 217 |
+
|
| 218 |
+
traverse_directory(root_dir)
|
| 219 |
+
return trash_data
|
| 220 |
+
|
| 221 |
+
def delete_file_folder(self, path: str) -> None:
|
| 222 |
+
|
| 223 |
+
if len(path.strip("/").split("/")) > 0:
|
| 224 |
+
folder_path = "/" + "/".join(path.strip("/").split("/")[:-1])
|
| 225 |
+
file_id = path.strip("/").split("/")[-1]
|
| 226 |
+
else:
|
| 227 |
+
folder_path = "/"
|
| 228 |
+
file_id = path.strip("/")
|
| 229 |
+
|
| 230 |
+
folder_data = self.get_directory(folder_path)
|
| 231 |
+
del folder_data.contents[file_id]
|
| 232 |
+
self.save()
|
| 233 |
+
logger.info(f"Item at path '{path}' deleted successfully.")
|
| 234 |
+
|
| 235 |
+
def search_file_folder(self, query: str):
|
| 236 |
+
logger.info(f"Searching for items matching query '{query}'.")
|
| 237 |
+
|
| 238 |
+
root_dir = self.get_directory("/")
|
| 239 |
+
search_results = {}
|
| 240 |
+
|
| 241 |
+
def traverse_directory(folder):
|
| 242 |
+
for item in folder.contents.values():
|
| 243 |
+
if query.lower() in item.name.lower():
|
| 244 |
+
search_results[item.id] = item
|
| 245 |
+
if item.type == "folder":
|
| 246 |
+
traverse_directory(item)
|
| 247 |
+
|
| 248 |
+
traverse_directory(root_dir)
|
| 249 |
+
logger.info(f"Search completed. Found {len(search_results)} matching items.")
|
| 250 |
+
return search_results
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
class NewBotMode:
|
| 254 |
+
def __init__(self, drive_data: NewDriveData) -> None:
|
| 255 |
+
self.drive_data = drive_data
|
| 256 |
+
|
| 257 |
+
# Set the current folder to root directory by default
|
| 258 |
+
self.current_folder = "/"
|
| 259 |
+
self.current_folder_name = "/ (root directory)"
|
| 260 |
+
|
| 261 |
+
def set_folder(self, folder_path: str, name: str) -> None:
|
| 262 |
+
self.current_folder = folder_path
|
| 263 |
+
self.current_folder_name = name
|
| 264 |
+
self.drive_data.save()
|
| 265 |
+
logger.info(f"Current folder set to '{name}' at path '{folder_path}'.")
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
DRIVE_DATA: NewDriveData = None
|
| 269 |
+
BOT_MODE: NewBotMode = None
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
# Function to backup the drive data to telegram
|
| 273 |
+
async def backup_drive_data(loop=True):
|
| 274 |
+
global DRIVE_DATA
|
| 275 |
+
logger.info("Starting backup drive data task.")
|
| 276 |
+
|
| 277 |
+
while True:
|
| 278 |
+
try:
|
| 279 |
+
if not DRIVE_DATA.isUpdated:
|
| 280 |
+
if not loop:
|
| 281 |
+
break
|
| 282 |
+
await asyncio.sleep(config.DATABASE_BACKUP_TIME)
|
| 283 |
+
continue
|
| 284 |
+
|
| 285 |
+
logger.info("Backing up drive data to Telegram.")
|
| 286 |
+
from utils.clients import get_client
|
| 287 |
+
|
| 288 |
+
client = get_client()
|
| 289 |
+
time_text = f"📅 **Last Updated :** {get_current_utc_time()} (UTC +00:00)"
|
| 290 |
+
caption = (
|
| 291 |
+
f"🔐 **TG Drive Data Backup File**\n\n"
|
| 292 |
+
"Do not edit or delete this message. This is a backup file for the tg drive data.\n\n"
|
| 293 |
+
f"{time_text}"
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
media_doc = InputMediaDocument(drive_cache_path, caption=caption)
|
| 297 |
+
msg = await client.edit_message_media(
|
| 298 |
+
config.STORAGE_CHANNEL,
|
| 299 |
+
config.DATABASE_BACKUP_MSG_ID,
|
| 300 |
+
media=media_doc,
|
| 301 |
+
file_name="drive.data",
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
DRIVE_DATA.isUpdated = False
|
| 305 |
+
logger.info("Drive data backed up to Telegram successfully.")
|
| 306 |
+
|
| 307 |
+
try:
|
| 308 |
+
await msg.pin()
|
| 309 |
+
except Exception as pin_e:
|
| 310 |
+
logger.error(f"Error pinning backup message: {pin_e}")
|
| 311 |
+
|
| 312 |
+
if not loop:
|
| 313 |
+
break
|
| 314 |
+
|
| 315 |
+
await asyncio.sleep(config.DATABASE_BACKUP_TIME)
|
| 316 |
+
except Exception as e:
|
| 317 |
+
logger.error(f"Backup Error: {e}")
|
| 318 |
+
await asyncio.sleep(10)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
async def init_drive_data():
|
| 322 |
+
global DRIVE_DATA
|
| 323 |
+
|
| 324 |
+
logger.info("Initializing drive data.")
|
| 325 |
+
root_dir = DRIVE_DATA.get_directory("/")
|
| 326 |
+
if not hasattr(root_dir, "auth_hashes"):
|
| 327 |
+
root_dir.auth_hashes = []
|
| 328 |
+
|
| 329 |
+
def traverse_directory(folder):
|
| 330 |
+
for item in folder.contents.values():
|
| 331 |
+
if item.type == "folder":
|
| 332 |
+
traverse_directory(item)
|
| 333 |
+
|
| 334 |
+
if not hasattr(item, "auth_hashes"):
|
| 335 |
+
item.auth_hashes = []
|
| 336 |
+
|
| 337 |
+
traverse_directory(root_dir)
|
| 338 |
+
DRIVE_DATA.save()
|
| 339 |
+
logger.info("Drive data initialization completed.")
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
async def loadDriveData():
|
| 343 |
+
global DRIVE_DATA, BOT_MODE
|
| 344 |
+
|
| 345 |
+
logger.info("Loading drive data.")
|
| 346 |
+
from utils.clients import get_client
|
| 347 |
+
|
| 348 |
+
client = get_client()
|
| 349 |
+
try:
|
| 350 |
+
try:
|
| 351 |
+
msg: Message = await client.get_messages(
|
| 352 |
+
config.STORAGE_CHANNEL, config.DATABASE_BACKUP_MSG_ID
|
| 353 |
+
)
|
| 354 |
+
except Exception as e:
|
| 355 |
+
logger.error(f"Error fetching backup message: {e}")
|
| 356 |
+
|
| 357 |
+
# Forcefully terminates the program immediately
|
| 358 |
+
os.kill(os.getpid(), signal.SIGKILL)
|
| 359 |
+
|
| 360 |
+
if not msg.document:
|
| 361 |
+
logger.error(f"Error fetching backup message: {e}")
|
| 362 |
+
|
| 363 |
+
# Forcefully terminates the program immediately
|
| 364 |
+
os.kill(os.getpid(), signal.SIGKILL)
|
| 365 |
+
|
| 366 |
+
if msg.document.file_name == "drive.data":
|
| 367 |
+
dl_path = await msg.download()
|
| 368 |
+
with open(dl_path, "rb") as f:
|
| 369 |
+
DRIVE_DATA = dill.load(f)
|
| 370 |
+
|
| 371 |
+
logger.info("Drive data loaded from Telegram backup.")
|
| 372 |
+
else:
|
| 373 |
+
raise Exception("Backup drive.data file not found on Telegram.")
|
| 374 |
+
except Exception as e:
|
| 375 |
+
logger.warning(f"Backup load failed: {e}")
|
| 376 |
+
logger.info("Creating new drive.data file.")
|
| 377 |
+
DRIVE_DATA = NewDriveData({"/": Folder("/", "/")}, [])
|
| 378 |
+
DRIVE_DATA.save()
|
| 379 |
+
|
| 380 |
+
await init_drive_data()
|
| 381 |
+
|
| 382 |
+
if config.MAIN_BOT_TOKEN:
|
| 383 |
+
from utils.bot_mode import start_bot_mode
|
| 384 |
+
|
| 385 |
+
BOT_MODE = NewBotMode(DRIVE_DATA)
|
| 386 |
+
await start_bot_mode(DRIVE_DATA, BOT_MODE)
|
| 387 |
+
logger.info("Bot mode started.")
|
utils/downloader.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import aiohttp, asyncio
|
| 3 |
+
from utils.extra import get_filename
|
| 4 |
+
from utils.logger import Logger
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from utils.uploader import start_file_uploader
|
| 7 |
+
from techzdl import TechZDL
|
| 8 |
+
|
| 9 |
+
logger = Logger(__name__)
|
| 10 |
+
|
| 11 |
+
DOWNLOAD_PROGRESS = {}
|
| 12 |
+
STOP_DOWNLOAD = []
|
| 13 |
+
|
| 14 |
+
cache_dir = Path("./cache")
|
| 15 |
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
async def download_progress_callback(status, current, total, id):
|
| 19 |
+
global DOWNLOAD_PROGRESS
|
| 20 |
+
|
| 21 |
+
DOWNLOAD_PROGRESS[id] = (
|
| 22 |
+
status,
|
| 23 |
+
current,
|
| 24 |
+
total,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
async def download_file(url, id, path, filename, singleThreaded):
|
| 29 |
+
global DOWNLOAD_PROGRESS, STOP_DOWNLOAD
|
| 30 |
+
|
| 31 |
+
logger.info(f"Downloading file from {url}")
|
| 32 |
+
|
| 33 |
+
try:
|
| 34 |
+
downloader = TechZDL(
|
| 35 |
+
url,
|
| 36 |
+
output_dir=cache_dir,
|
| 37 |
+
debug=False,
|
| 38 |
+
progress_callback=download_progress_callback,
|
| 39 |
+
progress_args=(id,),
|
| 40 |
+
max_retries=5,
|
| 41 |
+
single_threaded=singleThreaded,
|
| 42 |
+
)
|
| 43 |
+
await downloader.start(in_background=True)
|
| 44 |
+
|
| 45 |
+
await asyncio.sleep(5)
|
| 46 |
+
|
| 47 |
+
while downloader.is_running:
|
| 48 |
+
if id in STOP_DOWNLOAD:
|
| 49 |
+
logger.info(f"Stopping download {id}")
|
| 50 |
+
await downloader.stop()
|
| 51 |
+
return
|
| 52 |
+
await asyncio.sleep(1)
|
| 53 |
+
|
| 54 |
+
if downloader.download_success is False:
|
| 55 |
+
raise downloader.download_error
|
| 56 |
+
|
| 57 |
+
DOWNLOAD_PROGRESS[id] = (
|
| 58 |
+
"completed",
|
| 59 |
+
downloader.total_size,
|
| 60 |
+
downloader.total_size,
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
logger.info(f"File downloaded to {downloader.output_path}")
|
| 64 |
+
|
| 65 |
+
asyncio.create_task(
|
| 66 |
+
start_file_uploader(
|
| 67 |
+
downloader.output_path, id, path, filename, downloader.total_size
|
| 68 |
+
)
|
| 69 |
+
)
|
| 70 |
+
except Exception as e:
|
| 71 |
+
DOWNLOAD_PROGRESS[id] = ("error", 0, 0)
|
| 72 |
+
logger.error(f"Failed to download file: {url} {e}")
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
async def get_file_info_from_url(url):
|
| 76 |
+
downloader = TechZDL(
|
| 77 |
+
url,
|
| 78 |
+
output_dir=cache_dir,
|
| 79 |
+
debug=False,
|
| 80 |
+
progress_callback=download_progress_callback,
|
| 81 |
+
progress_args=(id,),
|
| 82 |
+
max_retries=5,
|
| 83 |
+
)
|
| 84 |
+
file_info = await downloader.get_file_info()
|
| 85 |
+
return {"file_size": file_info["total_size"], "file_name": file_info["filename"]}
|
utils/extra.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import mimetypes
|
| 2 |
+
from urllib.parse import unquote_plus
|
| 3 |
+
import re
|
| 4 |
+
import urllib.parse
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from config import WEBSITE_URL
|
| 7 |
+
import asyncio, aiohttp
|
| 8 |
+
from utils.directoryHandler import get_current_utc_time, getRandomID
|
| 9 |
+
from utils.logger import Logger
|
| 10 |
+
|
| 11 |
+
logger = Logger(__name__)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def convert_class_to_dict(data, isObject, showtrash=False):
|
| 15 |
+
if isObject == True:
|
| 16 |
+
data = data.__dict__.copy()
|
| 17 |
+
new_data = {"contents": {}}
|
| 18 |
+
|
| 19 |
+
for key in data["contents"]:
|
| 20 |
+
if data["contents"][key].trash == showtrash:
|
| 21 |
+
if data["contents"][key].type == "folder":
|
| 22 |
+
folder = data["contents"][key]
|
| 23 |
+
new_data["contents"][key] = {
|
| 24 |
+
"name": folder.name,
|
| 25 |
+
"type": folder.type,
|
| 26 |
+
"id": folder.id,
|
| 27 |
+
"path": folder.path,
|
| 28 |
+
"upload_date": folder.upload_date,
|
| 29 |
+
}
|
| 30 |
+
else:
|
| 31 |
+
file = data["contents"][key]
|
| 32 |
+
new_data["contents"][key] = {
|
| 33 |
+
"name": file.name,
|
| 34 |
+
"type": file.type,
|
| 35 |
+
"size": file.size,
|
| 36 |
+
"id": file.id,
|
| 37 |
+
"path": file.path,
|
| 38 |
+
"upload_date": file.upload_date,
|
| 39 |
+
}
|
| 40 |
+
return new_data
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
async def auto_ping_website():
|
| 44 |
+
if WEBSITE_URL is not None:
|
| 45 |
+
async with aiohttp.ClientSession() as session:
|
| 46 |
+
while True:
|
| 47 |
+
try:
|
| 48 |
+
async with session.get(WEBSITE_URL) as response:
|
| 49 |
+
if response.status == 200:
|
| 50 |
+
logger.info(f"Pinged website at {get_current_utc_time()}")
|
| 51 |
+
else:
|
| 52 |
+
logger.warning(f"Failed to ping website: {response.status}")
|
| 53 |
+
except Exception as e:
|
| 54 |
+
logger.warning(f"Failed to ping website: {e}")
|
| 55 |
+
|
| 56 |
+
await asyncio.sleep(60) # Ping website every minute
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
import shutil
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def reset_cache_dir():
|
| 63 |
+
cache_dir = Path("./cache")
|
| 64 |
+
downloads_dir = Path("./downloads")
|
| 65 |
+
shutil.rmtree(cache_dir, ignore_errors=True)
|
| 66 |
+
shutil.rmtree(downloads_dir, ignore_errors=True)
|
| 67 |
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
| 68 |
+
downloads_dir.mkdir(parents=True, exist_ok=True)
|
| 69 |
+
logger.info("Cache and downloads directory reset")
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def parse_content_disposition(content_disposition):
|
| 73 |
+
# Split the content disposition into parts
|
| 74 |
+
parts = content_disposition.split(";")
|
| 75 |
+
|
| 76 |
+
# Initialize filename variable
|
| 77 |
+
filename = None
|
| 78 |
+
|
| 79 |
+
# Loop through parts to find the filename
|
| 80 |
+
for part in parts:
|
| 81 |
+
part = part.strip()
|
| 82 |
+
if part.startswith("filename="):
|
| 83 |
+
# If filename is found
|
| 84 |
+
filename = part.split("=", 1)[1]
|
| 85 |
+
elif part.startswith("filename*="):
|
| 86 |
+
# If filename* is found
|
| 87 |
+
match = re.match(r"filename\*=(\S*)''(.*)", part)
|
| 88 |
+
if match:
|
| 89 |
+
encoding, value = match.groups()
|
| 90 |
+
try:
|
| 91 |
+
filename = urllib.parse.unquote(value, encoding=encoding)
|
| 92 |
+
except ValueError:
|
| 93 |
+
# Handle invalid encoding
|
| 94 |
+
pass
|
| 95 |
+
|
| 96 |
+
if filename is None:
|
| 97 |
+
raise Exception("Failed to get filename")
|
| 98 |
+
return filename
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def get_filename(headers, url):
|
| 102 |
+
try:
|
| 103 |
+
if headers.get("Content-Disposition"):
|
| 104 |
+
filename = parse_content_disposition(headers["Content-Disposition"])
|
| 105 |
+
else:
|
| 106 |
+
filename = unquote_plus(url.strip("/").split("/")[-1])
|
| 107 |
+
|
| 108 |
+
filename = filename.strip(' "')
|
| 109 |
+
except:
|
| 110 |
+
filename = unquote_plus(url.strip("/").split("/")[-1])
|
| 111 |
+
|
| 112 |
+
filename = filename.strip()
|
| 113 |
+
|
| 114 |
+
if filename == "" or "." not in filename:
|
| 115 |
+
if headers.get("Content-Type"):
|
| 116 |
+
extension = mimetypes.guess_extension(headers["Content-Type"])
|
| 117 |
+
if extension:
|
| 118 |
+
filename = f"{getRandomID()}{extension}"
|
| 119 |
+
else:
|
| 120 |
+
filename = getRandomID()
|
| 121 |
+
else:
|
| 122 |
+
filename = getRandomID()
|
| 123 |
+
|
| 124 |
+
return filename
|
utils/logger.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from tqdm import tqdm
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class TqdmLoggingHandler(logging.Handler):
|
| 6 |
+
def emit(self, record):
|
| 7 |
+
try:
|
| 8 |
+
msg = self.format(record)
|
| 9 |
+
tqdm.write(msg)
|
| 10 |
+
self.flush()
|
| 11 |
+
except Exception:
|
| 12 |
+
self.handleError(record)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class Logger:
|
| 16 |
+
def __init__(self, name, level=logging.DEBUG):
|
| 17 |
+
self.logger = logging.getLogger(name)
|
| 18 |
+
self.logger.setLevel(level)
|
| 19 |
+
self.formatter = logging.Formatter("%(name)s - %(levelname)s - %(message)s")
|
| 20 |
+
|
| 21 |
+
# Remove existing handlers to prevent duplicate logs
|
| 22 |
+
if self.logger.hasHandlers():
|
| 23 |
+
self.logger.handlers.clear()
|
| 24 |
+
|
| 25 |
+
# FileHandler for logging to a file
|
| 26 |
+
file_handler = logging.FileHandler("logs.txt", mode="w")
|
| 27 |
+
file_handler.setFormatter(self.formatter)
|
| 28 |
+
self.logger.addHandler(file_handler)
|
| 29 |
+
|
| 30 |
+
# Custom TqdmLoggingHandler for console output
|
| 31 |
+
stream_handler = TqdmLoggingHandler()
|
| 32 |
+
stream_handler.setFormatter(self.formatter)
|
| 33 |
+
self.logger.addHandler(stream_handler)
|
| 34 |
+
|
| 35 |
+
def debug(self, message):
|
| 36 |
+
self.logger.debug(message)
|
| 37 |
+
|
| 38 |
+
def info(self, message):
|
| 39 |
+
self.logger.info(message)
|
| 40 |
+
|
| 41 |
+
def warning(self, message):
|
| 42 |
+
self.logger.warning(message)
|
| 43 |
+
|
| 44 |
+
def error(self, message):
|
| 45 |
+
self.logger.error(message)
|
| 46 |
+
|
| 47 |
+
def critical(self, message):
|
| 48 |
+
self.logger.critical(message)
|
utils/streamer/__init__.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math, mimetypes
|
| 2 |
+
from fastapi.responses import StreamingResponse, Response
|
| 3 |
+
from utils.logger import Logger
|
| 4 |
+
from utils.streamer.custom_dl import ByteStreamer
|
| 5 |
+
from utils.streamer.file_properties import get_name
|
| 6 |
+
from utils.clients import (
|
| 7 |
+
get_client,
|
| 8 |
+
)
|
| 9 |
+
from urllib.parse import quote
|
| 10 |
+
|
| 11 |
+
logger = Logger(__name__)
|
| 12 |
+
|
| 13 |
+
class_cache = {}
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
async def media_streamer(channel: int, message_id: int, file_name: str, request):
|
| 17 |
+
global class_cache
|
| 18 |
+
|
| 19 |
+
range_header = request.headers.get("Range", 0)
|
| 20 |
+
|
| 21 |
+
faster_client = get_client()
|
| 22 |
+
|
| 23 |
+
if faster_client in class_cache:
|
| 24 |
+
tg_connect = class_cache[faster_client]
|
| 25 |
+
else:
|
| 26 |
+
tg_connect = ByteStreamer(faster_client)
|
| 27 |
+
class_cache[faster_client] = tg_connect
|
| 28 |
+
|
| 29 |
+
file_id = await tg_connect.get_file_properties(channel, message_id)
|
| 30 |
+
file_size = file_id.file_size
|
| 31 |
+
|
| 32 |
+
if range_header:
|
| 33 |
+
from_bytes, until_bytes = range_header.replace("bytes=", "").split("-")
|
| 34 |
+
from_bytes = int(from_bytes)
|
| 35 |
+
until_bytes = int(until_bytes) if until_bytes else file_size - 1
|
| 36 |
+
else:
|
| 37 |
+
from_bytes = 0
|
| 38 |
+
until_bytes = file_size - 1
|
| 39 |
+
|
| 40 |
+
if (until_bytes > file_size) or (from_bytes < 0) or (until_bytes < from_bytes):
|
| 41 |
+
return Response(
|
| 42 |
+
status_code=416,
|
| 43 |
+
content="416: Range not satisfiable",
|
| 44 |
+
headers={"Content-Range": f"bytes */{file_size}"},
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
chunk_size = 1024 * 1024
|
| 48 |
+
until_bytes = min(until_bytes, file_size - 1)
|
| 49 |
+
|
| 50 |
+
offset = from_bytes - (from_bytes % chunk_size)
|
| 51 |
+
first_part_cut = from_bytes - offset
|
| 52 |
+
last_part_cut = until_bytes % chunk_size + 1
|
| 53 |
+
|
| 54 |
+
req_length = until_bytes - from_bytes + 1
|
| 55 |
+
part_count = math.ceil(until_bytes / chunk_size) - math.floor(offset / chunk_size)
|
| 56 |
+
body = tg_connect.yield_file(
|
| 57 |
+
file_id, offset, first_part_cut, last_part_cut, part_count, chunk_size
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
disposition = "attachment"
|
| 61 |
+
mime_type = mimetypes.guess_type(file_name.lower())[0] or "application/octet-stream"
|
| 62 |
+
|
| 63 |
+
if (
|
| 64 |
+
"video/" in mime_type
|
| 65 |
+
or "audio/" in mime_type
|
| 66 |
+
or "image/" in mime_type
|
| 67 |
+
or "/html" in mime_type
|
| 68 |
+
):
|
| 69 |
+
disposition = "inline"
|
| 70 |
+
|
| 71 |
+
return StreamingResponse(
|
| 72 |
+
status_code=206 if range_header else 200,
|
| 73 |
+
content=body,
|
| 74 |
+
headers={
|
| 75 |
+
"Content-Type": f"{mime_type}",
|
| 76 |
+
"Content-Range": f"bytes {from_bytes}-{until_bytes}/{file_size}",
|
| 77 |
+
"Content-Length": str(req_length),
|
| 78 |
+
"Content-Disposition": f'{disposition}; filename="{quote(file_name)}"',
|
| 79 |
+
"Accept-Ranges": "bytes",
|
| 80 |
+
},
|
| 81 |
+
media_type=mime_type,
|
| 82 |
+
)
|
utils/streamer/custom_dl.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio
|
| 2 |
+
from typing import Dict, Union
|
| 3 |
+
from pyrogram import Client, utils, raw
|
| 4 |
+
from .file_properties import get_file_ids
|
| 5 |
+
from pyrogram.session import Session, Auth
|
| 6 |
+
from pyrogram.errors import AuthBytesInvalid
|
| 7 |
+
from pyrogram.file_id import FileId, FileType, ThumbnailSource
|
| 8 |
+
from utils.logger import Logger
|
| 9 |
+
|
| 10 |
+
logger = Logger(__name__)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class ByteStreamer:
|
| 14 |
+
def __init__(self, client: Client):
|
| 15 |
+
self.clean_timer = 30 * 60
|
| 16 |
+
self.client: Client = client
|
| 17 |
+
self.cached_file_ids: Dict[int, FileId] = {}
|
| 18 |
+
asyncio.create_task(self.clean_cache())
|
| 19 |
+
|
| 20 |
+
async def get_file_properties(self, channel, message_id: int) -> FileId:
|
| 21 |
+
if message_id not in self.cached_file_ids:
|
| 22 |
+
await self.generate_file_properties(channel, message_id)
|
| 23 |
+
return self.cached_file_ids[message_id]
|
| 24 |
+
|
| 25 |
+
async def generate_file_properties(self, channel, message_id: int) -> FileId:
|
| 26 |
+
file_id = await get_file_ids(self.client, channel, message_id)
|
| 27 |
+
if not file_id:
|
| 28 |
+
raise Exception("FileNotFound")
|
| 29 |
+
self.cached_file_ids[message_id] = file_id
|
| 30 |
+
return self.cached_file_ids[message_id]
|
| 31 |
+
|
| 32 |
+
async def generate_media_session(self, client: Client, file_id: FileId) -> Session:
|
| 33 |
+
"""
|
| 34 |
+
Generates the media session for the DC that contains the media file.
|
| 35 |
+
This is required for getting the bytes from Telegram servers.
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
media_session = client.media_sessions.get(file_id.dc_id, None)
|
| 39 |
+
|
| 40 |
+
if media_session is None:
|
| 41 |
+
if file_id.dc_id != await client.storage.dc_id():
|
| 42 |
+
media_session = Session(
|
| 43 |
+
client,
|
| 44 |
+
file_id.dc_id,
|
| 45 |
+
await Auth(
|
| 46 |
+
client, file_id.dc_id, await client.storage.test_mode()
|
| 47 |
+
).create(),
|
| 48 |
+
await client.storage.test_mode(),
|
| 49 |
+
is_media=True,
|
| 50 |
+
)
|
| 51 |
+
await media_session.start()
|
| 52 |
+
|
| 53 |
+
for _ in range(6):
|
| 54 |
+
exported_auth = await client.invoke(
|
| 55 |
+
raw.functions.auth.ExportAuthorization(dc_id=file_id.dc_id)
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
await media_session.invoke(
|
| 60 |
+
raw.functions.auth.ImportAuthorization(
|
| 61 |
+
id=exported_auth.id, bytes=exported_auth.bytes
|
| 62 |
+
)
|
| 63 |
+
)
|
| 64 |
+
break
|
| 65 |
+
except AuthBytesInvalid:
|
| 66 |
+
logger.debug(
|
| 67 |
+
f"Invalid authorization bytes for DC {file_id.dc_id}"
|
| 68 |
+
)
|
| 69 |
+
continue
|
| 70 |
+
else:
|
| 71 |
+
await media_session.stop()
|
| 72 |
+
raise AuthBytesInvalid
|
| 73 |
+
else:
|
| 74 |
+
media_session = Session(
|
| 75 |
+
client,
|
| 76 |
+
file_id.dc_id,
|
| 77 |
+
await client.storage.auth_key(),
|
| 78 |
+
await client.storage.test_mode(),
|
| 79 |
+
is_media=True,
|
| 80 |
+
)
|
| 81 |
+
await media_session.start()
|
| 82 |
+
logger.debug(f"Created media session for DC {file_id.dc_id}")
|
| 83 |
+
client.media_sessions[file_id.dc_id] = media_session
|
| 84 |
+
else:
|
| 85 |
+
logger.debug(f"Using cached media session for DC {file_id.dc_id}")
|
| 86 |
+
return media_session
|
| 87 |
+
|
| 88 |
+
@staticmethod
|
| 89 |
+
async def get_location(
|
| 90 |
+
file_id: FileId,
|
| 91 |
+
) -> Union[
|
| 92 |
+
raw.types.InputPhotoFileLocation,
|
| 93 |
+
raw.types.InputDocumentFileLocation,
|
| 94 |
+
raw.types.InputPeerPhotoFileLocation,
|
| 95 |
+
]:
|
| 96 |
+
"""
|
| 97 |
+
Returns the file location for the media file.
|
| 98 |
+
"""
|
| 99 |
+
file_type = file_id.file_type
|
| 100 |
+
|
| 101 |
+
if file_type == FileType.CHAT_PHOTO:
|
| 102 |
+
if file_id.chat_id > 0:
|
| 103 |
+
peer = raw.types.InputPeerUser(
|
| 104 |
+
user_id=file_id.chat_id, access_hash=file_id.chat_access_hash
|
| 105 |
+
)
|
| 106 |
+
else:
|
| 107 |
+
if file_id.chat_access_hash == 0:
|
| 108 |
+
peer = raw.types.InputPeerChat(chat_id=-file_id.chat_id)
|
| 109 |
+
else:
|
| 110 |
+
peer = raw.types.InputPeerChannel(
|
| 111 |
+
channel_id=utils.get_channel_id(file_id.chat_id),
|
| 112 |
+
access_hash=file_id.chat_access_hash,
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
location = raw.types.InputPeerPhotoFileLocation(
|
| 116 |
+
peer=peer,
|
| 117 |
+
volume_id=file_id.volume_id,
|
| 118 |
+
local_id=file_id.local_id,
|
| 119 |
+
big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG,
|
| 120 |
+
)
|
| 121 |
+
elif file_type == FileType.PHOTO:
|
| 122 |
+
location = raw.types.InputPhotoFileLocation(
|
| 123 |
+
id=file_id.media_id,
|
| 124 |
+
access_hash=file_id.access_hash,
|
| 125 |
+
file_reference=file_id.file_reference,
|
| 126 |
+
thumb_size=file_id.thumbnail_size,
|
| 127 |
+
)
|
| 128 |
+
else:
|
| 129 |
+
location = raw.types.InputDocumentFileLocation(
|
| 130 |
+
id=file_id.media_id,
|
| 131 |
+
access_hash=file_id.access_hash,
|
| 132 |
+
file_reference=file_id.file_reference,
|
| 133 |
+
thumb_size=file_id.thumbnail_size,
|
| 134 |
+
)
|
| 135 |
+
return location
|
| 136 |
+
|
| 137 |
+
async def yield_file(
|
| 138 |
+
self,
|
| 139 |
+
file_id: FileId,
|
| 140 |
+
offset: int,
|
| 141 |
+
first_part_cut: int,
|
| 142 |
+
last_part_cut: int,
|
| 143 |
+
part_count: int,
|
| 144 |
+
chunk_size: int,
|
| 145 |
+
):
|
| 146 |
+
"""
|
| 147 |
+
Custom generator that yields the bytes of the media file.
|
| 148 |
+
"""
|
| 149 |
+
client = self.client
|
| 150 |
+
logger.debug(f"Starting to yielding file with client.")
|
| 151 |
+
media_session = await self.generate_media_session(client, file_id)
|
| 152 |
+
|
| 153 |
+
current_part = 1
|
| 154 |
+
location = await self.get_location(file_id)
|
| 155 |
+
|
| 156 |
+
try:
|
| 157 |
+
r = await media_session.invoke(
|
| 158 |
+
raw.functions.upload.GetFile(
|
| 159 |
+
location=location, offset=offset, limit=chunk_size
|
| 160 |
+
),
|
| 161 |
+
)
|
| 162 |
+
if isinstance(r, raw.types.upload.File):
|
| 163 |
+
while True:
|
| 164 |
+
chunk = r.bytes
|
| 165 |
+
if not chunk:
|
| 166 |
+
break
|
| 167 |
+
elif part_count == 1:
|
| 168 |
+
yield chunk[first_part_cut:last_part_cut]
|
| 169 |
+
elif current_part == 1:
|
| 170 |
+
yield chunk[first_part_cut:]
|
| 171 |
+
elif current_part == part_count:
|
| 172 |
+
yield chunk[:last_part_cut]
|
| 173 |
+
else:
|
| 174 |
+
yield chunk
|
| 175 |
+
|
| 176 |
+
current_part += 1
|
| 177 |
+
offset += chunk_size
|
| 178 |
+
|
| 179 |
+
if current_part > part_count:
|
| 180 |
+
break
|
| 181 |
+
|
| 182 |
+
r = await media_session.invoke(
|
| 183 |
+
raw.functions.upload.GetFile(
|
| 184 |
+
location=location, offset=offset, limit=chunk_size
|
| 185 |
+
),
|
| 186 |
+
)
|
| 187 |
+
except (TimeoutError, AttributeError):
|
| 188 |
+
pass
|
| 189 |
+
finally:
|
| 190 |
+
logger.debug(f"Finished yielding file with {current_part} parts.")
|
| 191 |
+
|
| 192 |
+
async def clean_cache(self) -> None:
|
| 193 |
+
"""
|
| 194 |
+
function to clean the cache to reduce memory usage
|
| 195 |
+
"""
|
| 196 |
+
while True:
|
| 197 |
+
await asyncio.sleep(self.clean_timer)
|
| 198 |
+
self.cached_file_ids.clear()
|
| 199 |
+
logger.debug("Cleaned the cache")
|
utils/streamer/file_properties.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pyrogram import Client
|
| 2 |
+
from pyrogram.types import Message
|
| 3 |
+
from pyrogram.file_id import FileId
|
| 4 |
+
from typing import Any, Optional, Union
|
| 5 |
+
from pyrogram.raw.types.messages import Messages
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
async def parse_file_id(message: "Message") -> Optional[FileId]:
|
| 10 |
+
media = get_media_from_message(message)
|
| 11 |
+
if media:
|
| 12 |
+
return FileId.decode(media.file_id)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
async def parse_file_unique_id(message: "Messages") -> Optional[str]:
|
| 16 |
+
media = get_media_from_message(message)
|
| 17 |
+
if media:
|
| 18 |
+
return media.file_unique_id
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
async def get_file_ids(client: Client, chat_id, message_id) -> Optional[FileId]:
|
| 22 |
+
message = await client.get_messages(chat_id, int(message_id))
|
| 23 |
+
if message.empty:
|
| 24 |
+
raise Exception("FileNotFound")
|
| 25 |
+
media = get_media_from_message(message)
|
| 26 |
+
file_unique_id = await parse_file_unique_id(message)
|
| 27 |
+
file_id = await parse_file_id(message)
|
| 28 |
+
setattr(file_id, "file_size", getattr(media, "file_size", 0))
|
| 29 |
+
setattr(file_id, "mime_type", getattr(media, "mime_type", ""))
|
| 30 |
+
setattr(file_id, "file_name", getattr(media, "file_name", ""))
|
| 31 |
+
setattr(file_id, "unique_id", file_unique_id)
|
| 32 |
+
return file_id
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def get_media_from_message(message: "Message") -> Any:
|
| 36 |
+
media_types = (
|
| 37 |
+
"audio",
|
| 38 |
+
"document",
|
| 39 |
+
"photo",
|
| 40 |
+
"sticker",
|
| 41 |
+
"animation",
|
| 42 |
+
"video",
|
| 43 |
+
"voice",
|
| 44 |
+
"video_note",
|
| 45 |
+
)
|
| 46 |
+
for attr in media_types:
|
| 47 |
+
media = getattr(message, attr, None)
|
| 48 |
+
if media:
|
| 49 |
+
return media
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def get_name(media_msg: Union[Message, FileId]) -> str:
|
| 53 |
+
if isinstance(media_msg, Message):
|
| 54 |
+
media = get_media_from_message(media_msg)
|
| 55 |
+
file_name = getattr(media, "file_name", "")
|
| 56 |
+
|
| 57 |
+
elif isinstance(media_msg, FileId):
|
| 58 |
+
file_name = getattr(media_msg, "file_name", "")
|
| 59 |
+
|
| 60 |
+
if not file_name:
|
| 61 |
+
if isinstance(media_msg, Message) and media_msg.media:
|
| 62 |
+
media_type = media_msg.media.value
|
| 63 |
+
elif media_msg.file_type:
|
| 64 |
+
media_type = media_msg.file_type.name.lower()
|
| 65 |
+
else:
|
| 66 |
+
media_type = "file"
|
| 67 |
+
|
| 68 |
+
formats = {
|
| 69 |
+
"photo": "jpg",
|
| 70 |
+
"audio": "mp3",
|
| 71 |
+
"voice": "ogg",
|
| 72 |
+
"video": "mp4",
|
| 73 |
+
"animation": "mp4",
|
| 74 |
+
"video_note": "mp4",
|
| 75 |
+
"sticker": "webp",
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
ext = formats.get(media_type)
|
| 79 |
+
ext = "." + ext if ext else ""
|
| 80 |
+
|
| 81 |
+
date = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
| 82 |
+
file_name = f"{media_type}-{date}{ext}"
|
| 83 |
+
|
| 84 |
+
return file_name
|
utils/uploader.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from utils.clients import get_client
|
| 2 |
+
from pyrogram import Client
|
| 3 |
+
from pyrogram.types import Message
|
| 4 |
+
from config import STORAGE_CHANNEL
|
| 5 |
+
import os
|
| 6 |
+
from utils.logger import Logger
|
| 7 |
+
from urllib.parse import unquote_plus
|
| 8 |
+
|
| 9 |
+
logger = Logger(__name__)
|
| 10 |
+
PROGRESS_CACHE = {}
|
| 11 |
+
STOP_TRANSMISSION = []
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
async def progress_callback(current, total, id, client: Client, file_path):
|
| 15 |
+
global PROGRESS_CACHE, STOP_TRANSMISSION
|
| 16 |
+
|
| 17 |
+
PROGRESS_CACHE[id] = ("running", current, total)
|
| 18 |
+
if id in STOP_TRANSMISSION:
|
| 19 |
+
logger.info(f"Stopping transmission {id}")
|
| 20 |
+
client.stop_transmission()
|
| 21 |
+
try:
|
| 22 |
+
os.remove(file_path)
|
| 23 |
+
except:
|
| 24 |
+
pass
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
async def start_file_uploader(
|
| 28 |
+
file_path, id, directory_path, filename, file_size, delete=True
|
| 29 |
+
):
|
| 30 |
+
global PROGRESS_CACHE
|
| 31 |
+
from utils.directoryHandler import DRIVE_DATA
|
| 32 |
+
|
| 33 |
+
logger.info(f"Uploading file {file_path} {id}")
|
| 34 |
+
|
| 35 |
+
if file_size > 1.98 * 1024 * 1024 * 1024:
|
| 36 |
+
# Use premium client for files larger than 2 GB
|
| 37 |
+
client: Client = get_client(premium_required=True)
|
| 38 |
+
else:
|
| 39 |
+
client: Client = get_client()
|
| 40 |
+
|
| 41 |
+
PROGRESS_CACHE[id] = ("running", 0, 0)
|
| 42 |
+
|
| 43 |
+
message: Message = await client.send_document(
|
| 44 |
+
STORAGE_CHANNEL,
|
| 45 |
+
file_path,
|
| 46 |
+
progress=progress_callback,
|
| 47 |
+
progress_args=(id, client, file_path),
|
| 48 |
+
disable_notification=True,
|
| 49 |
+
)
|
| 50 |
+
size = (
|
| 51 |
+
message.photo
|
| 52 |
+
or message.document
|
| 53 |
+
or message.video
|
| 54 |
+
or message.audio
|
| 55 |
+
or message.sticker
|
| 56 |
+
).file_size
|
| 57 |
+
|
| 58 |
+
filename = unquote_plus(filename)
|
| 59 |
+
|
| 60 |
+
DRIVE_DATA.new_file(directory_path, filename, message.id, size)
|
| 61 |
+
PROGRESS_CACHE[id] = ("completed", size, size)
|
| 62 |
+
|
| 63 |
+
logger.info(f"Uploaded file {file_path} {id}")
|
| 64 |
+
|
| 65 |
+
if delete:
|
| 66 |
+
try:
|
| 67 |
+
os.remove(file_path)
|
| 68 |
+
except Exception as e:
|
| 69 |
+
pass
|
website/VideoPlayer.html
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8">
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 7 |
+
<title>TG Drive - Video Player</title>
|
| 8 |
+
|
| 9 |
+
<link href="//vjs.zencdn.net/8.3.0/video-js.min.css" rel="stylesheet">
|
| 10 |
+
<script src="//vjs.zencdn.net/8.3.0/video.min.js"></script>
|
| 11 |
+
<style>
|
| 12 |
+
body {
|
| 13 |
+
display: flex;
|
| 14 |
+
flex-direction: column;
|
| 15 |
+
align-items: center;
|
| 16 |
+
justify-content: center;
|
| 17 |
+
height: 100vh;
|
| 18 |
+
margin: 0;
|
| 19 |
+
font-family: Arial, sans-serif;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.video-container {
|
| 23 |
+
width: 90%;
|
| 24 |
+
max-width: 800px;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
.buttons-container {
|
| 28 |
+
margin-top: 20px;
|
| 29 |
+
display: flex;
|
| 30 |
+
justify-content: space-evenly;
|
| 31 |
+
width: 90%;
|
| 32 |
+
max-width: 800px;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.copy-button {
|
| 36 |
+
padding: 10px 20px;
|
| 37 |
+
font-size: 16px;
|
| 38 |
+
cursor: pointer;
|
| 39 |
+
background-color: #007bff;
|
| 40 |
+
color: white;
|
| 41 |
+
border: none;
|
| 42 |
+
border-radius: 5px;
|
| 43 |
+
transition: background-color 0.3s ease;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.copy-button:hover {
|
| 47 |
+
background-color: #0056b3;
|
| 48 |
+
}
|
| 49 |
+
</style>
|
| 50 |
+
</head>
|
| 51 |
+
|
| 52 |
+
<body>
|
| 53 |
+
|
| 54 |
+
<div class="video-container">
|
| 55 |
+
<video id="my-player" class="video-js vjs-fluid" controls preload="auto" data-setup='{}'>
|
| 56 |
+
<source id="video-src" src="" type="video/mp4">
|
| 57 |
+
</source>
|
| 58 |
+
<p class="vjs-no-js">
|
| 59 |
+
To view this video please enable JavaScript, and consider upgrading to a
|
| 60 |
+
web browser that
|
| 61 |
+
<a href="https://videojs.com/html5-video-support/" target="_blank">
|
| 62 |
+
supports HTML5 video
|
| 63 |
+
</a>
|
| 64 |
+
</p>
|
| 65 |
+
</video>
|
| 66 |
+
</div>
|
| 67 |
+
|
| 68 |
+
<div class="buttons-container">
|
| 69 |
+
<button class="copy-button" onclick="copyStreamUrl()">Copy Stream URL</button>
|
| 70 |
+
<button class="copy-button" onclick="copyDownloadUrl()">Copy Download URL</button>
|
| 71 |
+
</div>
|
| 72 |
+
|
| 73 |
+
<script>
|
| 74 |
+
const downloadUrl = (new URL(window.location.href)).searchParams.get('url');
|
| 75 |
+
document.getElementById('video-src').src = downloadUrl;
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
function copyTextToClipboard(text) {
|
| 79 |
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
| 80 |
+
navigator.clipboard.writeText(text).then(function () {
|
| 81 |
+
alert('Link copied to clipboard!');
|
| 82 |
+
}).catch(function (err) {
|
| 83 |
+
console.error('Could not copy text: ', err);
|
| 84 |
+
fallbackCopyTextToClipboard(text);
|
| 85 |
+
});
|
| 86 |
+
} else {
|
| 87 |
+
fallbackCopyTextToClipboard(text);
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
function fallbackCopyTextToClipboard(text) {
|
| 92 |
+
const textArea = document.createElement('textarea');
|
| 93 |
+
textArea.value = text;
|
| 94 |
+
document.body.appendChild(textArea);
|
| 95 |
+
textArea.focus();
|
| 96 |
+
textArea.select();
|
| 97 |
+
|
| 98 |
+
try {
|
| 99 |
+
const successful = document.execCommand('copy');
|
| 100 |
+
if (successful) {
|
| 101 |
+
alert('Link copied to clipboard!');
|
| 102 |
+
} else {
|
| 103 |
+
alert('Failed to copy the link.');
|
| 104 |
+
}
|
| 105 |
+
} catch (err) {
|
| 106 |
+
console.error('Fallback: Oops, unable to copy', err);
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
document.body.removeChild(textArea);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
function copyStreamUrl() {
|
| 113 |
+
copyTextToClipboard(window.location.href);
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
function copyDownloadUrl() {
|
| 117 |
+
copyTextToClipboard(downloadUrl);
|
| 118 |
+
}
|
| 119 |
+
</script>
|
| 120 |
+
|
| 121 |
+
</body>
|
| 122 |
+
|
| 123 |
+
</html>
|
website/home.html
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<meta charset="UTF-8" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>TG Drive</title>
|
| 8 |
+
<link rel="stylesheet" href="static/home.css" />
|
| 9 |
+
|
| 10 |
+
<!-- Fonts Start -->
|
| 11 |
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 12 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
| 13 |
+
<link
|
| 14 |
+
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
|
| 15 |
+
rel="stylesheet" />
|
| 16 |
+
<!-- Fonts End -->
|
| 17 |
+
</head>
|
| 18 |
+
|
| 19 |
+
<body>
|
| 20 |
+
<div class="container">
|
| 21 |
+
<!-- Sidebar Start -->
|
| 22 |
+
<div class="sidebar">
|
| 23 |
+
<div class="sidebar-header">
|
| 24 |
+
<img src="https://ssl.gstatic.com/images/branding/product/1x/drive_2020q4_48dp.png" />
|
| 25 |
+
<span>TG Drive</span>
|
| 26 |
+
</div>
|
| 27 |
+
|
| 28 |
+
<button id="new-button" class="new-button">
|
| 29 |
+
<img src="static/assets/plus-icon.svg" />New
|
| 30 |
+
</button>
|
| 31 |
+
|
| 32 |
+
<div id="new-upload" class="new-upload">
|
| 33 |
+
<input id="new-upload-focus" type="text"
|
| 34 |
+
style="height: 0px; width: 0px; border: none; position: absolute" readonly />
|
| 35 |
+
<div id="new-folder-btn">
|
| 36 |
+
<img src="static/assets/folder-icon.svg" />
|
| 37 |
+
New Folder
|
| 38 |
+
</div>
|
| 39 |
+
<hr />
|
| 40 |
+
<div id="file-upload-btn">
|
| 41 |
+
<img src="static/assets/upload-icon.svg" />
|
| 42 |
+
File Upload
|
| 43 |
+
</div>
|
| 44 |
+
<input type="file" id="fileInput" style="height: 0px; width: 0px; border: none; position: absolute" />
|
| 45 |
+
<hr />
|
| 46 |
+
<div id="url-upload-btn">
|
| 47 |
+
<img src="static/assets/link-icon.svg" />
|
| 48 |
+
URL Upload
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
|
| 52 |
+
<div class="sidebar-menu">
|
| 53 |
+
<a class="selected-item" href="/?path=/"><img src="static/assets/home-icon.svg" />Home</a>
|
| 54 |
+
<a class="unselected-item" href="/?path=/trash"><img src="static/assets/trash-icon.svg" />Trash</a>
|
| 55 |
+
</div>
|
| 56 |
+
</div>
|
| 57 |
+
<!-- Sidebar End -->
|
| 58 |
+
|
| 59 |
+
<div id="bg-blur" class="bg-blur"></div>
|
| 60 |
+
|
| 61 |
+
<!-- Create New Folder Start -->
|
| 62 |
+
<div id="create-new-folder" class="create-new-folder">
|
| 63 |
+
<span>New Folder</span>
|
| 64 |
+
<input type="text" id="new-folder-name" placeholder="Enter Folder Name" autocomplete="off" />
|
| 65 |
+
<div>
|
| 66 |
+
<button id="new-folder-cancel">Cancel</button>
|
| 67 |
+
<button id="new-folder-create">Create</button>
|
| 68 |
+
</div>
|
| 69 |
+
</div>
|
| 70 |
+
<!-- Create New Folder End -->
|
| 71 |
+
|
| 72 |
+
<!-- File / Folder Rename Start -->
|
| 73 |
+
<div id="rename-file-folder" class="create-new-folder">
|
| 74 |
+
<span>Edit File/Folder Name</span>
|
| 75 |
+
<input type="text" id="rename-name" placeholder="Enter File/Folder Name" autocomplete="off" />
|
| 76 |
+
<div>
|
| 77 |
+
<button id="rename-cancel">Cancel</button>
|
| 78 |
+
<button id="rename-create">Rename</button>
|
| 79 |
+
</div>
|
| 80 |
+
</div>
|
| 81 |
+
<!-- File / Folder Rename End -->
|
| 82 |
+
|
| 83 |
+
<!-- Remote Upload Start -->
|
| 84 |
+
<div id="new-url-upload" class="create-new-folder">
|
| 85 |
+
<span>Url Upload</span>
|
| 86 |
+
<input type="text" id="remote-url" placeholder="Enter Direct Download Link Of File" autocomplete="off" />
|
| 87 |
+
<div id="single-threaded-div">
|
| 88 |
+
<input type="checkbox" name="single-threaded-toggle" id="single-threaded-toggle">
|
| 89 |
+
<label for="single-threaded-toggle">Single Threaded</label>
|
| 90 |
+
<a href="#"><img src="static/assets/info-icon-small.svg" alt="Info"></a>
|
| 91 |
+
</div>
|
| 92 |
+
<div>
|
| 93 |
+
<button id="remote-cancel">Cancel</button>
|
| 94 |
+
<button id="remote-start">Upload</button>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
<!-- Remote Upload End -->
|
| 98 |
+
|
| 99 |
+
<!-- Get Password Start -->
|
| 100 |
+
<div id="get-password" class="create-new-folder">
|
| 101 |
+
<span>Admin Login</span>
|
| 102 |
+
<input type="text" id="auth-pass" placeholder="Enter Password" autocomplete="off" />
|
| 103 |
+
<div>
|
| 104 |
+
<button id="pass-login">Login</button>
|
| 105 |
+
</div>
|
| 106 |
+
</div>
|
| 107 |
+
<!-- Get Password End -->
|
| 108 |
+
|
| 109 |
+
<!-- File Uploader Start -->
|
| 110 |
+
<div id="file-uploader" class="file-uploader">
|
| 111 |
+
<span class="upload-head">🚀 Uploading File...</span>
|
| 112 |
+
<span id="upload-filename" class="upload-info">Filename : </span>
|
| 113 |
+
<span id="upload-filesize" class="upload-info">Filesize :</span>
|
| 114 |
+
<span id="upload-status" class="upload-info">Status : </span>
|
| 115 |
+
<span id="upload-percent" class="upload-info">Progress : </span>
|
| 116 |
+
<div class="progress">
|
| 117 |
+
<div class="progress-bar" id="progress-bar"></div>
|
| 118 |
+
</div>
|
| 119 |
+
<div class="btn-div">
|
| 120 |
+
<button id="cancel-file-upload">Cancel Upload</button>
|
| 121 |
+
</div>
|
| 122 |
+
</div>
|
| 123 |
+
<!-- File Uploader End -->
|
| 124 |
+
|
| 125 |
+
<!-- Main Content Start -->
|
| 126 |
+
<div class="main-content">
|
| 127 |
+
<div class="header">
|
| 128 |
+
<div class="search-bar">
|
| 129 |
+
<img src="static/assets/search-icon.svg" />
|
| 130 |
+
<form id="search-form">
|
| 131 |
+
<input id="file-search" type="text" placeholder="Search in Drive" autocomplete="off" />
|
| 132 |
+
</form>
|
| 133 |
+
|
| 134 |
+
</div>
|
| 135 |
+
</div>
|
| 136 |
+
|
| 137 |
+
<div class="directory">
|
| 138 |
+
<table>
|
| 139 |
+
<thead>
|
| 140 |
+
<tr>
|
| 141 |
+
<th>Name</th>
|
| 142 |
+
<th>File Size</th>
|
| 143 |
+
<th>More</th>
|
| 144 |
+
</tr>
|
| 145 |
+
</thead>
|
| 146 |
+
<tbody id="directory-data"></tbody>
|
| 147 |
+
</table>
|
| 148 |
+
</div>
|
| 149 |
+
</div>
|
| 150 |
+
</div>
|
| 151 |
+
<script src="static/js/extra.js"></script>
|
| 152 |
+
<script src="static/js/apiHandler.js"></script>
|
| 153 |
+
<script src="static/js/sidebar.js"></script>
|
| 154 |
+
<script src="static/js/fileClickHandler.js"></script>
|
| 155 |
+
<script src="static/js/main.js"></script>
|
| 156 |
+
</body>
|
| 157 |
+
|
| 158 |
+
</html>
|
website/static/assets/file-icon.svg
ADDED
|
|
website/static/assets/folder-icon.svg
ADDED
|
|
website/static/assets/folder-solid-icon.svg
ADDED
|
|
website/static/assets/home-icon.svg
ADDED
|
|
website/static/assets/info-icon-small.svg
ADDED
|
|
website/static/assets/link-icon.svg
ADDED
|
|
website/static/assets/load-icon.svg
ADDED
|
|
website/static/assets/more-icon.svg
ADDED
|
|
website/static/assets/pencil-icon.svg
ADDED
|
|
website/static/assets/plus-icon.svg
ADDED
|
|
website/static/assets/profile-icon.svg
ADDED
|
|
website/static/assets/search-icon.svg
ADDED
|
|
website/static/assets/share-icon.svg
ADDED
|
|
website/static/assets/trash-icon.svg
ADDED
|
|
website/static/assets/upload-icon.svg
ADDED
|
|
website/static/home.css
ADDED
|
@@ -0,0 +1,527 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
* {
|
| 2 |
+
margin: 0px;
|
| 3 |
+
padding: 0px;
|
| 4 |
+
box-sizing: border-box;
|
| 5 |
+
font-family: "Roboto", sans-serif;
|
| 6 |
+
user-select: none;
|
| 7 |
+
-webkit-user-select: none;
|
| 8 |
+
-moz-user-select: none;
|
| 9 |
+
-ms-user-select: none;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
.container {
|
| 13 |
+
width: 100%;
|
| 14 |
+
height: 100vh;
|
| 15 |
+
display: grid;
|
| 16 |
+
background: #f1f1f1;
|
| 17 |
+
grid-template-columns: auto 1fr;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
.rotate-90 {
|
| 21 |
+
transform: rotate(90deg);
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
/* Sidebar Style Start */
|
| 25 |
+
|
| 26 |
+
.sidebar {
|
| 27 |
+
width: 250px;
|
| 28 |
+
height: 100%;
|
| 29 |
+
background-color: #f8fafd;
|
| 30 |
+
padding: 20px;
|
| 31 |
+
display: flex;
|
| 32 |
+
flex-direction: column;
|
| 33 |
+
justify-content: start;
|
| 34 |
+
align-items: start;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.sidebar-header {
|
| 38 |
+
display: flex;
|
| 39 |
+
align-items: center;
|
| 40 |
+
justify-content: start;
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
.sidebar-header img {
|
| 44 |
+
width: 40px;
|
| 45 |
+
height: 40px;
|
| 46 |
+
margin-right: 10px;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.sidebar-header span {
|
| 50 |
+
font-size: 1.2rem;
|
| 51 |
+
font-weight: 500;
|
| 52 |
+
color: #444746;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.sidebar .new-button {
|
| 56 |
+
padding: 15px 20px;
|
| 57 |
+
background-color: #fff;
|
| 58 |
+
border: 1px solid #e0e0e0;
|
| 59 |
+
border-radius: 20px;
|
| 60 |
+
margin-top: 20px;
|
| 61 |
+
display: flex;
|
| 62 |
+
align-items: center;
|
| 63 |
+
justify-content: center;
|
| 64 |
+
cursor: pointer;
|
| 65 |
+
transition: all 0.3s ease;
|
| 66 |
+
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
|
| 67 |
+
font-size: 0.9rem;
|
| 68 |
+
color: #282c35;
|
| 69 |
+
font-weight: 500;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.sidebar .new-button:hover {
|
| 73 |
+
background-color: #edf1fa;
|
| 74 |
+
border: 1px solid #c9d0e6;
|
| 75 |
+
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3);
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.sidebar .new-button img {
|
| 79 |
+
width: 24px;
|
| 80 |
+
height: 24px;
|
| 81 |
+
margin-right: 10px;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.new-upload {
|
| 85 |
+
position: absolute;
|
| 86 |
+
background-color: #ffffff;
|
| 87 |
+
padding: 5px 0px;
|
| 88 |
+
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
|
| 89 |
+
border-radius: 5px;
|
| 90 |
+
width: 250px;
|
| 91 |
+
top: 40px;
|
| 92 |
+
z-index: -1;
|
| 93 |
+
opacity: 0;
|
| 94 |
+
transition: all 0.2s;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.new-upload div {
|
| 98 |
+
padding: 10px 20px;
|
| 99 |
+
display: flex;
|
| 100 |
+
align-items: center;
|
| 101 |
+
justify-content: start;
|
| 102 |
+
cursor: pointer;
|
| 103 |
+
transition: all 0.3s ease;
|
| 104 |
+
font-size: 14px;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.new-upload div:hover {
|
| 108 |
+
background-color: #f1f1f1;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.new-upload div img {
|
| 112 |
+
margin-right: 10px;
|
| 113 |
+
height: 20px;
|
| 114 |
+
width: 20px;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.new-upload hr {
|
| 118 |
+
border: 1px solid #e0e0e0;
|
| 119 |
+
margin: 5px 0px;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.bg-blur {
|
| 123 |
+
position: fixed;
|
| 124 |
+
background-color: #000000;
|
| 125 |
+
height: 100dvh;
|
| 126 |
+
width: 100dvw;
|
| 127 |
+
transition: opacity 0.3s ease;
|
| 128 |
+
z-index: -1;
|
| 129 |
+
opacity: 0;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
/* More Options Start */
|
| 133 |
+
|
| 134 |
+
.more-options {
|
| 135 |
+
position: absolute;
|
| 136 |
+
background-color: #ffffff;
|
| 137 |
+
padding: 2px 0px;
|
| 138 |
+
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
|
| 139 |
+
border-radius: 5px;
|
| 140 |
+
z-index: -1;
|
| 141 |
+
opacity: 0;
|
| 142 |
+
transition: all 0.2s;
|
| 143 |
+
width: 150px;
|
| 144 |
+
transform: translateX(-50%);
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
.more-options div {
|
| 148 |
+
width: 100%;
|
| 149 |
+
padding: 5px 20px;
|
| 150 |
+
display: flex;
|
| 151 |
+
align-items: center;
|
| 152 |
+
justify-content: center;
|
| 153 |
+
cursor: pointer;
|
| 154 |
+
transition: all 0.3s ease;
|
| 155 |
+
font-size: 14px;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
.more-options div:hover {
|
| 159 |
+
background-color: #f1f1f1;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
.more-options div img {
|
| 163 |
+
margin-right: 10px;
|
| 164 |
+
height: 20px;
|
| 165 |
+
width: 20px;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.more-options hr {
|
| 169 |
+
border: 1px solid #e0e0e0;
|
| 170 |
+
margin: 2px 0px;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
/* More Options End */
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
/* Create New Folder Start */
|
| 177 |
+
.create-new-folder {
|
| 178 |
+
position: fixed;
|
| 179 |
+
top: 50%;
|
| 180 |
+
left: 50%;
|
| 181 |
+
transform: translate(-50%, -50%);
|
| 182 |
+
padding: 20px;
|
| 183 |
+
display: flex;
|
| 184 |
+
align-items: center;
|
| 185 |
+
justify-content: center;
|
| 186 |
+
flex-direction: column;
|
| 187 |
+
background-color: #fff;
|
| 188 |
+
border-radius: 10px;
|
| 189 |
+
z-index: -1;
|
| 190 |
+
opacity: 0;
|
| 191 |
+
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
|
| 192 |
+
transition: opacity 0.3s ease;
|
| 193 |
+
width: 400px;
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
.create-new-folder span {
|
| 197 |
+
font-size: 1.2rem;
|
| 198 |
+
font-weight: 400;
|
| 199 |
+
color: #444746;
|
| 200 |
+
margin-bottom: 20px;
|
| 201 |
+
width: 100%;
|
| 202 |
+
margin-left: 10px;
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
.create-new-folder input {
|
| 206 |
+
width: 100%;
|
| 207 |
+
padding: 10px 20px;
|
| 208 |
+
border: 1px solid #e0e0e0;
|
| 209 |
+
border-radius: 5px;
|
| 210 |
+
outline: none;
|
| 211 |
+
font-size: 0.9rem;
|
| 212 |
+
font-weight: 400;
|
| 213 |
+
color: #444746;
|
| 214 |
+
margin-bottom: 20px;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
.create-new-folder div {
|
| 218 |
+
width: 100%;
|
| 219 |
+
display: flex;
|
| 220 |
+
align-items: center;
|
| 221 |
+
justify-content: end;
|
| 222 |
+
gap: 10px;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
.create-new-folder button {
|
| 226 |
+
background-color: transparent;
|
| 227 |
+
border: none;
|
| 228 |
+
border-radius: 20px;
|
| 229 |
+
display: flex;
|
| 230 |
+
align-items: center;
|
| 231 |
+
justify-content: center;
|
| 232 |
+
cursor: pointer;
|
| 233 |
+
transition: all 0.3s ease;
|
| 234 |
+
font-size: 0.9rem;
|
| 235 |
+
color: #0b57d0;
|
| 236 |
+
font-weight: 500;
|
| 237 |
+
padding: 10px 20px;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.create-new-folder button:hover {
|
| 241 |
+
background-color: #edf1fa;
|
| 242 |
+
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
/* Create New Folder End */
|
| 246 |
+
|
| 247 |
+
/* Remote URL Upload Start */
|
| 248 |
+
|
| 249 |
+
#single-threaded-div{
|
| 250 |
+
display: flex;
|
| 251 |
+
flex-direction: row;
|
| 252 |
+
justify-content: start;
|
| 253 |
+
align-items: center;
|
| 254 |
+
width: 100%;
|
| 255 |
+
margin: 10px 0px;
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
#single-threaded-div input{
|
| 259 |
+
width: auto;
|
| 260 |
+
margin: 0px;
|
| 261 |
+
margin-left: 10px;
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
#single-threaded-div img{
|
| 265 |
+
height: 16px;
|
| 266 |
+
width: 16px;
|
| 267 |
+
}
|
| 268 |
+
#single-threaded-div a{
|
| 269 |
+
height: 16px;
|
| 270 |
+
width: 16px;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
/* Remote URL Upload End */
|
| 274 |
+
|
| 275 |
+
/* File Uploader Start */
|
| 276 |
+
|
| 277 |
+
.file-uploader {
|
| 278 |
+
position: fixed;
|
| 279 |
+
top: 50%;
|
| 280 |
+
left: 50%;
|
| 281 |
+
transform: translate(-50%, -50%);
|
| 282 |
+
padding: 20px;
|
| 283 |
+
display: flex;
|
| 284 |
+
align-items: center;
|
| 285 |
+
justify-content: center;
|
| 286 |
+
flex-direction: column;
|
| 287 |
+
background-color: #fff;
|
| 288 |
+
border-radius: 10px;
|
| 289 |
+
z-index: -1;
|
| 290 |
+
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
|
| 291 |
+
transition: opacity 0.3s ease;
|
| 292 |
+
width: 500px;
|
| 293 |
+
opacity: 0;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.upload-head {
|
| 297 |
+
font-size: 1.2rem;
|
| 298 |
+
font-weight: 500;
|
| 299 |
+
color: #444746;
|
| 300 |
+
margin-bottom: 20px;
|
| 301 |
+
width: 100%;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
.upload-info {
|
| 305 |
+
font-size: 16px;
|
| 306 |
+
font-weight: 400;
|
| 307 |
+
color: #444746;
|
| 308 |
+
margin-bottom: 5px;
|
| 309 |
+
width: 100%;
|
| 310 |
+
max-height: 40px;
|
| 311 |
+
overflow: hidden;
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
.progress {
|
| 315 |
+
width: 100%;
|
| 316 |
+
background-color: #ddd;
|
| 317 |
+
border-radius: 5px;
|
| 318 |
+
overflow: hidden;
|
| 319 |
+
margin-top: 20px;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
.progress-bar {
|
| 323 |
+
height: 20px;
|
| 324 |
+
background-color: #007bff;
|
| 325 |
+
width: 0;
|
| 326 |
+
transition: width 0.3s;
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
.file-uploader .btn-div {
|
| 330 |
+
width: 100%;
|
| 331 |
+
display: flex;
|
| 332 |
+
align-items: center;
|
| 333 |
+
justify-content: end;
|
| 334 |
+
margin-top: 20px;
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
.file-uploader button {
|
| 338 |
+
background-color: transparent;
|
| 339 |
+
border: none;
|
| 340 |
+
border-radius: 20px;
|
| 341 |
+
display: flex;
|
| 342 |
+
align-items: center;
|
| 343 |
+
justify-content: center;
|
| 344 |
+
cursor: pointer;
|
| 345 |
+
transition: all 0.3s ease;
|
| 346 |
+
font-size: 0.9rem;
|
| 347 |
+
color: #0b57d0;
|
| 348 |
+
font-weight: 500;
|
| 349 |
+
padding: 10px 20px;
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
.file-uploader button:hover {
|
| 353 |
+
background-color: #edf1fa;
|
| 354 |
+
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2);
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
/* File Uploader End */
|
| 358 |
+
|
| 359 |
+
.sidebar-menu {
|
| 360 |
+
margin-top: 20px;
|
| 361 |
+
width: 100%;
|
| 362 |
+
display: flex;
|
| 363 |
+
flex-direction: column;
|
| 364 |
+
justify-content: start;
|
| 365 |
+
align-items: center;
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
+
.sidebar-menu a {
|
| 369 |
+
width: 100%;
|
| 370 |
+
padding: 10px 20px;
|
| 371 |
+
display: flex;
|
| 372 |
+
align-items: center;
|
| 373 |
+
justify-content: start;
|
| 374 |
+
color: #444746;
|
| 375 |
+
font-size: 0.9rem;
|
| 376 |
+
font-weight: 400;
|
| 377 |
+
text-decoration: none;
|
| 378 |
+
transition: all 0.3s ease;
|
| 379 |
+
border-radius: 20px;
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
.sidebar-menu .selected-item {
|
| 383 |
+
background-color: #c2e7ff;
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
.sidebar-menu .unselected-item:hover {
|
| 387 |
+
background-color: #cfcfcf;
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
.sidebar-menu a img {
|
| 391 |
+
width: 20px;
|
| 392 |
+
height: 20px;
|
| 393 |
+
margin-right: 10px;
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
/* Sidebar Style End */
|
| 397 |
+
|
| 398 |
+
/* Main Content Style Start */
|
| 399 |
+
|
| 400 |
+
.main-content {
|
| 401 |
+
width: 100%;
|
| 402 |
+
height: 100%;
|
| 403 |
+
padding: 20px;
|
| 404 |
+
display: flex;
|
| 405 |
+
flex-direction: column;
|
| 406 |
+
justify-content: start;
|
| 407 |
+
align-items: start;
|
| 408 |
+
background-color: #f8fafd;
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
.main-content .header {
|
| 412 |
+
width: 100%;
|
| 413 |
+
display: grid;
|
| 414 |
+
grid-template-columns: 1fr auto;
|
| 415 |
+
align-items: center;
|
| 416 |
+
gap: 40px;
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
#search-form {
|
| 420 |
+
height: 100%;
|
| 421 |
+
width: 100%;
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
.search-bar {
|
| 425 |
+
width: 100%;
|
| 426 |
+
display: flex;
|
| 427 |
+
align-items: center;
|
| 428 |
+
justify-content: start;
|
| 429 |
+
background-color: #e9eef6;
|
| 430 |
+
border-radius: 20px;
|
| 431 |
+
padding: 10px 20px;
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
.search-bar img {
|
| 435 |
+
width: 20px;
|
| 436 |
+
height: 20px;
|
| 437 |
+
margin-right: 20px;
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
.search-bar input {
|
| 441 |
+
width: 100%;
|
| 442 |
+
border: none;
|
| 443 |
+
outline: none;
|
| 444 |
+
background-color: transparent;
|
| 445 |
+
font-size: 1rem;
|
| 446 |
+
font-weight: 400;
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
.search-bar:focus-within {
|
| 450 |
+
background-color: #ffffff;
|
| 451 |
+
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
.directory {
|
| 455 |
+
background-color: #fff;
|
| 456 |
+
padding: 10px 20px;
|
| 457 |
+
width: 100%;
|
| 458 |
+
height: 100%;
|
| 459 |
+
border-radius: 20px;
|
| 460 |
+
margin-top: 20px;
|
| 461 |
+
}
|
| 462 |
+
|
| 463 |
+
.directory table {
|
| 464 |
+
width: 100%;
|
| 465 |
+
border-collapse: collapse;
|
| 466 |
+
}
|
| 467 |
+
|
| 468 |
+
.directory table tr th {
|
| 469 |
+
text-align: start;
|
| 470 |
+
font-size: 0.9rem;
|
| 471 |
+
font-weight: 500;
|
| 472 |
+
color: #444746;
|
| 473 |
+
padding: 10px 0px;
|
| 474 |
+
border-bottom: 1px solid #e0e0e0;
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
.directory table tr td {
|
| 478 |
+
text-align: start;
|
| 479 |
+
font-size: 0.9rem;
|
| 480 |
+
font-weight: 400;
|
| 481 |
+
color: #444746;
|
| 482 |
+
border-bottom: 1px solid #e0e0e0;
|
| 483 |
+
height: 50px;
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
.directory .body-tr {
|
| 487 |
+
transition: all 0.3s ease;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
.directory .body-tr:hover {
|
| 491 |
+
background-color: #f1f1f1;
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
.directory .td-align {
|
| 495 |
+
display: flex;
|
| 496 |
+
align-items: center;
|
| 497 |
+
justify-content: start;
|
| 498 |
+
width: 100%;
|
| 499 |
+
height: 100%;
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
.directory .td-align img {
|
| 503 |
+
width: 24px;
|
| 504 |
+
height: 24px;
|
| 505 |
+
margin-right: 10px;
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
.directory .more-btn img {
|
| 509 |
+
width: 14px;
|
| 510 |
+
height: 14px;
|
| 511 |
+
margin: 0px;
|
| 512 |
+
}
|
| 513 |
+
|
| 514 |
+
.directory .more-btn {
|
| 515 |
+
padding: 8px;
|
| 516 |
+
border-radius: 20px;
|
| 517 |
+
display: flex;
|
| 518 |
+
align-items: center;
|
| 519 |
+
justify-content: center;
|
| 520 |
+
cursor: pointer;
|
| 521 |
+
transition: all 0.3s ease;
|
| 522 |
+
}
|
| 523 |
+
|
| 524 |
+
.directory .more-btn:hover {
|
| 525 |
+
background-color: #b9b9b9;
|
| 526 |
+
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);
|
| 527 |
+
}
|
website/static/js/apiHandler.js
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Api Fuctions
|
| 2 |
+
async function postJson(url, data) {
|
| 3 |
+
data['password'] = getPassword()
|
| 4 |
+
const response = await fetch(url, {
|
| 5 |
+
method: 'POST',
|
| 6 |
+
headers: {
|
| 7 |
+
'Content-Type': 'application/json'
|
| 8 |
+
},
|
| 9 |
+
body: JSON.stringify(data)
|
| 10 |
+
})
|
| 11 |
+
return await response.json()
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
document.getElementById('pass-login').addEventListener('click', async () => {
|
| 15 |
+
const password = document.getElementById('auth-pass').value
|
| 16 |
+
const data = { 'pass': password }
|
| 17 |
+
const json = await postJson('/api/checkPassword', data)
|
| 18 |
+
if (json.status === 'ok') {
|
| 19 |
+
localStorage.setItem('password', password)
|
| 20 |
+
alert('Logged In Successfully')
|
| 21 |
+
window.location.reload()
|
| 22 |
+
}
|
| 23 |
+
else {
|
| 24 |
+
alert('Wrong Password')
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
})
|
| 28 |
+
|
| 29 |
+
async function getCurrentDirectory() {
|
| 30 |
+
let path = getCurrentPath()
|
| 31 |
+
if (path === 'redirect') {
|
| 32 |
+
return
|
| 33 |
+
}
|
| 34 |
+
try {
|
| 35 |
+
const auth = getFolderAuthFromPath()
|
| 36 |
+
console.log(path)
|
| 37 |
+
|
| 38 |
+
const data = { 'path': path, 'auth': auth }
|
| 39 |
+
const json = await postJson('/api/getDirectory', data)
|
| 40 |
+
|
| 41 |
+
if (json.status === 'ok') {
|
| 42 |
+
if (getCurrentPath().startsWith('/share')) {
|
| 43 |
+
const sections = document.querySelector('.sidebar-menu').getElementsByTagName('a')
|
| 44 |
+
console.log(path)
|
| 45 |
+
|
| 46 |
+
if (removeSlash(json['auth_home_path']) === removeSlash(path.split('_')[1])) {
|
| 47 |
+
sections[0].setAttribute('class', 'selected-item')
|
| 48 |
+
|
| 49 |
+
} else {
|
| 50 |
+
sections[0].setAttribute('class', 'unselected-item')
|
| 51 |
+
}
|
| 52 |
+
sections[0].href = `/?path=/share_${removeSlash(json['auth_home_path'])}&auth=${auth}`
|
| 53 |
+
console.log(`/?path=/share_${removeSlash(json['auth_home_path'])}&auth=${auth}`)
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
console.log(json)
|
| 57 |
+
showDirectory(json['data'])
|
| 58 |
+
} else {
|
| 59 |
+
alert('404 Current Directory Not Found')
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
catch (err) {
|
| 63 |
+
console.log(err)
|
| 64 |
+
alert('404 Current Directory Not Found')
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
async function createNewFolder() {
|
| 69 |
+
const folderName = document.getElementById('new-folder-name').value;
|
| 70 |
+
const path = getCurrentPath()
|
| 71 |
+
if (path === 'redirect') {
|
| 72 |
+
return
|
| 73 |
+
}
|
| 74 |
+
if (folderName.length > 0) {
|
| 75 |
+
const data = {
|
| 76 |
+
'name': folderName,
|
| 77 |
+
'path': path
|
| 78 |
+
}
|
| 79 |
+
try {
|
| 80 |
+
const json = await postJson('/api/createNewFolder', data)
|
| 81 |
+
|
| 82 |
+
if (json.status === 'ok') {
|
| 83 |
+
window.location.reload();
|
| 84 |
+
} else {
|
| 85 |
+
alert(json.status)
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
catch (err) {
|
| 89 |
+
alert('Error Creating Folder')
|
| 90 |
+
}
|
| 91 |
+
} else {
|
| 92 |
+
alert('Folder Name Cannot Be Empty')
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
async function getFolderShareAuth(path) {
|
| 98 |
+
const data = { 'path': path }
|
| 99 |
+
const json = await postJson('/api/getFolderShareAuth', data)
|
| 100 |
+
if (json.status === 'ok') {
|
| 101 |
+
return json.auth
|
| 102 |
+
} else {
|
| 103 |
+
alert('Error Getting Folder Share Auth')
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
// File Uploader Start
|
| 108 |
+
|
| 109 |
+
const MAX_FILE_SIZE = MAX_FILE_SIZE__SDGJDG // Will be replaced by the python
|
| 110 |
+
|
| 111 |
+
const fileInput = document.getElementById('fileInput');
|
| 112 |
+
const progressBar = document.getElementById('progress-bar');
|
| 113 |
+
const cancelButton = document.getElementById('cancel-file-upload');
|
| 114 |
+
const uploadPercent = document.getElementById('upload-percent');
|
| 115 |
+
let uploadRequest = null;
|
| 116 |
+
let uploadStep = 0;
|
| 117 |
+
let uploadID = null;
|
| 118 |
+
|
| 119 |
+
fileInput.addEventListener('change', async (e) => {
|
| 120 |
+
const file = fileInput.files[0];
|
| 121 |
+
|
| 122 |
+
if (file.size > MAX_FILE_SIZE) {
|
| 123 |
+
alert(`File size exceeds ${(MAX_FILE_SIZE / (1024 * 1024 * 1024)).toFixed(2)} GB limit`);
|
| 124 |
+
return;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
// Showing file uploader
|
| 128 |
+
document.getElementById('bg-blur').style.zIndex = '2';
|
| 129 |
+
document.getElementById('bg-blur').style.opacity = '0.1';
|
| 130 |
+
document.getElementById('file-uploader').style.zIndex = '3';
|
| 131 |
+
document.getElementById('file-uploader').style.opacity = '1';
|
| 132 |
+
|
| 133 |
+
document.getElementById('upload-filename').innerText = 'Filename: ' + file.name;
|
| 134 |
+
document.getElementById('upload-filesize').innerText = 'Filesize: ' + (file.size / (1024 * 1024)).toFixed(2) + ' MB';
|
| 135 |
+
document.getElementById('upload-status').innerText = 'Status: Uploading To Backend Server';
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
const formData = new FormData();
|
| 139 |
+
formData.append('file', file);
|
| 140 |
+
formData.append('path', getCurrentPath());
|
| 141 |
+
formData.append('password', getPassword());
|
| 142 |
+
const id = getRandomId();
|
| 143 |
+
formData.append('id', id);
|
| 144 |
+
formData.append('total_size', file.size);
|
| 145 |
+
|
| 146 |
+
uploadStep = 1;
|
| 147 |
+
uploadRequest = new XMLHttpRequest();
|
| 148 |
+
uploadRequest.open('POST', '/api/upload', true);
|
| 149 |
+
|
| 150 |
+
uploadRequest.upload.addEventListener('progress', (e) => {
|
| 151 |
+
if (e.lengthComputable) {
|
| 152 |
+
const percentComplete = (e.loaded / e.total) * 100;
|
| 153 |
+
progressBar.style.width = percentComplete + '%';
|
| 154 |
+
uploadPercent.innerText = 'Progress : ' + percentComplete.toFixed(2) + '%';
|
| 155 |
+
}
|
| 156 |
+
});
|
| 157 |
+
|
| 158 |
+
uploadRequest.upload.addEventListener('load', async () => {
|
| 159 |
+
await updateSaveProgress(id)
|
| 160 |
+
});
|
| 161 |
+
|
| 162 |
+
uploadRequest.upload.addEventListener('error', () => {
|
| 163 |
+
alert('Upload failed');
|
| 164 |
+
window.location.reload();
|
| 165 |
+
});
|
| 166 |
+
|
| 167 |
+
uploadRequest.send(formData);
|
| 168 |
+
});
|
| 169 |
+
|
| 170 |
+
cancelButton.addEventListener('click', () => {
|
| 171 |
+
if (uploadStep === 1) {
|
| 172 |
+
uploadRequest.abort();
|
| 173 |
+
} else if (uploadStep === 2) {
|
| 174 |
+
const data = { 'id': uploadID }
|
| 175 |
+
postJson('/api/cancelUpload', data)
|
| 176 |
+
}
|
| 177 |
+
alert('Upload canceled');
|
| 178 |
+
window.location.reload();
|
| 179 |
+
});
|
| 180 |
+
|
| 181 |
+
async function updateSaveProgress(id) {
|
| 182 |
+
console.log('save progress')
|
| 183 |
+
progressBar.style.width = '0%';
|
| 184 |
+
uploadPercent.innerText = 'Progress : 0%'
|
| 185 |
+
document.getElementById('upload-status').innerText = 'Status: Processing File On Backend Server';
|
| 186 |
+
|
| 187 |
+
const interval = setInterval(async () => {
|
| 188 |
+
const response = await postJson('/api/getSaveProgress', { 'id': id })
|
| 189 |
+
const data = response['data']
|
| 190 |
+
|
| 191 |
+
if (data[0] === 'running') {
|
| 192 |
+
const current = data[1];
|
| 193 |
+
const total = data[2];
|
| 194 |
+
document.getElementById('upload-filesize').innerText = 'Filesize: ' + (total / (1024 * 1024)).toFixed(2) + ' MB';
|
| 195 |
+
|
| 196 |
+
const percentComplete = (current / total) * 100;
|
| 197 |
+
progressBar.style.width = percentComplete + '%';
|
| 198 |
+
uploadPercent.innerText = 'Progress : ' + percentComplete.toFixed(2) + '%';
|
| 199 |
+
}
|
| 200 |
+
else if (data[0] === 'completed') {
|
| 201 |
+
clearInterval(interval);
|
| 202 |
+
uploadPercent.innerText = 'Progress : 100%'
|
| 203 |
+
progressBar.style.width = '100%';
|
| 204 |
+
|
| 205 |
+
await handleUpload2(id)
|
| 206 |
+
}
|
| 207 |
+
}, 3000)
|
| 208 |
+
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
async function handleUpload2(id) {
|
| 212 |
+
console.log(id)
|
| 213 |
+
document.getElementById('upload-status').innerText = 'Status: Uploading To Telegram Server';
|
| 214 |
+
progressBar.style.width = '0%';
|
| 215 |
+
uploadPercent.innerText = 'Progress : 0%';
|
| 216 |
+
|
| 217 |
+
const interval = setInterval(async () => {
|
| 218 |
+
const response = await postJson('/api/getUploadProgress', { 'id': id })
|
| 219 |
+
const data = response['data']
|
| 220 |
+
|
| 221 |
+
if (data[0] === 'running') {
|
| 222 |
+
const current = data[1];
|
| 223 |
+
const total = data[2];
|
| 224 |
+
document.getElementById('upload-filesize').innerText = 'Filesize: ' + (total / (1024 * 1024)).toFixed(2) + ' MB';
|
| 225 |
+
|
| 226 |
+
let percentComplete
|
| 227 |
+
if (total === 0) {
|
| 228 |
+
percentComplete = 0
|
| 229 |
+
}
|
| 230 |
+
else {
|
| 231 |
+
percentComplete = (current / total) * 100;
|
| 232 |
+
}
|
| 233 |
+
progressBar.style.width = percentComplete + '%';
|
| 234 |
+
uploadPercent.innerText = 'Progress : ' + percentComplete.toFixed(2) + '%';
|
| 235 |
+
}
|
| 236 |
+
else if (data[0] === 'completed') {
|
| 237 |
+
clearInterval(interval);
|
| 238 |
+
alert('Upload Completed')
|
| 239 |
+
window.location.reload();
|
| 240 |
+
}
|
| 241 |
+
}, 3000)
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
// File Uploader End
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
// URL Uploader Start
|
| 248 |
+
|
| 249 |
+
async function get_file_info_from_url(url) {
|
| 250 |
+
const data = { 'url': url }
|
| 251 |
+
const json = await postJson('/api/getFileInfoFromUrl', data)
|
| 252 |
+
if (json.status === 'ok') {
|
| 253 |
+
return json.data
|
| 254 |
+
} else {
|
| 255 |
+
throw new Error(`Error Getting File Info : ${json.status}`)
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
async function start_file_download_from_url(url, filename, singleThreaded) {
|
| 261 |
+
const data = { 'url': url, 'path': getCurrentPath(), 'filename': filename, 'singleThreaded': singleThreaded }
|
| 262 |
+
const json = await postJson('/api/startFileDownloadFromUrl', data)
|
| 263 |
+
if (json.status === 'ok') {
|
| 264 |
+
return json.id
|
| 265 |
+
} else {
|
| 266 |
+
throw new Error(`Error Starting File Download : ${json.status}`)
|
| 267 |
+
}
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
async function download_progress_updater(id, file_name, file_size) {
|
| 271 |
+
uploadID = id;
|
| 272 |
+
uploadStep = 2
|
| 273 |
+
// Showing file uploader
|
| 274 |
+
document.getElementById('bg-blur').style.zIndex = '2';
|
| 275 |
+
document.getElementById('bg-blur').style.opacity = '0.1';
|
| 276 |
+
document.getElementById('file-uploader').style.zIndex = '3';
|
| 277 |
+
document.getElementById('file-uploader').style.opacity = '1';
|
| 278 |
+
|
| 279 |
+
document.getElementById('upload-filename').innerText = 'Filename: ' + file_name;
|
| 280 |
+
document.getElementById('upload-filesize').innerText = 'Filesize: ' + (file_size / (1024 * 1024)).toFixed(2) + ' MB';
|
| 281 |
+
|
| 282 |
+
const interval = setInterval(async () => {
|
| 283 |
+
const response = await postJson('/api/getFileDownloadProgress', { 'id': id })
|
| 284 |
+
const data = response['data']
|
| 285 |
+
|
| 286 |
+
if (data[0] === 'error') {
|
| 287 |
+
clearInterval(interval);
|
| 288 |
+
alert('Failed To Download File From URL To Backend Server')
|
| 289 |
+
window.location.reload()
|
| 290 |
+
}
|
| 291 |
+
else if (data[0] === 'completed') {
|
| 292 |
+
clearInterval(interval);
|
| 293 |
+
uploadPercent.innerText = 'Progress : 100%'
|
| 294 |
+
progressBar.style.width = '100%';
|
| 295 |
+
await handleUpload2(id)
|
| 296 |
+
}
|
| 297 |
+
else {
|
| 298 |
+
const current = data[1];
|
| 299 |
+
const total = data[2];
|
| 300 |
+
|
| 301 |
+
const percentComplete = (current / total) * 100;
|
| 302 |
+
progressBar.style.width = percentComplete + '%';
|
| 303 |
+
uploadPercent.innerText = 'Progress : ' + percentComplete.toFixed(2) + '%';
|
| 304 |
+
|
| 305 |
+
if (data[0] === 'Downloading') {
|
| 306 |
+
document.getElementById('upload-status').innerText = 'Status: Downloading File From Url To Backend Server';
|
| 307 |
+
}
|
| 308 |
+
else {
|
| 309 |
+
document.getElementById('upload-status').innerText = `Status: ${data[0]}`;
|
| 310 |
+
}
|
| 311 |
+
}
|
| 312 |
+
}, 3000)
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
async function Start_URL_Upload() {
|
| 317 |
+
try {
|
| 318 |
+
document.getElementById('new-url-upload').style.opacity = '0';
|
| 319 |
+
setTimeout(() => {
|
| 320 |
+
document.getElementById('new-url-upload').style.zIndex = '-1';
|
| 321 |
+
}, 300)
|
| 322 |
+
|
| 323 |
+
const file_url = document.getElementById('remote-url').value
|
| 324 |
+
const singleThreaded = document.getElementById('single-threaded-toggle').checked
|
| 325 |
+
|
| 326 |
+
const file_info = await get_file_info_from_url(file_url)
|
| 327 |
+
const file_name = file_info.file_name
|
| 328 |
+
const file_size = file_info.file_size
|
| 329 |
+
|
| 330 |
+
if (file_size > MAX_FILE_SIZE) {
|
| 331 |
+
throw new Error(`File size exceeds ${(MAX_FILE_SIZE / (1024 * 1024 * 1024)).toFixed(2)} GB limit`)
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
const id = await start_file_download_from_url(file_url, file_name, singleThreaded)
|
| 335 |
+
|
| 336 |
+
await download_progress_updater(id, file_name, file_size)
|
| 337 |
+
|
| 338 |
+
}
|
| 339 |
+
catch (err) {
|
| 340 |
+
alert(err)
|
| 341 |
+
window.location.reload()
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
// URL Uploader End
|
website/static/js/extra.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function getCurrentPath() {
|
| 2 |
+
const url = new URL(window.location.href);
|
| 3 |
+
const path = url.searchParams.get('path')
|
| 4 |
+
if (path === null) {
|
| 5 |
+
window.location.href = '/?path=/'
|
| 6 |
+
return 'redirect'
|
| 7 |
+
}
|
| 8 |
+
return path
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
function getFolderAuthFromPath() {
|
| 12 |
+
const url = new URL(window.location.href);
|
| 13 |
+
const auth = url.searchParams.get('auth')
|
| 14 |
+
return auth
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
// Changing sidebar section class
|
| 18 |
+
if (getCurrentPath() !== '/') {
|
| 19 |
+
const sections = document.querySelector('.sidebar-menu').getElementsByTagName('a')
|
| 20 |
+
sections[0].setAttribute('class', 'unselected-item')
|
| 21 |
+
|
| 22 |
+
if (getCurrentPath().includes('/trash')) {
|
| 23 |
+
sections[1].setAttribute('class', 'selected-item')
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
function convertBytes(bytes) {
|
| 28 |
+
const kilobyte = 1024;
|
| 29 |
+
const megabyte = kilobyte * 1024;
|
| 30 |
+
const gigabyte = megabyte * 1024;
|
| 31 |
+
|
| 32 |
+
if (bytes >= gigabyte) {
|
| 33 |
+
return (bytes / gigabyte).toFixed(2) + ' GB';
|
| 34 |
+
} else if (bytes >= megabyte) {
|
| 35 |
+
return (bytes / megabyte).toFixed(2) + ' MB';
|
| 36 |
+
} else if (bytes >= kilobyte) {
|
| 37 |
+
return (bytes / kilobyte).toFixed(2) + ' KB';
|
| 38 |
+
} else {
|
| 39 |
+
return bytes + ' bytes';
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
const INPUTS = {}
|
| 44 |
+
|
| 45 |
+
function validateInput(event) {
|
| 46 |
+
console.log('Validating Input')
|
| 47 |
+
const pattern = /^[a-zA-Z0-9 \-_\\[\]()@#!$%*+={}:;<>,.?/|\\~`]*$/;;
|
| 48 |
+
const input = event.target;
|
| 49 |
+
if (!pattern.test(input.value)) {
|
| 50 |
+
input.value = INPUTS[input.id]
|
| 51 |
+
} else {
|
| 52 |
+
INPUTS[input.id] = input.value
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
function getRootUrl() {
|
| 57 |
+
const url = new URL(window.location.href);
|
| 58 |
+
const protocol = url.protocol; // Get the protocol, e.g., "https:"
|
| 59 |
+
const hostname = url.hostname; // Get the hostname, e.g., "sub.example.com" or "192.168.1.1"
|
| 60 |
+
const port = url.port; // Get the port, e.g., "8080"
|
| 61 |
+
|
| 62 |
+
const rootUrl = `${protocol}//${hostname}${port ? ':' + port : ''}`;
|
| 63 |
+
|
| 64 |
+
return rootUrl;
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
function copyTextToClipboard(text) {
|
| 68 |
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
| 69 |
+
navigator.clipboard.writeText(text).then(function () {
|
| 70 |
+
alert('Link copied to clipboard!');
|
| 71 |
+
}).catch(function (err) {
|
| 72 |
+
console.error('Could not copy text: ', err);
|
| 73 |
+
fallbackCopyTextToClipboard(text);
|
| 74 |
+
});
|
| 75 |
+
} else {
|
| 76 |
+
fallbackCopyTextToClipboard(text);
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
function fallbackCopyTextToClipboard(text) {
|
| 81 |
+
const textArea = document.createElement('textarea');
|
| 82 |
+
textArea.value = text;
|
| 83 |
+
document.body.appendChild(textArea);
|
| 84 |
+
textArea.focus();
|
| 85 |
+
textArea.select();
|
| 86 |
+
|
| 87 |
+
try {
|
| 88 |
+
const successful = document.execCommand('copy');
|
| 89 |
+
if (successful) {
|
| 90 |
+
alert('Link copied to clipboard!');
|
| 91 |
+
} else {
|
| 92 |
+
alert('Failed to copy the link.');
|
| 93 |
+
}
|
| 94 |
+
} catch (err) {
|
| 95 |
+
console.error('Fallback: Oops, unable to copy', err);
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
document.body.removeChild(textArea);
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
function getPassword() {
|
| 102 |
+
return localStorage.getItem('password')
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
function getRandomId() {
|
| 106 |
+
const length = 6;
|
| 107 |
+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
| 108 |
+
let result = '';
|
| 109 |
+
for (let i = 0; i < length; i++) {
|
| 110 |
+
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
| 111 |
+
}
|
| 112 |
+
return result;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
function removeSlash(text) {
|
| 116 |
+
let charactersToRemove = "[/]+"; // Define the characters to remove inside square brackets
|
| 117 |
+
let trimmedStr = text.replace(new RegExp(`^${charactersToRemove}|${charactersToRemove}$`, 'g'), '');
|
| 118 |
+
return trimmedStr;
|
| 119 |
+
}
|
website/static/js/fileClickHandler.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function openFolder() {
|
| 2 |
+
let path = (getCurrentPath() + '/' + this.getAttribute('data-id') + '/').replaceAll('//', '/')
|
| 3 |
+
|
| 4 |
+
const auth = getFolderAuthFromPath()
|
| 5 |
+
if (auth) {
|
| 6 |
+
path = path + '&auth=' + auth
|
| 7 |
+
}
|
| 8 |
+
window.location.href = `/?path=${path}`
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
function openFile() {
|
| 12 |
+
const fileName = this.getAttribute('data-name').toLowerCase()
|
| 13 |
+
let path = '/file?path=' + this.getAttribute('data-path') + '/' + this.getAttribute('data-id')
|
| 14 |
+
|
| 15 |
+
if (fileName.endsWith('.mp4') || fileName.endsWith('.mkv') || fileName.endsWith('.webm') || fileName.endsWith('.mov') || fileName.endsWith('.avi') || fileName.endsWith('.ts') || fileName.endsWith('.ogv')) {
|
| 16 |
+
path = '/stream?url=' + getRootUrl() + path
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
window.open(path, '_blank')
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
// File More Button Handler Start
|
| 24 |
+
|
| 25 |
+
function openMoreButton(div) {
|
| 26 |
+
const id = div.getAttribute('data-id')
|
| 27 |
+
const moreDiv = document.getElementById(`more-option-${id}`)
|
| 28 |
+
|
| 29 |
+
const rect = div.getBoundingClientRect();
|
| 30 |
+
const x = rect.left + window.scrollX - 40;
|
| 31 |
+
const y = rect.top + window.scrollY;
|
| 32 |
+
|
| 33 |
+
moreDiv.style.zIndex = 2
|
| 34 |
+
moreDiv.style.opacity = 1
|
| 35 |
+
moreDiv.style.left = `${x}px`
|
| 36 |
+
moreDiv.style.top = `${y}px`
|
| 37 |
+
|
| 38 |
+
const isTrash = getCurrentPath().includes('/trash')
|
| 39 |
+
|
| 40 |
+
moreDiv.querySelector('.more-options-focus').focus()
|
| 41 |
+
moreDiv.querySelector('.more-options-focus').addEventListener('blur', closeMoreBtnFocus);
|
| 42 |
+
moreDiv.querySelector('.more-options-focus').addEventListener('focusout', closeMoreBtnFocus);
|
| 43 |
+
if (!isTrash) {
|
| 44 |
+
moreDiv.querySelector(`#rename-${id}`).addEventListener('click', renameFileFolder)
|
| 45 |
+
moreDiv.querySelector(`#trash-${id}`).addEventListener('click', trashFileFolder)
|
| 46 |
+
try {
|
| 47 |
+
moreDiv.querySelector(`#share-${id}`).addEventListener('click', shareFile)
|
| 48 |
+
}
|
| 49 |
+
catch { }
|
| 50 |
+
try {
|
| 51 |
+
moreDiv.querySelector(`#folder-share-${id}`).addEventListener('click', shareFolder)
|
| 52 |
+
}
|
| 53 |
+
catch { }
|
| 54 |
+
}
|
| 55 |
+
else {
|
| 56 |
+
moreDiv.querySelector(`#restore-${id}`).addEventListener('click', restoreFileFolder)
|
| 57 |
+
moreDiv.querySelector(`#delete-${id}`).addEventListener('click', deleteFileFolder)
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
function closeMoreBtnFocus() {
|
| 62 |
+
const moreDiv = this.parentElement
|
| 63 |
+
moreDiv.style.opacity = '0'
|
| 64 |
+
setTimeout(() => {
|
| 65 |
+
moreDiv.style.zIndex = '-1'
|
| 66 |
+
}, 300)
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
// Rename File Folder Start
|
| 70 |
+
function renameFileFolder() {
|
| 71 |
+
const id = this.getAttribute('id').split('-')[1]
|
| 72 |
+
console.log(id)
|
| 73 |
+
|
| 74 |
+
document.getElementById('rename-name').value = this.parentElement.getAttribute('data-name');
|
| 75 |
+
document.getElementById('bg-blur').style.zIndex = '2';
|
| 76 |
+
document.getElementById('bg-blur').style.opacity = '0.1';
|
| 77 |
+
|
| 78 |
+
document.getElementById('rename-file-folder').style.zIndex = '3';
|
| 79 |
+
document.getElementById('rename-file-folder').style.opacity = '1';
|
| 80 |
+
document.getElementById('rename-file-folder').setAttribute('data-id', id);
|
| 81 |
+
setTimeout(() => {
|
| 82 |
+
document.getElementById('rename-name').focus();
|
| 83 |
+
}, 300)
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
document.getElementById('rename-cancel').addEventListener('click', () => {
|
| 87 |
+
document.getElementById('rename-name').value = '';
|
| 88 |
+
document.getElementById('bg-blur').style.opacity = '0';
|
| 89 |
+
setTimeout(() => {
|
| 90 |
+
document.getElementById('bg-blur').style.zIndex = '-1';
|
| 91 |
+
}, 300)
|
| 92 |
+
document.getElementById('rename-file-folder').style.opacity = '0';
|
| 93 |
+
setTimeout(() => {
|
| 94 |
+
document.getElementById('rename-file-folder').style.zIndex = '-1';
|
| 95 |
+
}, 300)
|
| 96 |
+
});
|
| 97 |
+
|
| 98 |
+
document.getElementById('rename-create').addEventListener('click', async () => {
|
| 99 |
+
const name = document.getElementById('rename-name').value;
|
| 100 |
+
if (name === '') {
|
| 101 |
+
alert('Name cannot be empty')
|
| 102 |
+
return
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
const id = document.getElementById('rename-file-folder').getAttribute('data-id')
|
| 106 |
+
|
| 107 |
+
const path = document.getElementById(`more-option-${id}`).getAttribute('data-path') + '/' + id
|
| 108 |
+
|
| 109 |
+
const data = {
|
| 110 |
+
'name': name,
|
| 111 |
+
'path': path
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
const response = await postJson('/api/renameFileFolder', data)
|
| 115 |
+
if (response.status === 'ok') {
|
| 116 |
+
alert('File/Folder Renamed Successfully')
|
| 117 |
+
window.location.reload();
|
| 118 |
+
} else {
|
| 119 |
+
alert('Failed to rename file/folder')
|
| 120 |
+
window.location.reload();
|
| 121 |
+
}
|
| 122 |
+
});
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
// Rename File Folder End
|
| 126 |
+
|
| 127 |
+
async function trashFileFolder() {
|
| 128 |
+
const id = this.getAttribute('id').split('-')[1]
|
| 129 |
+
console.log(id)
|
| 130 |
+
const path = document.getElementById(`more-option-${id}`).getAttribute('data-path') + '/' + id
|
| 131 |
+
const data = {
|
| 132 |
+
'path': path,
|
| 133 |
+
'trash': true
|
| 134 |
+
}
|
| 135 |
+
const response = await postJson('/api/trashFileFolder', data)
|
| 136 |
+
|
| 137 |
+
if (response.status === 'ok') {
|
| 138 |
+
alert('File/Folder Sent to Trash Successfully')
|
| 139 |
+
window.location.reload();
|
| 140 |
+
} else {
|
| 141 |
+
alert('Failed to Send File/Folder to Trash')
|
| 142 |
+
window.location.reload();
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
async function restoreFileFolder() {
|
| 147 |
+
const id = this.getAttribute('id').split('-')[1]
|
| 148 |
+
const path = this.getAttribute('data-path') + '/' + id
|
| 149 |
+
const data = {
|
| 150 |
+
'path': path,
|
| 151 |
+
'trash': false
|
| 152 |
+
}
|
| 153 |
+
const response = await postJson('/api/trashFileFolder', data)
|
| 154 |
+
|
| 155 |
+
if (response.status === 'ok') {
|
| 156 |
+
alert('File/Folder Restored Successfully')
|
| 157 |
+
window.location.reload();
|
| 158 |
+
} else {
|
| 159 |
+
alert('Failed to Restored File/Folder')
|
| 160 |
+
window.location.reload();
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
async function deleteFileFolder() {
|
| 165 |
+
const id = this.getAttribute('id').split('-')[1]
|
| 166 |
+
const path = this.getAttribute('data-path') + '/' + id
|
| 167 |
+
const data = {
|
| 168 |
+
'path': path
|
| 169 |
+
}
|
| 170 |
+
const response = await postJson('/api/deleteFileFolder', data)
|
| 171 |
+
|
| 172 |
+
if (response.status === 'ok') {
|
| 173 |
+
alert('File/Folder Deleted Successfully')
|
| 174 |
+
window.location.reload();
|
| 175 |
+
} else {
|
| 176 |
+
alert('Failed to Delete File/Folder')
|
| 177 |
+
window.location.reload();
|
| 178 |
+
}
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
async function shareFile() {
|
| 182 |
+
const fileName = this.parentElement.getAttribute('data-name').toLowerCase()
|
| 183 |
+
const id = this.getAttribute('id').split('-')[1]
|
| 184 |
+
const path = document.getElementById(`more-option-${id}`).getAttribute('data-path') + '/' + id
|
| 185 |
+
const root_url = getRootUrl()
|
| 186 |
+
|
| 187 |
+
let link
|
| 188 |
+
if (fileName.endsWith('.mp4') || fileName.endsWith('.mkv') || fileName.endsWith('.webm') || fileName.endsWith('.mov') || fileName.endsWith('.avi') || fileName.endsWith('.ts') || fileName.endsWith('.ogv')) {
|
| 189 |
+
link = `${root_url}/stream?url=${root_url}/file?path=${path}`
|
| 190 |
+
} else {
|
| 191 |
+
link = `${root_url}/file?path=${path}`
|
| 192 |
+
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
copyTextToClipboard(link)
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
async function shareFolder() {
|
| 200 |
+
const id = this.getAttribute('id').split('-')[2]
|
| 201 |
+
console.log(id)
|
| 202 |
+
let path = document.getElementById(`more-option-${id}`).getAttribute('data-path') + '/' + id
|
| 203 |
+
const root_url = getRootUrl()
|
| 204 |
+
|
| 205 |
+
const auth = await getFolderShareAuth(path)
|
| 206 |
+
path = path.slice(1)
|
| 207 |
+
|
| 208 |
+
let link = `${root_url}/?path=/share_${path}&auth=${auth}`
|
| 209 |
+
console.log(link)
|
| 210 |
+
|
| 211 |
+
copyTextToClipboard(link)
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
// File More Button Handler End
|
website/static/js/main.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
function showDirectory(data) {
|
| 2 |
+
data = data['contents']
|
| 3 |
+
document.getElementById('directory-data').innerHTML = ''
|
| 4 |
+
const isTrash = getCurrentPath().startsWith('/trash')
|
| 5 |
+
|
| 6 |
+
let html = ''
|
| 7 |
+
|
| 8 |
+
// Step 2: Sort the array based on the 'date' values
|
| 9 |
+
let entries = Object.entries(data);
|
| 10 |
+
let folders = entries.filter(([key, value]) => value.type === 'folder');
|
| 11 |
+
let files = entries.filter(([key, value]) => value.type === 'file');
|
| 12 |
+
|
| 13 |
+
folders.sort((a, b) => new Date(b[1].upload_date) - new Date(a[1].upload_date));
|
| 14 |
+
files.sort((a, b) => new Date(b[1].upload_date) - new Date(a[1].upload_date));
|
| 15 |
+
|
| 16 |
+
for (const [key, item] of folders) {
|
| 17 |
+
if (item.type === 'folder') {
|
| 18 |
+
html += `<tr data-path="${item.path}" data-id="${item.id}" class="body-tr folder-tr"><td><div class="td-align"><img src="static/assets/folder-solid-icon.svg">${item.name}</div></td><td><div class="td-align"></div></td><td><div class="td-align"><a data-id="${item.id}" class="more-btn"><img src="static/assets/more-icon.svg" class="rotate-90"></a></div></td></tr>`
|
| 19 |
+
|
| 20 |
+
if (isTrash) {
|
| 21 |
+
html += `<div data-path="${item.path}" id="more-option-${item.id}" data-name="${item.name}" class="more-options"><input class="more-options-focus" readonly="readonly" style="height:0;width:0;border:none;position:absolute"><div id="restore-${item.id}" data-path="${item.path}"><img src="static/assets/load-icon.svg"> Restore</div><hr><div id="delete-${item.id}" data-path="${item.path}"><img src="static/assets/trash-icon.svg"> Delete</div></div>`
|
| 22 |
+
}
|
| 23 |
+
else {
|
| 24 |
+
html += `<div data-path="${item.path}" id="more-option-${item.id}" data-name="${item.name}" class="more-options"><input class="more-options-focus" readonly="readonly" style="height:0;width:0;border:none;position:absolute"><div id="rename-${item.id}"><img src="static/assets/pencil-icon.svg"> Rename</div><hr><div id="trash-${item.id}"><img src="static/assets/trash-icon.svg"> Trash</div><hr><div id="folder-share-${item.id}"><img src="static/assets/share-icon.svg"> Share</div></div>`
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
for (const [key, item] of files) {
|
| 30 |
+
if (item.type === 'file') {
|
| 31 |
+
const size = convertBytes(item.size)
|
| 32 |
+
html += `<tr data-path="${item.path}" data-id="${item.id}" data-name="${item.name}" class="body-tr file-tr"><td><div class="td-align"><img src="static/assets/file-icon.svg">${item.name}</div></td><td><div class="td-align">${size}</div></td><td><div class="td-align"><a data-id="${item.id}" class="more-btn"><img src="static/assets/more-icon.svg" class="rotate-90"></a></div></td></tr>`
|
| 33 |
+
|
| 34 |
+
if (isTrash) {
|
| 35 |
+
html += `<div data-path="${item.path}" id="more-option-${item.id}" data-name="${item.name}" class="more-options"><input class="more-options-focus" readonly="readonly" style="height:0;width:0;border:none;position:absolute"><div id="restore-${item.id}" data-path="${item.path}"><img src="static/assets/load-icon.svg"> Restore</div><hr><div id="delete-${item.id}" data-path="${item.path}"><img src="static/assets/trash-icon.svg"> Delete</div></div>`
|
| 36 |
+
}
|
| 37 |
+
else {
|
| 38 |
+
html += `<div data-path="${item.path}" id="more-option-${item.id}" data-name="${item.name}" class="more-options"><input class="more-options-focus" readonly="readonly" style="height:0;width:0;border:none;position:absolute"><div id="rename-${item.id}"><img src="static/assets/pencil-icon.svg"> Rename</div><hr><div id="trash-${item.id}"><img src="static/assets/trash-icon.svg"> Trash</div><hr><div id="share-${item.id}"><img src="static/assets/share-icon.svg"> Share</div></div>`
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
document.getElementById('directory-data').innerHTML = html
|
| 43 |
+
|
| 44 |
+
if (!isTrash) {
|
| 45 |
+
document.querySelectorAll('.folder-tr').forEach(div => {
|
| 46 |
+
div.ondblclick = openFolder;
|
| 47 |
+
});
|
| 48 |
+
document.querySelectorAll('.file-tr').forEach(div => {
|
| 49 |
+
div.ondblclick = openFile;
|
| 50 |
+
});
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
document.querySelectorAll('.more-btn').forEach(div => {
|
| 54 |
+
div.addEventListener('click', function (event) {
|
| 55 |
+
event.preventDefault();
|
| 56 |
+
openMoreButton(div)
|
| 57 |
+
});
|
| 58 |
+
});
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
document.getElementById('search-form').addEventListener('submit', async (event) => {
|
| 62 |
+
event.preventDefault();
|
| 63 |
+
const query = document.getElementById('file-search').value;
|
| 64 |
+
console.log(query)
|
| 65 |
+
if (query === '') {
|
| 66 |
+
alert('Search field is empty');
|
| 67 |
+
return;
|
| 68 |
+
}
|
| 69 |
+
const path = '/?path=/search_' + encodeURI(query);
|
| 70 |
+
console.log(path)
|
| 71 |
+
window.location = path;
|
| 72 |
+
});
|
| 73 |
+
|
| 74 |
+
// Loading Main Page
|
| 75 |
+
|
| 76 |
+
document.addEventListener('DOMContentLoaded', function () {
|
| 77 |
+
const inputs = ['new-folder-name', 'rename-name', 'file-search']
|
| 78 |
+
for (let i = 0; i < inputs.length; i++) {
|
| 79 |
+
document.getElementById(inputs[i]).addEventListener('input', validateInput);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
if (getCurrentPath().includes('/share_')) {
|
| 83 |
+
getCurrentDirectory()
|
| 84 |
+
} else {
|
| 85 |
+
if (getPassword() === null) {
|
| 86 |
+
document.getElementById('bg-blur').style.zIndex = '2';
|
| 87 |
+
document.getElementById('bg-blur').style.opacity = '0.1';
|
| 88 |
+
|
| 89 |
+
document.getElementById('get-password').style.zIndex = '3';
|
| 90 |
+
document.getElementById('get-password').style.opacity = '1';
|
| 91 |
+
} else {
|
| 92 |
+
getCurrentDirectory()
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
});
|
website/static/js/sidebar.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Handling New Button On Sidebar Click
|
| 2 |
+
const isTrash = getCurrentPath().startsWith('/trash')
|
| 3 |
+
const isSearch = getCurrentPath().startsWith('/search')
|
| 4 |
+
const isShare = getCurrentPath().startsWith('/share')
|
| 5 |
+
|
| 6 |
+
if (!isTrash && !isSearch) {
|
| 7 |
+
document.getElementById('new-button').addEventListener('click', () => {
|
| 8 |
+
document.getElementById('new-upload').style.zIndex = '1'
|
| 9 |
+
document.getElementById('new-upload').style.opacity = '1'
|
| 10 |
+
document.getElementById('new-upload').style.top = '80px'
|
| 11 |
+
document.getElementById('new-upload-focus').focus()
|
| 12 |
+
});
|
| 13 |
+
}
|
| 14 |
+
else {
|
| 15 |
+
document.getElementById('new-button').style.display = 'none'
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
if (isShare) {
|
| 19 |
+
document.getElementById('new-button').style.display = 'none'
|
| 20 |
+
const sections = document.querySelector('.sidebar-menu').getElementsByTagName('a')
|
| 21 |
+
sections[1].remove()
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
// New File Upload Start
|
| 25 |
+
|
| 26 |
+
function closeNewUploadFocus() {
|
| 27 |
+
setTimeout(() => {
|
| 28 |
+
document.getElementById('new-upload').style.opacity = '0'
|
| 29 |
+
document.getElementById('new-upload').style.top = '40px'
|
| 30 |
+
setTimeout(() => {
|
| 31 |
+
document.getElementById('new-upload').style.zIndex = '-1'
|
| 32 |
+
}, 300)
|
| 33 |
+
}, 200)
|
| 34 |
+
}
|
| 35 |
+
document.getElementById('new-upload-focus').addEventListener('blur', closeNewUploadFocus);
|
| 36 |
+
document.getElementById('new-upload-focus').addEventListener('focusout', closeNewUploadFocus);
|
| 37 |
+
|
| 38 |
+
document.getElementById('file-upload-btn').addEventListener('click', () => {
|
| 39 |
+
document.getElementById('fileInput').click()
|
| 40 |
+
});
|
| 41 |
+
|
| 42 |
+
// New File Upload End
|
| 43 |
+
|
| 44 |
+
// New Folder Start
|
| 45 |
+
|
| 46 |
+
document.getElementById('new-folder-btn').addEventListener('click', () => {
|
| 47 |
+
document.getElementById('new-folder-name').value = '';
|
| 48 |
+
document.getElementById('bg-blur').style.zIndex = '2';
|
| 49 |
+
document.getElementById('bg-blur').style.opacity = '0.1';
|
| 50 |
+
|
| 51 |
+
document.getElementById('create-new-folder').style.zIndex = '3';
|
| 52 |
+
document.getElementById('create-new-folder').style.opacity = '1';
|
| 53 |
+
setTimeout(() => {
|
| 54 |
+
document.getElementById('new-folder-name').focus();
|
| 55 |
+
}, 300)
|
| 56 |
+
})
|
| 57 |
+
|
| 58 |
+
document.getElementById('new-folder-cancel').addEventListener('click', () => {
|
| 59 |
+
document.getElementById('new-folder-name').value = '';
|
| 60 |
+
document.getElementById('bg-blur').style.opacity = '0';
|
| 61 |
+
setTimeout(() => {
|
| 62 |
+
document.getElementById('bg-blur').style.zIndex = '-1';
|
| 63 |
+
}, 300)
|
| 64 |
+
document.getElementById('create-new-folder').style.opacity = '0';
|
| 65 |
+
setTimeout(() => {
|
| 66 |
+
document.getElementById('create-new-folder').style.zIndex = '-1';
|
| 67 |
+
}, 300)
|
| 68 |
+
});
|
| 69 |
+
|
| 70 |
+
document.getElementById('new-folder-create').addEventListener('click', createNewFolder);
|
| 71 |
+
|
| 72 |
+
// New Folder End
|
| 73 |
+
|
| 74 |
+
// New Url Upload Start
|
| 75 |
+
|
| 76 |
+
document.getElementById('url-upload-btn').addEventListener('click', () => {
|
| 77 |
+
document.getElementById('remote-url').value = '';
|
| 78 |
+
document.getElementById('bg-blur').style.zIndex = '2';
|
| 79 |
+
document.getElementById('bg-blur').style.opacity = '0.1';
|
| 80 |
+
|
| 81 |
+
document.getElementById('new-url-upload').style.zIndex = '3';
|
| 82 |
+
document.getElementById('new-url-upload').style.opacity = '1';
|
| 83 |
+
setTimeout(() => {
|
| 84 |
+
document.getElementById('remote-url').focus();
|
| 85 |
+
}, 300)
|
| 86 |
+
})
|
| 87 |
+
|
| 88 |
+
document.getElementById('remote-cancel').addEventListener('click', () => {
|
| 89 |
+
document.getElementById('remote-url').value = '';
|
| 90 |
+
document.getElementById('bg-blur').style.opacity = '0';
|
| 91 |
+
setTimeout(() => {
|
| 92 |
+
document.getElementById('bg-blur').style.zIndex = '-1';
|
| 93 |
+
}, 300)
|
| 94 |
+
document.getElementById('new-url-upload').style.opacity = '0';
|
| 95 |
+
setTimeout(() => {
|
| 96 |
+
document.getElementById('new-url-upload').style.zIndex = '-1';
|
| 97 |
+
}, 300)
|
| 98 |
+
});
|
| 99 |
+
|
| 100 |
+
document.getElementById('remote-start').addEventListener('click', Start_URL_Upload);
|
| 101 |
+
|
| 102 |
+
// New Url Upload End
|