├── .github └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── .netrc ├── .pre-commit-config.yaml ├── Dockerfile ├── LICENSE ├── README.md ├── add_to_team_drive.py ├── aria.bat ├── aria.sh ├── bot ├── __init__.py ├── __main__.py ├── helper │ ├── __init__.py │ ├── ext_utils │ │ ├── __init__.py │ │ ├── bot_utils.py │ │ ├── db_handler.py │ │ ├── exceptions.py │ │ ├── fs_utils.py │ │ └── telegraph_helper.py │ ├── mirror_utils │ │ ├── __init__.py │ │ ├── download_utils │ │ │ ├── __init__.py │ │ │ ├── aria2_download.py │ │ │ ├── direct_link_generator.py │ │ │ ├── direct_link_generator_license.md │ │ │ ├── download_helper.py │ │ │ ├── mega_download.py │ │ │ ├── telegram_downloader.py │ │ │ └── youtube_dl_download_helper.py │ │ ├── status_utils │ │ │ ├── __init__.py │ │ │ ├── aria_download_status.py │ │ │ ├── clone_status.py │ │ │ ├── extract_status.py │ │ │ ├── gdownload_status.py │ │ │ ├── listeners.py │ │ │ ├── mega_status.py │ │ │ ├── split_status.py │ │ │ ├── status.py │ │ │ ├── tar_status.py │ │ │ ├── telegram_download_status.py │ │ │ ├── tg_upload_status.py │ │ │ ├── upload_status.py │ │ │ └── youtube_dl_download_status.py │ │ └── upload_utils │ │ │ ├── __init__.py │ │ │ ├── gdriveTools.py │ │ │ └── pyrogramEngine.py │ └── telegram_helper │ │ ├── __init__.py │ │ ├── bot_commands.py │ │ ├── button_build.py │ │ ├── filters.py │ │ └── message_utils.py └── modules │ ├── __init__.py │ ├── authorize.py │ ├── cancel_mirror.py │ ├── clone.py │ ├── count.py │ ├── delete.py │ ├── leech_settings.py │ ├── list.py │ ├── mirror.py │ ├── mirror_status.py │ ├── speedtest.py │ └── watch.py ├── captain-definition ├── config_sample.env ├── drive_folder ├── driveid.py ├── extract ├── gen_sa_accounts.py ├── generate_drive_token.py ├── heroku.yml ├── heroku_push.sh ├── pextract ├── requirements.txt └── start.sh /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Host On Heroku 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | 7 | build: 8 | 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | 13 | - uses: actions/checkout@v2 14 | 15 | - uses: akhileshns/heroku-deploy@v3.12.12 16 | 17 | with: 18 | 19 | region: "us" 20 | 21 | branch: "master" 22 | 23 | usedocker: true 24 | 25 | stack: "container" 26 | 27 | heroku_api_key: ${{secrets.HEROKU_API_KEY}} 28 | heroku_app_name: ${{secrets.HEROKU_APP_NAME}} 29 | heroku_email: ${{secrets.HEROKU_EMAIL}} 30 | 31 | docker_heroku_process_type: worker 32 | env: 33 | HD_CONFIG_FILE_URL: ${{secrets.CONFIG_FILE_URL}} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | config.env 2 | *auth_token.txt 3 | *.pyc 4 | downloads/* 5 | download/* 6 | data* 7 | .vscode 8 | .idea 9 | *.json 10 | *.pickle 11 | authorized_chats.txt 12 | log.txt 13 | accounts/* 14 | venv/ 15 | .vim/ 16 | drive_folder 17 | drive_folder 18 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/cmrudl"] 2 | path = vendor/cmrudl 3 | url = https://github.com/JrMasterModelBuilder/cmrudl.py.git 4 | -------------------------------------------------------------------------------- /.netrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshpreets63/Mirror-Bot/08cd0fc9ecb8460d964e8970cfa8a1268f6faade/.netrc -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.0.1 4 | hooks: 5 | - id: check-ast 6 | - id: check-merge-conflict 7 | - id: detect-private-key 8 | - id: end-of-file-fixer 9 | - id: mixed-line-ending 10 | - id: requirements-txt-fixer 11 | - id: trailing-whitespace 12 | - repo: https://github.com/asottile/pyupgrade 13 | rev: v2.26.0 14 | hooks: 15 | - id: pyupgrade 16 | args: [--py38-plus] 17 | - repo: https://github.com/myint/autoflake 18 | rev: v1.4 19 | hooks: 20 | - id: autoflake 21 | args: 22 | [ 23 | "--in-place", 24 | "--ignore-init-module-imports", 25 | "--remove-unused-variables", 26 | "--remove-all-unused-imports", 27 | "--expand-star-imports", 28 | ] 29 | - repo: https://github.com/pycqa/isort 30 | rev: 5.9.3 31 | hooks: 32 | - id: isort 33 | args: ["--profile", "black", "--filter-files"] 34 | - repo: "https://github.com/psf/black" 35 | rev: 21.9b0 36 | hooks: 37 | - id: black 38 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM harshpreets63/random:simple 2 | 3 | WORKDIR /usr/src/app 4 | COPY . . 5 | 6 | RUN set -ex \ 7 | && chmod 777 /usr/src/app \ 8 | && cp .netrc /root/.netrc \ 9 | && chmod 600 /usr/src/app/.netrc \ 10 | && cp extract pextract /usr/local/bin \ 11 | && chmod +x aria.sh /usr/local/bin/extract /usr/local/bin/pextract 12 | RUN pip3 install -U pyrogram 13 | CMD ["bash", "start.sh"] 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![HarshMirroRepo](https://telegra.ph/file/5d6de8adcfc1a7917c422.jpg)](https://t.me/HarshMirrorRepo) 2 | 3 | # Important - Read these points first 4 | 5 | - Original repo is https://github.com/lzzy12/python-aria-mirror-bot 6 | - I have collected some cool features from various repositories and merged them 7 | in one. 8 | - So, credits goes to original repo holder, not to me. I have just collected 9 | them. 10 | - This (or any custom) repo is not supported in official bot support group. 11 | - So if you have any issue then check first that issue is in official repo or 12 | not, You are only allowed to report that issue in bot support group if that 13 | issue is also present in official repo. 14 | 15 | # Features supported: 16 | 17 | - Mirroring direct download links to google drive 18 | - Mirroring Mega.nz links to google drive (In development stage) 19 | - Mirror Telegram files to google drive 20 | - Mirror all youtube-dl supported links 21 | - Mirror Torrent Files and magnet links 22 | - Mirror By Reply 23 | - Custom filename support in direct link, telegram files, YT-DL links 24 | - Extract these filetypes and uploads to google drive 25 | > ZIP, RAR, TAR, 7z, ISO, WIM, CAB, GZIP, BZIP2, APM, ARJ, CHM, CPIO, CramFS, 26 | > DEB, DMG, FAT, HFS, LZH, LZMA, LZMA2, MBR, MSI, MSLZ, NSIS, NTFS, RPM, 27 | > SquashFS, UDF, VHD, XAR, Z. 28 | - Copy files from someone's drive to your drive (using Autorclone) 29 | - Service account support in cloning and uploading 30 | - Download progress 31 | - Upload progress 32 | - Download/upload speeds and ETAs 33 | - Docker support 34 | - Uploading To Team Drives. 35 | - Index Link support 36 | - Shortener support 37 | - View Index Links To Steam Videos Or Music Online (Works With Bhadoo) 38 | - Leech Files To Telegram Supported. 39 | - Clone Status. 40 | - Multi Drive Search. 41 | - SpeedTest. 42 | - Count Drive Files. 43 | - Extract password protected files (It's not hack, you have to enter password 44 | for extracting. LOL) 45 | 46 | - For extracting password protected files, using custom filename and download 47 | from password protected index links see these examples :- 48 | > https://telegra.ph/Magneto-Python-Aria---Custom-Filename-Examples-01-20 49 | 50 | # Multi Search IDs 51 | To use list from multi TD/folder. Run driveid.py in your terminal and follow it. It will generate **drive_folder** file or u can simply create `drive_folder` file in working directory and fill it, check below format: 52 | ``` 53 | MyTdName tdID IndexLink(if available) 54 | MyTdName2 tdID IndexLink(if available) 55 | ``` 56 | Turn On RECURSIVE_SEARCH In Config -RECURSIVE_SEARCH = "True" 57 | 58 | ## Credits :- 59 | 60 | - First of all, full credit goes to 61 | [Shivam Jha aka lzzy12](https://github.com/lzzy12) and 62 | [JaskaranSM aka Zero Cool](https://github.com/jaskaranSM) They build up this 63 | bot from scratch. 64 | - Then a huge thanks to [Sreeraj V R](https://github.com/SVR666) You can 65 | checkout his [repo here](https://github.com/SVR666/LoaderX-Bot) 66 | - Features added from [Sreeraj V R's](https://github.com/SVR666) repo 67 | And Anas [repo here](https://github.com/breakdowns/slam-mirrorbot) Repo (Archived) 68 | - Thanks To Ken For Base Repo 69 | checkout his [repo here](https://github.com/KenHV/Mirror-Bot) 70 | 71 | 72 | ```` 73 | 1. Added Inline Buttons 74 | 2. Added /del command to delete files from drive 75 | 3. /list module will post search result on telegra.ph ``` 76 | ```` 77 | 78 | - Special thanks to [archie](https://github.com/archie9211) for very much useful 79 | feature **Unzipmirror** 80 | 81 | ```` 82 | 1. unzipmirror 83 | 2. Update tracker list dynamically 84 | 3. Fix SSL handsake error ``` 85 | ```` 86 | 87 | # What is this repo about? 88 | 89 | This is a telegram bot writen in python for mirroring files on the internet to 90 | our beloved Google Drive. 91 | 92 | # Inspiration 93 | 94 | This project is heavily inspired from @out386 's telegram bot which is written 95 | in JS. 96 | 97 | ## Deploying on Heroku 98 | - Guide For Deploying on Heroku 99 |

100 | 101 | # Contact Me 102 | - [Telegram](https://T.me/HarshMirrorRepo) 103 | 104 | ## Generate Database 105 |
106 | Click Here For More Details 107 | 108 | **1. Using ElephantSQL** 109 | - Go to https://elephantsql.com and create account (skip this if you already have **ElephantSQL** account) 110 | - Hit `Create New Instance` 111 | - Follow the further instructions in the screen 112 | - Hit `Select Region` 113 | - Hit `Review` 114 | - Hit `Create instance` 115 | - Select your database name 116 | - Copy your database url, and fill to `DATABASE_URL` in config 117 | 118 | **2. Using Heroku PostgreSQL** 119 |

120 | 121 |
122 | 123 | ----- 124 | 125 | ## Gdtot Cookies 126 | To Clone or Leech gdtot link follow these steps: 127 | 1. Login/Register to [gdtot](https://new.gdtot.top). 128 | 2. Copy this script and paste it in browser address bar. 129 | - **Note**: After pasting it check at the beginning of the script in broswer address bar if `javascript:` exists or not, if not so write it as shown below. 130 | ``` 131 | javascript:(function () { 132 | const input = document.createElement('input'); 133 | input.value = JSON.stringify({url : window.location.href, cookie : document.cookie}); 134 | document.body.appendChild(input); 135 | input.focus(); 136 | input.select(); 137 | var result = document.execCommand('copy'); 138 | document.body.removeChild(input); 139 | if(result) 140 | alert('Cookie copied to clipboard'); 141 | else 142 | prompt('Failed to copy cookie. Manually copy below cookie\n\n', input.value); 143 | })(); 144 | ``` 145 | - After pressing enter your browser will prompt a alert. 146 | 3. Now you'll get this type of data in your clipboard 147 | ``` 148 | {"url":"https://new.gdtot.org/","cookie":"PHPSESSID=k2xxxxxxxxxxxxxxxxxxxxj63o; crypt=NGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxWdSVT0%3D"} 149 | 150 | ``` 151 | 4. From this you have to paste value of PHPSESSID and crypt in config.env file. 152 | 153 | ----- 154 | 155 | # How to deploy? 156 | 157 | Deploying is pretty much straight forward and is divided into several steps as 158 | follows: 159 | 160 | ## Installing requirements 161 | 162 | - Clone this repo: 163 | 164 | ``` 165 | git clone https://github.com/magneto261290/magneto-python-aria mirror-bot/ 166 | cd mirror-bot 167 | ``` 168 | 169 | - Install requirements For Debian based distros 170 | 171 | ``` 172 | sudo apt install python3 173 | ``` 174 | 175 | Install Docker by following the 176 | [official docker docs](https://docs.docker.com/engine/install/debian/) 177 | 178 | - For Arch and it's derivatives: 179 | 180 | ``` 181 | sudo pacman -S docker python 182 | ``` 183 | 184 | - Install dependencies for running setup scripts: 185 | 186 | ``` 187 | pip3 install -r requirements-cli.txt 188 | ``` 189 | 190 | ## Setting up config file 191 | 192 | ``` 193 | cp config_sample.env config.env 194 | ``` 195 | 196 | - Remove the first line saying: 197 | 198 | ``` 199 | _____REMOVE_THIS_LINE_____=True 200 | ``` 201 | 202 | Fill up rest of the fields. Meaning of each fields are discussed below: 203 | 204 | - **BOT_TOKEN** : The telegram bot token that you get from @BotFather 205 | - **GDRIVE_FOLDER_ID** : This is the folder ID of the Google Drive Folder to 206 | which you want to upload all the mirrors. 207 | - **DOWNLOAD_DIR** : The path to the local folder where the downloads should be 208 | downloaded to 209 | - **DOWNLOAD_STATUS_UPDATE_INTERVAL** : A short interval of time in seconds 210 | after which the Mirror progress message is updated. (I recommend to keep it 5 211 | seconds at least) 212 | - **OWNER_ID** : The Telegram user ID (not username) of the owner of the bot 213 | - **AUTO_DELETE_MESSAGE_DURATION** : Interval of time (in seconds), after which 214 | the bot deletes it's message (and command message) which is expected to be 215 | viewed instantly. Note: Set to -1 to never automatically delete messages 216 | - **IS_TEAM_DRIVE** : (Optional field) Set to "True" if GDRIVE_FOLDER_ID is from 217 | a Team Drive else False or Leave it empty. 218 | - **USE_SERVICE_ACCOUNTS**: (Optional field) (Leave empty if unsure) Whether to 219 | use service accounts or not. For this to work see "Using service accounts" 220 | section below. 221 | - **INDEX_URL** : (Optional field) Refer to 222 | https://github.com/maple3142/GDIndex/ The URL should not have any trailing '/' 223 | - **API_KEY** : This is to authenticate to your telegram account for downloading 224 | Telegram files. You can get this from https://my.telegram.org DO NOT put this 225 | in quotes. 226 | - **API_HASH** : This is to authenticate to your telegram account for 227 | downloading Telegram files. You can get this from https://my.telegram.org 228 | - **MEGA_KEY**: Mega.nz api key to mirror mega.nz links. Get it from 229 | [Mega SDK Page](https://mega.nz/sdk) 230 | - **MEGA_USERNAME**: Your email id you used to sign up on mega.nz for using 231 | premium accounts (Leave th) 232 | - **MEGA_PASSWORD**: Your password for your mega.nz account 233 | - **BLOCK_MEGA_LINKS**: (Optional field) If you want to remove mega.nz mirror 234 | support (bcoz it's too much buggy and unstable), set it to `True`. 235 | - **SHORTENER**: (Optional field) if you want to use shortener in Gdrive and 236 | index link, fill shotener url here. Examples :- 237 | - exe.io 238 | - gplinks.in 239 | - shrinkme.io 240 | - urlshortx.com 241 | - shortzon.com 242 | 243 | 244 | 245 | Note :- Above are the supported url shorteners. Except these only some url 246 | shorteners are supported. If you want to use any other url shortener then first 247 | ask me that shortener is supported or not. 248 | 249 | - **SHORTENER_API**: Fill your shortener api key if you are using shortener. 250 | - **IGNORE_PENDING_REQUESTS**: (Optional field) If you want the bot to ignore 251 | pending requests after it restarts, set this to `True`. 252 | 253 | Note: You can limit maximum concurrent downloads by changing the value of 254 | MAX_CONCURRENT_DOWNLOADS in aria.sh. By default, it's set to 4 255 | 256 | ## Getting Google OAuth API credential file 257 | 258 | - Visit the 259 | [Google Cloud Console](https://console.developers.google.com/apis/credentials) 260 | - Go to the OAuth Consent tab, fill it, and save. 261 | - Go to the Credentials tab and click Create Credentials -> OAuth Client ID 262 | - Choose Other and Create. 263 | - Use the download button to download your credentials. 264 | - Move that file to the root of mirror-bot, and rename it to credentials.json 265 | - Visit [Google API page](https://console.developers.google.com/apis/library) 266 | - Search for Drive and enable it if it is disabled 267 | - Finally, run the script to generate token file (token.pickle) for Google 268 | Drive: 269 | 270 | ````pip install google-api-python-client google-auth-httplib2 271 | google-auth-oauthlib python3 generate_drive_token.py ``` 272 | 273 | ## Deploying 274 | 275 | - Start docker daemon (skip if already running): 276 | ```` 277 | 278 | sudo dockerd 279 | 280 | ```` 281 | 282 | - Build Docker image: 283 | ```sudo docker build . -t mirror-bot 284 | ```` 285 | 286 | - Run the image: 287 | 288 | ``` 289 | sudo docker run mirror-bot 290 | ``` 291 | 292 | # Using service accounts for uploading to avoid user rate limit 293 | 294 | For Service Account to work, you must set USE_SERVICE_ACCOUNTS="True" in config 295 | file or environment variables Many thanks to 296 | [AutoRClone](https://github.com/xyou365/AutoRclone) for the scripts **NOTE:** 297 | Using service accounts is only recommended while uploading to a team drive. 298 | 299 | ## Generating service accounts 300 | 301 | ## Step 1. Generate service accounts [What is service 302 | 303 | account](https://cloud.google.com/iam/docs/service-accounts) 304 | 305 | Let us create only the service accounts that we need. **Warning:** abuse of this 306 | feature is not the aim of this project and we do **NOT** recommend that you make 307 | a lot of projects, just one project and 100 sa allow you plenty of use, its also 308 | possible that over abuse might get your projects banned by google. 309 | 310 | Note: 1 service account can copy around 750gb a day, 1 project can make 100 311 | service accounts so that's 75tb a day, for most users this should easily 312 | suffice. 313 | 314 | ``` 315 | python3 gen_sa_accounts.py --quick-setup 1 --new-only 316 | ``` 317 | 318 | A folder named accounts will be created which will contain keys for the service 319 | accounts 320 | 321 | NOTE: If you have created SAs in past from this script, you can also just re 322 | download the keys by running: 323 | 324 | ``` 325 | python3 gen_sa_accounts.py --download-keys project_id 326 | ``` 327 | 328 | ### Add all the service accounts to the Team Drive 329 | 330 | - Run: 331 | 332 | ``` 333 | python3 add_to_team_drive.py -d SharedTeamDriveSrcID 334 | ``` 335 | 336 | # Youtube-dl authentication using .netrc file 337 | 338 | For using your premium accounts in youtube-dl, edit the netrc file (in the root 339 | directory of this repository) according to following format: 340 | 341 | ``` 342 | machine host login username password my_youtube_password 343 | ``` 344 | 345 | where host is the name of extractor (eg. youtube, twitch). Multiple accounts of 346 | different hosts can be added each separated by a new line -------------------------------------------------------------------------------- /add_to_team_drive.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import glob 3 | import json 4 | import os 5 | import pickle 6 | import sys 7 | import time 8 | 9 | import googleapiclient.discovery 10 | import progress.bar 11 | from google.auth.transport.requests import Request 12 | from google_auth_oauthlib.flow import InstalledAppFlow 13 | 14 | stt = time.time() 15 | 16 | parse = argparse.ArgumentParser( 17 | description="A tool to add service accounts to a shared drive from a folder containing credential files." 18 | ) 19 | parse.add_argument( 20 | "--path", 21 | "-p", 22 | default="accounts", 23 | help="Specify an alternative path to the service accounts folder.", 24 | ) 25 | parse.add_argument( 26 | "--credentials", 27 | "-c", 28 | default="./credentials.json", 29 | help="Specify the relative path for the credentials file.", 30 | ) 31 | parse.add_argument( 32 | "--yes", "-y", default=False, action="store_true", help="Skips the sanity prompt." 33 | ) 34 | parsereq = parse.add_argument_group("required arguments") 35 | parsereq.add_argument( 36 | "--drive-id", "-d", help="The ID of the Shared Drive.", required=True 37 | ) 38 | 39 | args = parse.parse_args() 40 | acc_dir = args.path 41 | did = args.drive_id 42 | credentials = glob.glob(args.credentials) 43 | 44 | try: 45 | open(credentials[0]) 46 | print(">> Found credentials.") 47 | except IndexError: 48 | print(">> No credentials found.") 49 | sys.exit(0) 50 | 51 | if not args.yes: 52 | # input('Make sure the following client id is added to the shared drive as Manager:\n' + json.loads((open( 53 | # credentials[0],'r').read()))['installed']['client_id']) 54 | input( 55 | ">> Make sure the **Google account** that has generated credentials.json\n is added into your Team Drive " 56 | "(shared drive) as Manager\n>> (Press any key to continue)" 57 | ) 58 | 59 | creds = None 60 | if os.path.exists("token_sa.pickle"): 61 | with open("token_sa.pickle", "rb") as token: 62 | creds = pickle.load(token) 63 | # If there are no (valid) credentials available, let the user log in. 64 | if not creds or not creds.valid: 65 | if creds and creds.expired and creds.refresh_token: 66 | creds.refresh(Request()) 67 | else: 68 | flow = InstalledAppFlow.from_client_secrets_file( 69 | credentials[0], 70 | scopes=[ 71 | "https://www.googleapis.com/auth/admin.directory.group", 72 | "https://www.googleapis.com/auth/admin.directory.group.member", 73 | ], 74 | ) 75 | # creds = flow.run_local_server(port=0) 76 | creds = flow.run_console() 77 | # Save the credentials for the next run 78 | with open("token_sa.pickle", "wb") as token: 79 | pickle.dump(creds, token) 80 | 81 | drive = googleapiclient.discovery.build("drive", "v3", credentials=creds) 82 | batch = drive.new_batch_http_request() 83 | 84 | aa = glob.glob("%s/*.json" % acc_dir) 85 | pbar = progress.bar.Bar("Readying accounts", max=len(aa)) 86 | for i in aa: 87 | ce = json.loads(open(i).read())["client_email"] 88 | batch.add( 89 | drive.permissions().create( 90 | fileId=did, 91 | supportsAllDrives=True, 92 | body={"role": "fileOrganizer", "type": "user", "emailAddress": ce}, 93 | ) 94 | ) 95 | pbar.next() 96 | pbar.finish() 97 | print("Adding...") 98 | batch.execute() 99 | 100 | print("Complete.") 101 | hours, rem = divmod((time.time() - stt), 3600) 102 | minutes, sec = divmod(rem, 60) 103 | print(f"Elapsed Time:\n{int(hours):0>2}:{int(minutes):0>2}:{sec:05.2f}") 104 | -------------------------------------------------------------------------------- /aria.bat: -------------------------------------------------------------------------------- 1 | aria2c --enable-rpc --rpc-listen-all=false --rpc-listen-port 6800 --max-connection-per-server=10 --rpc-max-request-size=1024M --seed-time=0.01 --min-split-size=10M --follow-torrent=mem --split=10 --daemon=true --allow-overwrite=true 2 | -------------------------------------------------------------------------------- /aria.sh: -------------------------------------------------------------------------------- 1 | TRACKERS=$(curl -Ns https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all_udp.txt | awk '$1' | tr '\n' ',') 2 | 3 | aria2c \ 4 | --allow-overwrite=true \ 5 | --bt-enable-lpd=true \ 6 | --bt-max-peers=0 \ 7 | --bt-tracker="[$TRACKERS]" \ 8 | --check-certificate=false \ 9 | --daemon=true \ 10 | --enable-rpc \ 11 | --follow-torrent=mem \ 12 | --max-connection-per-server=16 \ 13 | --max-overall-upload-limit=1K \ 14 | --peer-agent=qBittorrent/4.3.6 \ 15 | --peer-id-prefix=-qB4360- \ 16 | --seed-time=0 \ 17 | --bt-tracker-connect-timeout=300 \ 18 | --bt-stop-timeout=1200 \ 19 | --user-agent=qBittorrent/4.3.6 -------------------------------------------------------------------------------- /bot/__init__.py: -------------------------------------------------------------------------------- 1 | import faulthandler 2 | import logging 3 | import os 4 | import random 5 | import socket 6 | import string 7 | import threading 8 | import time 9 | import requests 10 | import aria2p 11 | import telegram.ext as tg 12 | from dotenv import load_dotenv 13 | from pyrogram import Client 14 | 15 | import psycopg2 16 | from psycopg2 import Error 17 | 18 | faulthandler.enable() 19 | import subprocess 20 | 21 | from megasdkrestclient import MegaSdkRestClient 22 | from megasdkrestclient import errors as mega_err 23 | 24 | socket.setdefaulttimeout(600) 25 | 26 | botStartTime = time.time() 27 | if os.path.exists("log.txt"): 28 | with open("log.txt", "r+") as f: 29 | f.truncate(0) 30 | 31 | logging.basicConfig( 32 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 33 | handlers=[logging.FileHandler("log.txt"), logging.StreamHandler()], 34 | level=logging.INFO, 35 | ) 36 | #Config And Heroku Support 37 | CONFIG_FILE_URL = os.environ.get('CONFIG_FILE_URL') 38 | if CONFIG_FILE_URL: 39 | if len(CONFIG_FILE_URL) == 0: 40 | CONFIG_FILE_URL = None 41 | if CONFIG_FILE_URL is not None: 42 | logging.error("Downloading config.env From Provided URL") 43 | if os.path.isfile("config.env"): 44 | logging.error("Updating config.env") 45 | os.remove("config.env") 46 | res = requests.get(CONFIG_FILE_URL) 47 | if res.status_code == 200: 48 | with open('config.env', 'wb+') as f: 49 | f.write(res.content) 50 | f.close() 51 | else: 52 | logging.error(f"Failed to download config.env {res.status_code}") 53 | load_dotenv("config.env") 54 | 55 | Interval = [] 56 | def mktable(): 57 | try: 58 | conn = psycopg2.connect(DB_URI) 59 | cur = conn.cursor() 60 | sql = "CREATE TABLE users (uid bigint, sudo boolean DEFAULT FALSE);" 61 | cur.execute(sql) 62 | conn.commit() 63 | logging.info("Table Created!") 64 | except Error as e: 65 | logging.error(e) 66 | exit(1) 67 | 68 | def getConfig(name: str): 69 | return os.environ[name] 70 | 71 | 72 | LOGGER = logging.getLogger(__name__) 73 | 74 | try: 75 | if bool(getConfig("_____REMOVE_THIS_LINE_____")): 76 | logging.error("The README.md file there to be read! Exiting now!") 77 | exit() 78 | except KeyError: 79 | pass 80 | 81 | #RECURSIVE SEARCH 82 | DRIVE_NAME = [] 83 | DRIVE_ID = [] 84 | UNI_INDEX_URL = [] 85 | 86 | if os.path.exists('drive_folder'): 87 | with open('drive_folder', 'r+') as f: 88 | lines = f.readlines() 89 | for line in lines: 90 | temp = line.strip().split() 91 | DRIVE_NAME.append(temp[0].replace("_", " ")) 92 | DRIVE_ID.append(temp[1]) 93 | try: 94 | UNI_INDEX_URL.append(temp[2]) 95 | except IndexError as e: 96 | UNI_INDEX_URL.append(None) 97 | try: 98 | RECURSIVE_SEARCH = getConfig("RECURSIVE_SEARCH") 99 | if RECURSIVE_SEARCH.lower() == "true": 100 | RECURSIVE_SEARCH = True 101 | else: 102 | RECURSIVE_SEARCH = False 103 | except KeyError: 104 | RECURSIVE_SEARCH = False 105 | 106 | 107 | if RECURSIVE_SEARCH: 108 | if DRIVE_ID: 109 | pass 110 | else : 111 | LOGGER.error("Fill Drive_Folder File For Multi Drive Search!") 112 | exit(1) 113 | 114 | 115 | aria2 = aria2p.API( 116 | aria2p.Client( 117 | host="http://localhost", 118 | port=6800, 119 | secret="", 120 | ) 121 | ) 122 | 123 | def aria2c_init(): 124 | try: 125 | if not os.path.isfile(".restartmsg"): 126 | logging.info("Initializing Aria2c") 127 | link = "https://releases.ubuntu.com/21.10/ubuntu-21.10-desktop-amd64.iso.torrent" 128 | path = "/usr/src/app/" 129 | aria2.add_uris([link], {'dir': path}) 130 | time.sleep(3) 131 | downloads = aria2.get_downloads() 132 | time.sleep(30) 133 | for download in downloads: 134 | aria2.remove([download], force=True, files=True) 135 | except Exception as e: 136 | logging.error(f"Aria2c initializing error: {e}") 137 | pass 138 | 139 | threading.Thread(target=aria2c_init).start() 140 | time.sleep(0.5) 141 | 142 | DOWNLOAD_DIR = None 143 | BOT_TOKEN = None 144 | 145 | download_dict_lock = threading.Lock() 146 | status_reply_dict_lock = threading.Lock() 147 | # Key: update.effective_chat.id 148 | # Value: telegram.Message 149 | status_reply_dict = {} 150 | # Key: update.message.message_id 151 | # Value: An object of Status 152 | download_dict = {} 153 | AS_DOC_USERS = set() 154 | AS_MEDIA_USERS = set() 155 | # Stores list of users and chats the bot is authorized to use in 156 | AUTHORIZED_CHATS = set() 157 | SUDO_USERS = set() 158 | LOGS_CHATS = set() 159 | if os.path.exists('sudo_users.txt'): 160 | with open('sudo_users.txt', 'r+') as f: 161 | lines = f.readlines() 162 | for line in lines: 163 | SUDO_USERS.add(int(line.split()[0])) 164 | try: 165 | schats = getConfig('SUDO_USERS') 166 | schats = schats.split(" ") 167 | for chats in schats: 168 | SUDO_USERS.add(int(chats)) 169 | except: 170 | pass 171 | if os.path.exists("authorized_chats.txt"): 172 | with open("authorized_chats.txt", "r+") as f: 173 | lines = f.readlines() 174 | for line in lines: 175 | # LOGGER.info(line.split()) 176 | AUTHORIZED_CHATS.add(int(line.split()[0])) 177 | try: 178 | achats = getConfig("AUTHORIZED_CHATS") 179 | achats = achats.split(" ") 180 | for chats in achats: 181 | AUTHORIZED_CHATS.add(int(chats)) 182 | except: 183 | pass 184 | 185 | if os.path.exists("logs_chat.txt"): 186 | with open("logs_chat.txt", "r+") as f: 187 | lines = f.readlines() 188 | for line in lines: 189 | # LOGGER.info(line.split()) 190 | LOGS_CHATS.add(int(line.split()[0])) 191 | try: 192 | achats = getConfig("LOGS_CHATS") 193 | achats = achats.split(" ") 194 | for chats in achats: 195 | LOGS_CHATS.add(int(chats)) 196 | except: 197 | logging.warning('Logs Chat Details not provided!') 198 | pass 199 | 200 | try: 201 | PHPSESSID = getConfig('PHPSESSID') 202 | CRYPT = getConfig('CRYPT') 203 | if len(PHPSESSID) == 0 or len(CRYPT) == 0: 204 | raise KeyError 205 | except KeyError: 206 | PHPSESSID = None 207 | CRYPT = None 208 | 209 | try: 210 | BOT_TOKEN = getConfig("BOT_TOKEN") 211 | parent_id = getConfig("GDRIVE_FOLDER_ID") 212 | DOWNLOAD_DIR = getConfig("DOWNLOAD_DIR") 213 | if not DOWNLOAD_DIR.endswith("/"): 214 | DOWNLOAD_DIR = DOWNLOAD_DIR + "/" 215 | DOWNLOAD_STATUS_UPDATE_INTERVAL = int(getConfig("DOWNLOAD_STATUS_UPDATE_INTERVAL")) 216 | OWNER_ID = int(getConfig("OWNER_ID")) 217 | AUTO_DELETE_MESSAGE_DURATION = int(getConfig("AUTO_DELETE_MESSAGE_DURATION")) 218 | TELEGRAM_API = getConfig("TELEGRAM_API") 219 | TELEGRAM_HASH = getConfig("TELEGRAM_HASH") 220 | except KeyError as missing: 221 | LOGGER.error("One or more env variables missing! Exiting now ") 222 | LOGGER.error(str(missing) + " Env Variable Is Missing.") 223 | exit(1) 224 | 225 | try: 226 | DB_URI = getConfig('DATABASE_URL') 227 | if len(DB_URI) == 0: 228 | raise KeyError 229 | except KeyError: 230 | DB_URI = None 231 | if DB_URI is not None: 232 | try: 233 | conn = psycopg2.connect(DB_URI) 234 | cur = conn.cursor() 235 | sql = "SELECT * from users;" 236 | cur.execute(sql) 237 | rows = cur.fetchall() #returns a list ==> (uid, sudo) 238 | for row in rows: 239 | AUTHORIZED_CHATS.add(row[0]) 240 | if row[1]: 241 | SUDO_USERS.add(row[0]) 242 | except Error as e: 243 | if 'relation "users" does not exist' in str(e): 244 | mktable() 245 | else: 246 | LOGGER.error(e) 247 | exit(1) 248 | finally: 249 | cur.close() 250 | conn.close() 251 | 252 | LOGGER.info("Generating USER_SESSION_STRING") 253 | app = Client( 254 | ":memory:", api_id=int(TELEGRAM_API), api_hash=TELEGRAM_HASH, bot_token=BOT_TOKEN 255 | ) 256 | 257 | try: 258 | MEGA_KEY = getConfig("MEGA_KEY") 259 | 260 | except KeyError: 261 | MEGA_KEY = None 262 | LOGGER.info("MEGA API KEY NOT AVAILABLE") 263 | if MEGA_KEY is not None: 264 | # Start megasdkrest binary 265 | subprocess.Popen(["megasdkrest", "--apikey", MEGA_KEY]) 266 | time.sleep(3) # Wait for the mega server to start listening 267 | mega_client = MegaSdkRestClient("http://localhost:6090") 268 | try: 269 | MEGA_USERNAME = getConfig("MEGA_USERNAME") 270 | MEGA_PASSWORD = getConfig("MEGA_PASSWORD") 271 | if len(MEGA_USERNAME) > 0 and len(MEGA_PASSWORD) > 0: 272 | try: 273 | mega_client.login(MEGA_USERNAME, MEGA_PASSWORD) 274 | except mega_err.MegaSdkRestClientException as e: 275 | logging.error(e.message["message"]) 276 | exit(0) 277 | else: 278 | LOGGER.info( 279 | "Mega API KEY provided but credentials not provided. Starting mega in anonymous mode!" 280 | ) 281 | MEGA_USERNAME = None 282 | MEGA_PASSWORD = None 283 | except KeyError: 284 | LOGGER.info( 285 | "Mega API KEY provided but credentials not provided. Starting mega in anonymous mode!" 286 | ) 287 | MEGA_USERNAME = None 288 | MEGA_PASSWORD = None 289 | else: 290 | MEGA_USERNAME = None 291 | MEGA_PASSWORD = None 292 | try: 293 | INDEX_URL = getConfig("INDEX_URL") 294 | if len(INDEX_URL) == 0: 295 | INDEX_URL = None 296 | except KeyError: 297 | INDEX_URL = None 298 | try: 299 | BUTTON_THREE_NAME = getConfig("BUTTON_THREE_NAME") 300 | BUTTON_THREE_URL = getConfig("BUTTON_THREE_URL") 301 | if len(BUTTON_THREE_NAME) == 0 or len(BUTTON_THREE_URL) == 0: 302 | raise KeyError 303 | except KeyError: 304 | BUTTON_THREE_NAME = None 305 | BUTTON_THREE_URL = None 306 | try: 307 | BUTTON_FOUR_NAME = getConfig("BUTTON_FOUR_NAME") 308 | BUTTON_FOUR_URL = getConfig("BUTTON_FOUR_URL") 309 | if len(BUTTON_FOUR_NAME) == 0 or len(BUTTON_FOUR_URL) == 0: 310 | raise KeyError 311 | except KeyError: 312 | BUTTON_FOUR_NAME = None 313 | BUTTON_FOUR_URL = None 314 | try: 315 | BUTTON_FIVE_NAME = getConfig("BUTTON_FIVE_NAME") 316 | BUTTON_FIVE_URL = getConfig("BUTTON_FIVE_URL") 317 | if len(BUTTON_FIVE_NAME) == 0 or len(BUTTON_FIVE_URL) == 0: 318 | raise KeyError 319 | except KeyError: 320 | BUTTON_FIVE_NAME = None 321 | BUTTON_FIVE_URL = None 322 | try: 323 | IS_TEAM_DRIVE = getConfig("IS_TEAM_DRIVE") 324 | IS_TEAM_DRIVE = IS_TEAM_DRIVE.lower() == "true" 325 | except KeyError: 326 | IS_TEAM_DRIVE = False 327 | 328 | try: 329 | USE_SERVICE_ACCOUNTS = getConfig("USE_SERVICE_ACCOUNTS") 330 | if USE_SERVICE_ACCOUNTS.lower() == "true": 331 | USE_SERVICE_ACCOUNTS = True 332 | else: 333 | USE_SERVICE_ACCOUNTS = False 334 | except KeyError: 335 | USE_SERVICE_ACCOUNTS = False 336 | 337 | try: 338 | BLOCK_MEGA_LINKS = getConfig("BLOCK_MEGA_LINKS") 339 | BLOCK_MEGA_LINKS = BLOCK_MEGA_LINKS.lower() == "true" 340 | except KeyError: 341 | BLOCK_MEGA_LINKS = False 342 | 343 | try: 344 | SHORTENER = getConfig("SHORTENER") 345 | SHORTENER_API = getConfig("SHORTENER_API") 346 | if len(SHORTENER) == 0 or len(SHORTENER_API) == 0: 347 | raise KeyError 348 | except KeyError: 349 | SHORTENER = None 350 | SHORTENER_API = None 351 | 352 | IGNORE_PENDING_REQUESTS = False 353 | try: 354 | if getConfig("IGNORE_PENDING_REQUESTS").lower() == "true": 355 | IGNORE_PENDING_REQUESTS = True 356 | except KeyError: 357 | pass 358 | 359 | try: 360 | TG_SPLIT_SIZE = getConfig('TG_SPLIT_SIZE') 361 | if len(TG_SPLIT_SIZE) == 0 or int(TG_SPLIT_SIZE) > 2097152000: 362 | raise KeyError 363 | else: 364 | TG_SPLIT_SIZE = int(TG_SPLIT_SIZE) 365 | except KeyError: 366 | TG_SPLIT_SIZE = 2097152000 367 | try: 368 | AS_DOCUMENT = getConfig('AS_DOCUMENT') 369 | AS_DOCUMENT = AS_DOCUMENT.lower() == 'true' 370 | except KeyError: 371 | AS_DOCUMENT = False 372 | 373 | #VIEW_LINK 374 | try: 375 | VIEW_LINK = getConfig('VIEW_LINK') 376 | if VIEW_LINK.lower() == 'true': 377 | VIEW_LINK = True 378 | else: 379 | VIEW_LINK = False 380 | except KeyError: 381 | VIEW_LINK = False 382 | #CLONE 383 | try: 384 | CLONE_LIMIT = getConfig('CLONE_LIMIT') 385 | if len(CLONE_LIMIT) == 0: 386 | CLONE_LIMIT = None 387 | except KeyError: 388 | CLONE_LIMIT = None 389 | 390 | try: 391 | STOP_DUPLICATE_CLONE = getConfig('STOP_DUPLICATE_CLONE') 392 | if STOP_DUPLICATE_CLONE.lower() == 'true': 393 | STOP_DUPLICATE_CLONE = True 394 | else: 395 | STOP_DUPLICATE_CLONE = False 396 | except KeyError: 397 | STOP_DUPLICATE_CLONE = False 398 | #HEROKUSUPPORT 399 | try: 400 | TOKEN_PICKLE_URL = getConfig('TOKEN_PICKLE_URL') 401 | if len(TOKEN_PICKLE_URL) == 0: 402 | TOKEN_PICKLE_URL = None 403 | else: 404 | res = requests.get(TOKEN_PICKLE_URL) 405 | if res.status_code == 200: 406 | with open('token.pickle', 'wb+') as f: 407 | f.write(res.content) 408 | f.close() 409 | else: 410 | logging.error(f"Failed to download token.pickle {res.status_code}") 411 | raise KeyError 412 | except KeyError: 413 | pass 414 | 415 | try: 416 | ACCOUNTS_ZIP_URL = getConfig('ACCOUNTS_ZIP_URL') 417 | if len(ACCOUNTS_ZIP_URL) == 0: 418 | ACCOUNTS_ZIP_URL = None 419 | else: 420 | res = requests.get(ACCOUNTS_ZIP_URL) 421 | if res.status_code == 200: 422 | with open('accounts.zip', 'wb+') as f: 423 | f.write(res.content) 424 | f.close() 425 | else: 426 | logging.error(f"Failed to download accounts.zip {res.status_code}") 427 | raise KeyError 428 | subprocess.run(["unzip", "-q", "-o", "accounts.zip"]) 429 | os.remove("accounts.zip") 430 | except KeyError: 431 | pass 432 | #uptobox 433 | try: 434 | UPTOBOX_TOKEN = getConfig('UPTOBOX_TOKEN') 435 | if len(UPTOBOX_TOKEN) == 0: 436 | raise KeyError 437 | except KeyError: 438 | UPTOBOX_TOKEN = None 439 | updater = tg.Updater(token=BOT_TOKEN) 440 | bot = updater.bot 441 | dispatcher = updater.dispatcher 442 | -------------------------------------------------------------------------------- /bot/__main__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import signal 4 | import time 5 | from sys import executable 6 | 7 | import psutil 8 | from pyrogram import idle 9 | from telegram import InlineKeyboardMarkup 10 | from telegram.ext import CommandHandler 11 | from bot import IGNORE_PENDING_REQUESTS, app, bot, botStartTime, dispatcher, updater 12 | from bot.helper.ext_utils import fs_utils 13 | from bot.helper.ext_utils.bot_utils import get_readable_file_size, get_readable_time 14 | from bot.helper.telegram_helper.bot_commands import BotCommands 15 | from bot.helper.telegram_helper import button_build 16 | from bot.helper.telegram_helper.filters import CustomFilters 17 | from bot.helper.telegram_helper.message_utils import ( 18 | LOGGER, 19 | editMessage, 20 | sendLogFile, 21 | sendMessage, 22 | sendMarkup, 23 | ) 24 | from bot.modules import ( # noqa 25 | authorize, 26 | cancel_mirror, 27 | clone, 28 | delete, 29 | list, 30 | mirror, 31 | mirror_status, 32 | watch, 33 | leech_settings, 34 | speedtest, 35 | count, 36 | ) 37 | 38 | 39 | def stats(update, context): 40 | currentTime = get_readable_time(time.time() - botStartTime) 41 | total, used, free = shutil.disk_usage(".") 42 | total = get_readable_file_size(total) 43 | used = get_readable_file_size(used) 44 | free = get_readable_file_size(free) 45 | sent = get_readable_file_size(psutil.net_io_counters().bytes_sent) 46 | recv = get_readable_file_size(psutil.net_io_counters().bytes_recv) 47 | cpuUsage = psutil.cpu_percent(interval=0.5) 48 | memory = psutil.virtual_memory().percent 49 | disk = psutil.disk_usage("/").percent 50 | stats = ( 51 | f"Bot Uptime: {currentTime}\n" 52 | f"Total disk space: {total}\n" 53 | f"Used: {used} " 54 | f"Free: {free}\n\n" 55 | f"Data Usage\nUpload: {sent}\n" 56 | f"Down: {recv}\n\n" 57 | f"CPU: {cpuUsage}% " 58 | f"RAM: {memory}% " 59 | f"Disk: {disk}%" 60 | ) 61 | sendMessage(stats, context.bot, update) 62 | 63 | def start(update, context): 64 | buttons = button_build.ButtonMaker() 65 | buttons.buildbutton("Repo", "https://github.com/harshpreets63/Mirror-Bot") 66 | buttons.buildbutton("Channel", "https://t.me/HarshMirror") 67 | reply_markup = InlineKeyboardMarkup(buttons.build_menu(2)) 68 | if CustomFilters.authorized_user(update) or CustomFilters.authorized_chat(update): 69 | start_string = f''' 70 | This bot can mirror all your links to Google Drive! 71 | Type /{BotCommands.HelpCommand} to get a list of available commands 72 | ''' 73 | sendMarkup(start_string, context.bot, update, reply_markup) 74 | else: 75 | sendMarkup( 76 | 'Oops! not a Authorized user.\nPlease join our Channel.\nOr Host Your Own Bot Using My Repo.', 77 | context.bot, 78 | update, 79 | reply_markup, 80 | ) 81 | 82 | def restart(update, context): 83 | restart_message = sendMessage("Restarting, Please wait!", context.bot, update) 84 | # Save restart message ID and chat ID in order to edit it after restarting 85 | with open(".restartmsg", "w") as f: 86 | f.truncate(0) 87 | f.write(f"{restart_message.chat.id}\n{restart_message.message_id}\n") 88 | fs_utils.clean_all() 89 | os.execl(executable, executable, "-m", "bot") 90 | 91 | 92 | def ping(update, context): 93 | start_time = int(round(time.time() * 1000)) 94 | reply = sendMessage("Starting Ping", context.bot, update) 95 | end_time = int(round(time.time() * 1000)) 96 | editMessage(f"{end_time - start_time} ms", reply) 97 | 98 | 99 | def log(update, context): 100 | sendLogFile(context.bot, update) 101 | 102 | 103 | def bot_help(update, context): 104 | help_string = f""" 105 | /{BotCommands.HelpCommand}: To get this message 106 | 107 | /{BotCommands.MirrorCommand} [download_url][magnet_link]: Start mirroring the link to google drive. 108 | 109 | /{BotCommands.CloneCommand} [Drive_Link]: Copy link to google drive 110 | /{BotCommands.CountCommand} [Drive_Link]: Count Files Of a Drive Link 111 | 112 | /{BotCommands.UnzipMirrorCommand} [download_url][magnet_link] : starts mirroring and if downloaded file is any archive , extracts it to google drive 113 | /{BotCommands.ZipMirrorCommand} [download_url][magnet_link]: start mirroring and upload the archived (.zip) version of the download 114 | /{BotCommands.TarMirrorCommand} [download_url][magnet_link]: start mirroring and upload the archived (.tar) version of the download 115 | 116 | /{BotCommands.LeechCommand} [download_url][magnet_link]: Start mirroring the link to telegram 117 | /{BotCommands.UnzipLeechCommand} [download_url][magnet_link] : starts mirroring and if downloaded file is any archive , extracts it to telegram 118 | /{BotCommands.ZipLeechCommand} [download_url][magnet_link]: start mirroring and upload the archived (.zip) version of the download 119 | /{BotCommands.TarLeechCommand} [download_url][magnet_link]: start mirroring and upload the archived (.tar) version of the download 120 | 121 | /{BotCommands.WatchCommand} [youtube-dl supported link]: Mirror through youtube-dl. Click /{BotCommands.WatchCommand} for more help 122 | /{BotCommands.LeechWatchCommand} Mirror Youtube-dl support link(Leech)"), 123 | /{BotCommands.LeechTarWatchCommand} Mirror Youtube playlist link as .tar(Leech)"), 124 | /{BotCommands.LeechZipWatchCommand} Mirror Youtube playlist link as .zip(Leech)"), 125 | /{BotCommands.ZipWatchCommand} [youtube-dl supported link]: Mirror through youtube-dl and zip before uploading 126 | /{BotCommands.TarWatchCommand} [youtube-dl supported link]: Mirror through youtube-dl and tar before uploading 127 | 128 | /{BotCommands.CancelMirror} Reply to the message by which the download was initiated and that download will be cancelled 129 | 130 | /{BotCommands.StatusCommand} Shows a status of all the downloads 131 | 132 | /{BotCommands.SpeedCommand} Test Internet Speed Of the Bot 133 | 134 | /{BotCommands.ListCommand} [search term]: Searches the search term in the Google drive, if found replies with the link 135 | 136 | /{BotCommands.StatsCommand} Show Stats of the machine the bot is hosted on 137 | 138 | /{BotCommands.AuthorizeCommand} Authorize a chat or a user to use the bot (Can only be invoked by owner of the bot) 139 | 140 | /{BotCommands.LogCommand} Get a log file of the bot. Handy for getting crash reports 141 | /{BotCommands.LeechSetCommand} Leech settings 142 | /{BotCommands.SetThumbCommand} Reply photo to set it as Thumbnail 143 | 144 | """ 145 | sendMessage(help_string, context.bot, update) 146 | 147 | 148 | botcmds = [ 149 | (f"{BotCommands.HelpCommand}", "Get detailed help"), 150 | (f"{BotCommands.MirrorCommand}", "Start mirroring"), 151 | (f"{BotCommands.LeechCommand}", "Start Leeching"), 152 | (f"{BotCommands.UnzipLeechCommand}", "Extract files(Leech)"), 153 | (f"{BotCommands.LeechWatchCommand}", "Mirror Youtube-dl support link(Leech)"), 154 | (f"{BotCommands.LeechTarWatchCommand}", "Mirror Youtube playlist link as .zip(Leech)"), 155 | (f"{BotCommands.LeechZipWatchCommand}", "Mirror Youtube playlist link as .zip(Leech)"), 156 | (f"{BotCommands.ZipLeechCommand}", "Start mirroring and upload as .zip(Leech)"), 157 | (f"{BotCommands.TarLeechCommand}", "Start mirroring and upload as .tar(Leech)"), 158 | (f"{BotCommands.ZipMirrorCommand}", "Start mirroring and upload as .zip"), 159 | (f"{BotCommands.TarMirrorCommand}", "Start mirroring and upload as .tar"), 160 | (f"{BotCommands.UnzipMirrorCommand}", "Extract files"), 161 | (f"{BotCommands.CloneCommand}", "Copy file/folder from GDrive"), 162 | (f"{BotCommands.deleteCommand}", "Delete file from GDrive [owner only]"), 163 | (f"{BotCommands.WatchCommand}", "Mirror Youtube-dl support link"), 164 | (f"{BotCommands.ZipWatchCommand}", "Mirror Youtube playlist link as .zip"), 165 | (f"{BotCommands.TarWatchCommand}", "Mirror Youtube playlist link as .tar"), 166 | (f'{BotCommands.ListCommand}','Search in Drive'), 167 | (f"{BotCommands.CancelMirror}", "Cancel a task"), 168 | (f"{BotCommands.CancelAllCommand}", "Cancel all tasks [owner only]"), 169 | (f"{BotCommands.CountCommand}", "Count files/folders of G-Drive Links"), 170 | (f"{BotCommands.StatusCommand}", "Get mirror status"), 171 | (f"{BotCommands.StatsCommand}", "Bot usage stats"), 172 | (f"{BotCommands.SpeedCommand}", "Check Internet Speed of the Host"), 173 | (f"{BotCommands.PingCommand}", "Ping the bot"), 174 | (f"{BotCommands.RestartCommand}", "Restart the bot [owner only]"), 175 | (f"{BotCommands.LogCommand}", "Get the bot log [owner only]"), 176 | (f"{BotCommands.AuthorizedUsersCommand}", "Get Authorized Users List[owner only]"), 177 | (f"{BotCommands.AuthorizeCommand}", "Authorize User To Use Bot [owner only]"), 178 | (f"{BotCommands.UnAuthorizeCommand}", "UnAuthorize User From Bot [owner only]"), 179 | (f"{BotCommands.AddSudoCommand}", "Add Sudo User [owner only]"), 180 | (f"{BotCommands.RmSudoCommand}", "Remove sudo User [owner only]"), 181 | (f'{BotCommands.LeechSetCommand}','Leech settings'), 182 | (f'{BotCommands.SetThumbCommand}','Set thumbnail'), 183 | ] 184 | 185 | 186 | def main(): 187 | fs_utils.start_cleanup() 188 | # Check if the bot is restarting 189 | if os.path.isfile(".restartmsg"): 190 | with open(".restartmsg") as f: 191 | chat_id, msg_id = map(int, f) 192 | bot.edit_message_text("Restarted successfully!", chat_id, msg_id) 193 | os.remove(".restartmsg") 194 | bot.set_my_commands(botcmds) 195 | 196 | start_handler = CommandHandler( 197 | BotCommands.StartCommand, 198 | start, 199 | run_async=True, 200 | ) 201 | ping_handler = CommandHandler( 202 | BotCommands.PingCommand, 203 | ping, 204 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, 205 | run_async=True, 206 | ) 207 | restart_handler = CommandHandler( 208 | BotCommands.RestartCommand, 209 | restart, 210 | filters=CustomFilters.owner_filter | CustomFilters.sudo_user, 211 | run_async=True, 212 | ) 213 | help_handler = CommandHandler( 214 | BotCommands.HelpCommand, 215 | bot_help, 216 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, 217 | run_async=True, 218 | ) 219 | stats_handler = CommandHandler( 220 | BotCommands.StatsCommand, 221 | stats, 222 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, 223 | run_async=True, 224 | ) 225 | log_handler = CommandHandler( 226 | BotCommands.LogCommand, log, filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True 227 | ) 228 | dispatcher.add_handler(start_handler) 229 | dispatcher.add_handler(ping_handler) 230 | dispatcher.add_handler(restart_handler) 231 | dispatcher.add_handler(help_handler) 232 | dispatcher.add_handler(stats_handler) 233 | dispatcher.add_handler(log_handler) 234 | updater.start_polling(drop_pending_updates=IGNORE_PENDING_REQUESTS) 235 | LOGGER.info("Bot Started!") 236 | signal.signal(signal.SIGINT, fs_utils.exit_clean_up) 237 | 238 | 239 | app.start() 240 | main() 241 | idle() -------------------------------------------------------------------------------- /bot/helper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshpreets63/Mirror-Bot/08cd0fc9ecb8460d964e8970cfa8a1268f6faade/bot/helper/__init__.py -------------------------------------------------------------------------------- /bot/helper/ext_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshpreets63/Mirror-Bot/08cd0fc9ecb8460d964e8970cfa8a1268f6faade/bot/helper/ext_utils/__init__.py -------------------------------------------------------------------------------- /bot/helper/ext_utils/bot_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import re 3 | import threading 4 | import time 5 | 6 | from bot import download_dict, download_dict_lock 7 | from bot.helper.telegram_helper.bot_commands import BotCommands 8 | LOGGER = logging.getLogger(__name__) 9 | 10 | MAGNET_REGEX = r"magnet:\?xt=urn:btih:[a-zA-Z0-9]*" 11 | 12 | URL_REGEX = r"(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+" 13 | 14 | 15 | class MirrorStatus: 16 | STATUS_UPLOADING = "Uploading..." 17 | STATUS_DOWNLOADING = "Downloading..." 18 | STATUS_WAITING = "Queued..." 19 | STATUS_FAILED = "Failed. Cleaning download..." 20 | STATUS_CANCELLED = "Cancelled." 21 | STATUS_ARCHIVING = "Archiving..." 22 | STATUS_EXTRACTING = "Extracting..." 23 | STATUS_SPLITTING = "Splitting..." 24 | STATUS_CLONING = "Cloning..." 25 | 26 | 27 | PROGRESS_MAX_SIZE = 100 // 8 28 | PROGRESS_INCOMPLETE = ["●", "●", "●", "●", "●", "●", "●"] 29 | 30 | SIZE_UNITS = ["B", "KB", "MB", "GB", "TB", "PB"] 31 | 32 | 33 | class setInterval: 34 | def __init__(self, interval, action): 35 | self.interval = interval 36 | self.action = action 37 | self.stopEvent = threading.Event() 38 | thread = threading.Thread(target=self.__setInterval) 39 | thread.start() 40 | 41 | def __setInterval(self): 42 | nextTime = time.time() + self.interval 43 | while not self.stopEvent.wait(nextTime - time.time()): 44 | nextTime += self.interval 45 | self.action() 46 | 47 | def cancel(self): 48 | self.stopEvent.set() 49 | 50 | def check_limit(size, limit): 51 | LOGGER.info('Checking File/Folder Size...') 52 | if limit is not None: 53 | limit = limit.split(' ', maxsplit=1) 54 | limitint = int(limit[0]) 55 | if 'G' in limit[1] or 'g' in limit[1]: 56 | if size > limitint * 1024**3: 57 | return True 58 | elif 'T' in limit[1] or 't' in limit[1]: 59 | if size > limitint * 1024**4: 60 | return True 61 | 62 | 63 | def get_readable_file_size(size_in_bytes) -> str: 64 | if size_in_bytes is None: 65 | return "0B" 66 | index = 0 67 | while size_in_bytes >= 1024: 68 | size_in_bytes /= 1024 69 | index += 1 70 | try: 71 | return f"{round(size_in_bytes, 2)}{SIZE_UNITS[index]}" 72 | except IndexError: 73 | return "File too large" 74 | 75 | def getAllDownload(): 76 | with download_dict_lock: 77 | for dlDetails in download_dict.values(): 78 | status = dlDetails.status() 79 | if ( 80 | status 81 | not in [ 82 | MirrorStatus.STATUS_ARCHIVING, 83 | MirrorStatus.STATUS_EXTRACTING, 84 | MirrorStatus.STATUS_SPLITTING, 85 | MirrorStatus.STATUS_CLONING, 86 | MirrorStatus.STATUS_UPLOADING, 87 | ] 88 | and dlDetails 89 | ): 90 | return dlDetails 91 | return None 92 | 93 | def getDownloadByGid(gid): 94 | with download_dict_lock: 95 | for dl in download_dict.values(): 96 | status = dl.status() 97 | if ( 98 | status 99 | not in [ 100 | MirrorStatus.STATUS_ARCHIVING, 101 | MirrorStatus.STATUS_EXTRACTING, 102 | MirrorStatus.STATUS_SPLITTING, 103 | ] 104 | and dl.gid() == gid 105 | ): 106 | return dl 107 | return None 108 | 109 | 110 | def get_progress_bar_string(status): 111 | completed = status.processed_bytes() / 8 112 | total = status.size_raw() / 8 113 | p = 0 if total == 0 else round(completed * 100 / total) 114 | p = min(max(p, 0), 100) 115 | cFull = p // 8 116 | cPart = p % 8 - 1 117 | p_str = "●" * cFull 118 | if cPart >= 0: 119 | p_str += PROGRESS_INCOMPLETE[cPart] 120 | p_str += "○" * (PROGRESS_MAX_SIZE - cFull) 121 | p_str = f"[{p_str}]" 122 | return p_str 123 | 124 | def get_readable_message(): 125 | with download_dict_lock: 126 | msg = "" 127 | for download in list(download_dict.values()): 128 | msg += f"Filename: {download.name()}" 129 | msg += f"\nStatus: {download.status()}" 130 | if download.status() != MirrorStatus.STATUS_ARCHIVING and download.status() != MirrorStatus.STATUS_EXTRACTING and download.status() != MirrorStatus.STATUS_SPLITTING: 131 | msg += f"\n{get_progress_bar_string(download)} {download.progress()}" 132 | if download.status() == MirrorStatus.STATUS_DOWNLOADING: 133 | msg += f"\nDownloaded: {get_readable_file_size(download.processed_bytes())} of {download.size()}" 134 | elif download.status() == MirrorStatus.STATUS_CLONING: 135 | msg += f"\nCloned: {get_readable_file_size(download.processed_bytes())} of {download.size()}" 136 | else: 137 | msg += f"\nUploaded: {get_readable_file_size(download.processed_bytes())} of {download.size()}" 138 | msg += f"\nSpeed: {download.speed()}" \ 139 | f", ETA: {download.eta()} " 140 | # if hasattr(download, 'is_torrent'): 141 | try: 142 | msg += f"\nSeeders: {download.aria_download().num_seeders}" \ 143 | f" | Peers: {download.aria_download().connections}" 144 | except: 145 | pass 146 | try: 147 | msg += f"\nSeeders: {download.torrent_info().num_seeds}" \ 148 | f" | Leechers: {download.torrent_info().num_leechs}" 149 | except: 150 | pass 151 | msg += f'\nUser: {download.message.from_user.first_name} ➡️{download.message.from_user.id}' 152 | msg += f"\nTo Stop: /{BotCommands.CancelMirror} {download.gid()}" 153 | msg += "\n\n" 154 | return msg 155 | 156 | def get_readable_time(seconds: int) -> str: 157 | result = "" 158 | (days, remainder) = divmod(seconds, 86400) 159 | days = int(days) 160 | if days != 0: 161 | result += f"{days}d" 162 | (hours, remainder) = divmod(remainder, 3600) 163 | hours = int(hours) 164 | if hours != 0: 165 | result += f"{hours}h" 166 | (minutes, seconds) = divmod(remainder, 60) 167 | minutes = int(minutes) 168 | if minutes != 0: 169 | result += f"{minutes}m" 170 | seconds = int(seconds) 171 | result += f"{seconds}s" 172 | return result 173 | 174 | 175 | def is_gdrive_link(url: str): 176 | return "drive.google.com" in url 177 | 178 | def is_gdtot_link(url: str): 179 | url = re.match(r'https?://.*\.gdtot\.\S+', url) 180 | return bool(url) 181 | 182 | def is_mega_link(url: str): 183 | return "mega.nz" in url or "mega.co.nz" in url 184 | 185 | 186 | def is_url(url: str): 187 | url = re.findall(URL_REGEX, url) 188 | return bool(url) 189 | 190 | 191 | def is_magnet(url: str): 192 | magnet = re.findall(MAGNET_REGEX, url) 193 | return bool(magnet) 194 | 195 | 196 | def new_thread(fn): 197 | """To use as decorator to make a function call threaded. 198 | Needs import 199 | from threading import Thread""" 200 | 201 | def wrapper(*args, **kwargs): 202 | thread = threading.Thread(target=fn, args=args, kwargs=kwargs) 203 | thread.start() 204 | return thread 205 | 206 | return wrapper 207 | -------------------------------------------------------------------------------- /bot/helper/ext_utils/db_handler.py: -------------------------------------------------------------------------------- 1 | import psycopg2 2 | from psycopg2 import Error 3 | from bot import AUTHORIZED_CHATS, SUDO_USERS, DB_URI, LOGGER 4 | 5 | class DbManger: 6 | def __init__(self): 7 | self.err = False 8 | 9 | def connect(self): 10 | try: 11 | self.conn = psycopg2.connect(DB_URI) 12 | self.cur = self.conn.cursor() 13 | except psycopg2.DatabaseError as error : 14 | LOGGER.error("Error in dbMang : ", error) 15 | self.err = True 16 | 17 | def disconnect(self): 18 | self.cur.close() 19 | self.conn.close() 20 | 21 | def db_auth(self,chat_id: int): 22 | self.connect() 23 | if self.err: 24 | return "There's some error check log for details" 25 | sql = 'INSERT INTO users VALUES ({});'.format(chat_id) 26 | self.cur.execute(sql) 27 | self.conn.commit() 28 | self.disconnect() 29 | AUTHORIZED_CHATS.add(chat_id) 30 | return 'Authorized successfully' 31 | 32 | def db_unauth(self,chat_id: int): 33 | self.connect() 34 | if self.err: 35 | return "There's some error check log for details" 36 | sql = 'DELETE from users where uid = {};'.format(chat_id) 37 | self.cur.execute(sql) 38 | self.conn.commit() 39 | self.disconnect() 40 | AUTHORIZED_CHATS.remove(chat_id) 41 | return 'Unauthorized successfully' 42 | 43 | def db_addsudo(self,chat_id: int): 44 | self.connect() 45 | if self.err: 46 | return "There's some error check log for details" 47 | if chat_id in AUTHORIZED_CHATS: 48 | sql = 'UPDATE users SET sudo = TRUE where uid = {};'.format(chat_id) 49 | self.cur.execute(sql) 50 | self.conn.commit() 51 | self.disconnect() 52 | SUDO_USERS.add(chat_id) 53 | return 'Successfully promoted as Sudo' 54 | else: 55 | sql = 'INSERT INTO users VALUES ({},TRUE);'.format(chat_id) 56 | self.cur.execute(sql) 57 | self.conn.commit() 58 | self.disconnect() 59 | SUDO_USERS.add(chat_id) 60 | return 'Successfully Authorized and promoted as Sudo' 61 | 62 | def db_rmsudo(self,chat_id: int): 63 | self.connect() 64 | if self.err: 65 | return "There's some error check log for details" 66 | sql = 'UPDATE users SET sudo = FALSE where uid = {};'.format(chat_id) 67 | self.cur.execute(sql) 68 | self.conn.commit() 69 | self.disconnect() 70 | SUDO_USERS.remove(chat_id) 71 | return 'Successfully removed from Sudo' -------------------------------------------------------------------------------- /bot/helper/ext_utils/exceptions.py: -------------------------------------------------------------------------------- 1 | class DirectDownloadLinkException(Exception): 2 | """Not method found for extracting direct download link from the http link""" 3 | 4 | 5 | class NotSupportedExtractionArchive(Exception): 6 | """The archive format use is trying to extract is not supported""" 7 | -------------------------------------------------------------------------------- /bot/helper/ext_utils/fs_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import sys 4 | import pathlib 5 | import tarfile 6 | import magic 7 | 8 | from bot import DOWNLOAD_DIR, LOGGER, aria2 9 | import subprocess 10 | import time 11 | 12 | from PIL import Image 13 | from hachoir.parser import createParser 14 | from hachoir.metadata import extractMetadata 15 | from fsplit.filesplit import Filesplit 16 | from bot import aria2, LOGGER, DOWNLOAD_DIR, TG_SPLIT_SIZE 17 | 18 | VIDEO_SUFFIXES = ("M4V", "MP4", "MOV", "FLV", "WMV", "3GP", "MPG", "WEBM", "MKV", "AVI") 19 | 20 | fs = Filesplit() 21 | from .exceptions import NotSupportedExtractionArchive 22 | 23 | 24 | def clean_download(path: str): 25 | if os.path.exists(path): 26 | LOGGER.info(f"Cleaning download: {path}") 27 | shutil.rmtree(path) 28 | 29 | 30 | def start_cleanup(): 31 | try: 32 | shutil.rmtree(DOWNLOAD_DIR) 33 | except FileNotFoundError: 34 | pass 35 | 36 | 37 | def clean_all(): 38 | aria2.remove_all(True) 39 | try: 40 | shutil.rmtree(DOWNLOAD_DIR) 41 | except FileNotFoundError: 42 | pass 43 | 44 | 45 | def exit_clean_up(signal, frame): 46 | try: 47 | LOGGER.info( 48 | "Please wait, while we clean up the downloads and stop running downloads" 49 | ) 50 | clean_all() 51 | sys.exit(0) 52 | except KeyboardInterrupt: 53 | LOGGER.warning("Force Exiting before the cleanup finishes!") 54 | sys.exit(1) 55 | 56 | 57 | def get_path_size(path): 58 | if os.path.isfile(path): 59 | return os.path.getsize(path) 60 | total_size = 0 61 | for root, dirs, files in os.walk(path): 62 | for f in files: 63 | abs_path = os.path.join(root, f) 64 | total_size += os.path.getsize(abs_path) 65 | return total_size 66 | 67 | def tar(org_path): 68 | tar_path = org_path + ".tar" 69 | path = pathlib.PurePath(org_path) 70 | LOGGER.info(f"Tar: orig_path: {org_path}, tar_path: {tar_path}") 71 | tar = tarfile.open(tar_path, "w") 72 | tar.add(org_path, arcname=path.name) 73 | tar.close() 74 | return tar_path 75 | 76 | def get_base_name(orig_path: str): 77 | if orig_path.endswith(".tar.bz2"): 78 | return orig_path.replace(".tar.bz2", "") 79 | elif orig_path.endswith(".tar.gz"): 80 | return orig_path.replace(".tar.gz", "") 81 | elif orig_path.endswith(".bz2"): 82 | return orig_path.replace(".bz2", "") 83 | elif orig_path.endswith(".gz"): 84 | return orig_path.replace(".gz", "") 85 | elif orig_path.endswith(".tar.xz"): 86 | return orig_path.replace(".tar.xz", "") 87 | elif orig_path.endswith(".tar"): 88 | return orig_path.replace(".tar", "") 89 | elif orig_path.endswith(".tbz2"): 90 | return orig_path.replace("tbz2", "") 91 | elif orig_path.endswith(".tgz"): 92 | return orig_path.replace(".tgz", "") 93 | elif orig_path.endswith(".zip"): 94 | return orig_path.replace(".zip", "") 95 | elif orig_path.endswith(".7z"): 96 | return orig_path.replace(".7z", "") 97 | elif orig_path.endswith(".Z"): 98 | return orig_path.replace(".Z", "") 99 | elif orig_path.endswith(".rar"): 100 | return orig_path.replace(".rar", "") 101 | elif orig_path.endswith(".iso"): 102 | return orig_path.replace(".iso", "") 103 | elif orig_path.endswith(".wim"): 104 | return orig_path.replace(".wim", "") 105 | elif orig_path.endswith(".cab"): 106 | return orig_path.replace(".cab", "") 107 | elif orig_path.endswith(".apm"): 108 | return orig_path.replace(".apm", "") 109 | elif orig_path.endswith(".arj"): 110 | return orig_path.replace(".arj", "") 111 | elif orig_path.endswith(".chm"): 112 | return orig_path.replace(".chm", "") 113 | elif orig_path.endswith(".cpio"): 114 | return orig_path.replace(".cpio", "") 115 | elif orig_path.endswith(".cramfs"): 116 | return orig_path.replace(".cramfs", "") 117 | elif orig_path.endswith(".deb"): 118 | return orig_path.replace(".deb", "") 119 | elif orig_path.endswith(".dmg"): 120 | return orig_path.replace(".dmg", "") 121 | elif orig_path.endswith(".fat"): 122 | return orig_path.replace(".fat", "") 123 | elif orig_path.endswith(".hfs"): 124 | return orig_path.replace(".hfs", "") 125 | elif orig_path.endswith(".lzh"): 126 | return orig_path.replace(".lzh", "") 127 | elif orig_path.endswith(".lzma"): 128 | return orig_path.replace(".lzma", "") 129 | elif orig_path.endswith(".lzma2"): 130 | return orig_path.replace(".lzma2", "") 131 | elif orig_path.endswith(".mbr"): 132 | return orig_path.replace(".mbr", "") 133 | elif orig_path.endswith(".msi"): 134 | return orig_path.replace(".msi", "") 135 | elif orig_path.endswith(".mslz"): 136 | return orig_path.replace(".mslz", "") 137 | elif orig_path.endswith(".nsis"): 138 | return orig_path.replace(".nsis", "") 139 | elif orig_path.endswith(".ntfs"): 140 | return orig_path.replace(".ntfs", "") 141 | elif orig_path.endswith(".rpm"): 142 | return orig_path.replace(".rpm", "") 143 | elif orig_path.endswith(".squashfs"): 144 | return orig_path.replace(".squashfs", "") 145 | elif orig_path.endswith(".udf"): 146 | return orig_path.replace(".udf", "") 147 | elif orig_path.endswith(".vhd"): 148 | return orig_path.replace(".vhd", "") 149 | elif orig_path.endswith(".xar"): 150 | return orig_path.replace(".xar", "") 151 | else: 152 | raise NotSupportedExtractionArchive("File format not supported for extraction") 153 | 154 | 155 | def get_mime_type(file_path): 156 | mime = magic.Magic(mime=True) 157 | mime_type = mime.from_file(file_path) 158 | mime_type = mime_type or "text/plain" 159 | return mime_type 160 | def take_ss(video_file): 161 | des_dir = 'Thumbnails' 162 | if not os.path.exists(des_dir): 163 | os.mkdir(des_dir) 164 | des_dir = os.path.join(des_dir, f"{time.time()}.jpg") 165 | metadata = extractMetadata(createParser(video_file)) 166 | duration = metadata.get('duration').seconds if metadata.has("duration") else 5 167 | duration = int(duration) / 2 168 | subprocess.run(["ffmpeg", "-hide_banner", "-loglevel", "error", "-ss", str(duration), 169 | "-i", video_file, "-vframes", "1", des_dir]) 170 | if not os.path.lexists(des_dir): 171 | return None 172 | 173 | Image.open(des_dir).convert("RGB").save(des_dir) 174 | img = Image.open(des_dir) 175 | img.resize((480, 320)) 176 | img.save(des_dir, "JPEG") 177 | return des_dir 178 | 179 | def split(path, size, file, dirpath, split_size, start_time=0, i=1): 180 | if file.upper().endswith(VIDEO_SUFFIXES): 181 | base_name, extension = os.path.splitext(file) 182 | metadata = extractMetadata(createParser(path)) 183 | total_duration = metadata.get('duration').seconds - 8 184 | split_size = split_size - 3000000 185 | while start_time < total_duration: 186 | parted_name = "{}.part{}{}".format(str(base_name), str(i).zfill(3), str(extension)) 187 | out_path = os.path.join(dirpath, parted_name) 188 | subprocess.run(["ffmpeg", "-hide_banner", "-loglevel", "error", "-i", 189 | path, "-ss", str(start_time), "-fs", str(split_size), 190 | "-strict", "-2", "-c", "copy", out_path]) 191 | out_size = get_path_size(out_path) 192 | if out_size > TG_SPLIT_SIZE: 193 | dif = out_size - TG_SPLIT_SIZE 194 | split_size = split_size - dif + 2000000 195 | os.remove(out_path) 196 | return split(path, size, file, dirpath, split_size, start_time, i) 197 | metadata = extractMetadata(createParser(out_path)) 198 | start_time = start_time + metadata.get('duration').seconds - 5 199 | i = i + 1 200 | else: 201 | out_path = os.path.join(dirpath, file + ".") 202 | subprocess.run(["split", "--numeric-suffixes=1", "--suffix-length=3", f"--bytes={split_size}", path, out_path]) -------------------------------------------------------------------------------- /bot/helper/ext_utils/telegraph_helper.py: -------------------------------------------------------------------------------- 1 | # Implement By - @VarnaX-279 2 | 3 | import time 4 | import string 5 | import random 6 | import logging 7 | 8 | from telegraph import Telegraph 9 | from telegraph.exceptions import RetryAfterError 10 | 11 | from bot import LOGGER 12 | 13 | 14 | class TelegraphHelper: 15 | def __init__(self, author_name=None, author_url=None): 16 | self.telegraph = Telegraph() 17 | self.short_name = ''.join(random.SystemRandom().choices(string.ascii_letters, k=8)) 18 | self.access_token = None 19 | self.author_name = author_name 20 | self.author_url = author_url 21 | self.create_account() 22 | 23 | def create_account(self): 24 | self.telegraph.create_account( 25 | short_name=self.short_name, 26 | author_name=self.author_name, 27 | author_url=self.author_url 28 | ) 29 | self.access_token = self.telegraph.get_access_token() 30 | LOGGER.info(f"Creating TELEGRAPH Account using '{self.short_name}' name") 31 | 32 | def create_page(self, title, content): 33 | try: 34 | return self.telegraph.create_page( 35 | title = title, 36 | author_name=self.author_name, 37 | author_url=self.author_url, 38 | html_content=content 39 | ) 40 | except RetryAfterError as st: 41 | LOGGER.warning(f'Telegraph Flood control exceeded. I will sleep for {st.retry_after} seconds.') 42 | time.sleep(st.retry_after) 43 | return self.create_page(title, content) 44 | 45 | def edit_page(self, path, title, content): 46 | try: 47 | return self.telegraph.edit_page( 48 | path = path, 49 | title = title, 50 | author_name=self.author_name, 51 | author_url=self.author_url, 52 | html_content=content 53 | ) 54 | except RetryAfterError as st: 55 | LOGGER.warning(f'Telegraph Flood control exceeded. I will sleep for {st.retry_after} seconds.') 56 | time.sleep(st.retry_after) 57 | return self.edit_page(path, title, content) 58 | 59 | 60 | telegraph=TelegraphHelper('Harsh Mirror Repo', 'https://github.com/harshpreets63/Mirror-Bot') 61 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshpreets63/Mirror-Bot/08cd0fc9ecb8460d964e8970cfa8a1268f6faade/bot/helper/mirror_utils/__init__.py -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshpreets63/Mirror-Bot/08cd0fc9ecb8460d964e8970cfa8a1268f6faade/bot/helper/mirror_utils/download_utils/__init__.py -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/aria2_download.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from time import sleep 3 | 4 | from aria2p import API 5 | 6 | from bot import aria2, download_dict_lock 7 | from bot.helper.ext_utils.bot_utils import ( 8 | LOGGER, 9 | download_dict, 10 | getDownloadByGid, 11 | is_magnet, 12 | new_thread, 13 | ) 14 | from bot.helper.mirror_utils.status_utils.aria_download_status import AriaDownloadStatus 15 | from bot.helper.telegram_helper.message_utils import update_all_messages 16 | 17 | from .download_helper import DownloadHelper 18 | 19 | 20 | class AriaDownloadHelper(DownloadHelper): 21 | def __init__(self): 22 | super().__init__() 23 | 24 | @new_thread 25 | def __onDownloadStarted(self, api, gid): 26 | sleep(1) 27 | LOGGER.info(f"onDownloadStart: {gid}") 28 | download = api.get_download(gid) 29 | self.name = download.name 30 | update_all_messages() 31 | 32 | def __onDownloadComplete(self, api: API, gid): 33 | LOGGER.info(f"onDownloadComplete: {gid}") 34 | dl = getDownloadByGid(gid) 35 | download = api.get_download(gid) 36 | if download.followed_by_ids: 37 | new_gid = download.followed_by_ids[0] 38 | new_download = api.get_download(new_gid) 39 | if dl is None: 40 | dl = getDownloadByGid(new_gid) 41 | with download_dict_lock: 42 | download_dict[dl.uid()] = AriaDownloadStatus(new_gid, dl.getListener()) 43 | if new_download.is_torrent: 44 | download_dict[dl.uid()].is_torrent = True 45 | update_all_messages() 46 | LOGGER.info(f"Changed gid from {gid} to {new_gid}") 47 | elif dl: 48 | threading.Thread(target=dl.getListener().onDownloadComplete).start() 49 | 50 | @new_thread 51 | def __onDownloadPause(self, api, gid): 52 | LOGGER.info(f"onDownloadPause: {gid}") 53 | dl = getDownloadByGid(gid) 54 | dl.getListener().onDownloadError("Download stopped by user!") 55 | 56 | @new_thread 57 | def __onDownloadStopped(self, api, gid): 58 | LOGGER.info(f"onDownloadStop: {gid}") 59 | dl = getDownloadByGid(gid) 60 | if dl: 61 | dl.getListener().onDownloadError("Dead Torrent!") 62 | 63 | @new_thread 64 | def __onDownloadError(self, api, gid): 65 | sleep( 66 | 0.5 67 | ) # sleep for split second to ensure proper dl gid update from onDownloadComplete 68 | LOGGER.info(f"onDownloadError: {gid}") 69 | dl = getDownloadByGid(gid) 70 | download = api.get_download(gid) 71 | error = download.error_message 72 | LOGGER.info(f"Download Error: {error}") 73 | if dl: 74 | dl.getListener().onDownloadError(error) 75 | 76 | def start_listener(self): 77 | aria2.listen_to_notifications( 78 | threaded=True, 79 | on_download_start=self.__onDownloadStarted, 80 | on_download_error=self.__onDownloadError, 81 | on_download_pause=self.__onDownloadPause, 82 | on_download_stop=self.__onDownloadStopped, 83 | on_download_complete=self.__onDownloadComplete, 84 | ) 85 | 86 | def add_download(self, link: str, path, listener, filename): 87 | if is_magnet(link): 88 | download = aria2.add_magnet(link, {"dir": path, "out": filename}) 89 | else: 90 | download = aria2.add_uris([link], {"dir": path, "out": filename}) 91 | if download.error_message: # no need to proceed further at this point 92 | listener.onDownloadError(download.error_message) 93 | return 94 | with download_dict_lock: 95 | download_dict[listener.uid] = AriaDownloadStatus(download.gid, listener) 96 | LOGGER.info(f"Started: {download.gid} DIR:{download.dir} ") 97 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/direct_link_generator_license.md: -------------------------------------------------------------------------------- 1 | RAPHIELSCAPE PUBLIC LICENSE 2 | Version 1.c, June 2019 3 | 4 | Copyright (C) 2019 Raphielscape LLC. 5 | Copyright (C) 2019 Devscapes Open Source Holding GmbH. 6 | 7 | Everyone is permitted to copy and distribute verbatim or modified 8 | copies of this license document, and changing it is allowed as long 9 | as the name is changed. 10 | 11 | RAPHIELSCAPE PUBLIC LICENSE 12 | A-1. DEFINITIONS 13 | 14 | 0. “This License” refers to version 1.c of the Raphielscape Public License. 15 | 16 | 1. “Copyright” also means copyright-like laws that apply to other kinds of works. 17 | 18 | 2. “The Work" refers to any copyrightable work licensed under this License. Each licensee is addressed as “you”. 19 | “Licensees” and “recipients” may be individuals or organizations. 20 | 21 | 3. To “modify” a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, 22 | other than the making of an exact copy. The resulting work is called a “modified version” of the earlier work 23 | or a work “based on” the earlier work. 24 | 25 | 4. Source Form. The “source form” for a work means the preferred form of the work for making modifications to it. 26 | “Object code” means any non-source form of a work. 27 | 28 | The “Corresponding Source” for a work in object code form means all the source code needed to generate, install, and 29 | (for an executable work) run the object code and to modify the work, including scripts to control those activities. 30 | 31 | The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. 32 | The Corresponding Source for a work in source code form is that same work. 33 | 34 | 5. "The author" refers to "author" of the code, which is the one that made the particular code which exists inside of 35 | the Corresponding Source. 36 | 37 | 6. "Owner" refers to any parties which is made the early form of the Corresponding Source. 38 | 39 | A-2. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 40 | 41 | 0. You must give any other recipients of the Work or Derivative Works a copy of this License; and 42 | 43 | 1. You must cause any modified files to carry prominent notices stating that You changed the files; and 44 | 45 | 2. You must retain, in the Source form of any Derivative Works that You distribute, 46 | this license, all copyright, patent, trademark, authorships and attribution notices 47 | from the Source form of the Work; and 48 | 49 | 3. Respecting the author and owner of works that are distributed in any way. 50 | 51 | You may add Your own copyright statement to Your modifications and may provide 52 | additional or different license terms and conditions for use, reproduction, 53 | or distribution of Your modifications, or for any such Derivative Works as a whole, 54 | provided Your use, reproduction, and distribution of the Work otherwise complies 55 | with the conditions stated in this License. 56 | 57 | B. DISCLAIMER OF WARRANTY 58 | 59 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR 60 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 61 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS 62 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 63 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 64 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 65 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 66 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 67 | 68 | 69 | C. REVISED VERSION OF THIS LICENSE 70 | 71 | The Devscapes Open Source Holding GmbH. may publish revised and/or new versions of the 72 | Raphielscape Public License from time to time. Such new versions will be similar in spirit 73 | to the present version, but may differ in detail to address new problems or concerns. 74 | 75 | Each version is given a distinguishing version number. If the Program specifies that a 76 | certain numbered version of the Raphielscape Public License "or any later version" applies to it, 77 | you have the option of following the terms and conditions either of that numbered version or of 78 | any later version published by the Devscapes Open Source Holding GmbH. If the Program does not specify a 79 | version number of the Raphielscape Public License, you may choose any version ever published 80 | by the Devscapes Open Source Holding GmbH. 81 | 82 | END OF LICENSE 83 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/download_helper.py: -------------------------------------------------------------------------------- 1 | # An abstract class which will be inherited by the tool specific classes like aria2_helper or mega_download_helper 2 | import threading 3 | 4 | 5 | class MethodNotImplementedError(NotImplementedError): 6 | def __init__(self): 7 | super(self, "Not implemented method") 8 | 9 | 10 | class DownloadHelper: 11 | def __init__(self): 12 | self.__name = ( 13 | "" # Name of the download; empty string if no download has been started 14 | ) 15 | self.__size = 0.0 # Size of the download 16 | self.downloaded_bytes = 0.0 # Bytes downloaded 17 | self.speed = 0.0 # Download speed in bytes per second 18 | self.progress = 0.0 19 | self.progress_string = "0.00%" 20 | self.eta = 0 # Estimated time of download complete 21 | self.eta_string = "0s" # A listener class which have event callbacks 22 | self._resource_lock = threading.Lock() 23 | 24 | def add_download(self, link: str, path): 25 | raise MethodNotImplementedError 26 | 27 | def cancel_download(self): 28 | # Returns None if successfully cancelled, else error string 29 | raise MethodNotImplementedError 30 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/mega_download.py: -------------------------------------------------------------------------------- 1 | import threading 2 | from pathlib import Path 3 | 4 | from megasdkrestclient import MegaSdkRestClient, constants 5 | 6 | from bot import LOGGER, download_dict, download_dict_lock 7 | from bot.helper.ext_utils.bot_utils import setInterval 8 | 9 | from ..status_utils.mega_status import MegaDownloadStatus 10 | 11 | 12 | class MegaDownloader: 13 | POLLING_INTERVAL = 2 14 | 15 | def __init__(self, listener): 16 | super().__init__() 17 | self.__listener = listener 18 | self.__name = "" 19 | self.__gid = "" 20 | self.__resource_lock = threading.Lock() 21 | self.__mega_client = MegaSdkRestClient("http://localhost:6090") 22 | self.__periodic = None 23 | self.__downloaded_bytes = 0 24 | self.__progress = 0 25 | self.__size = 0 26 | 27 | @property 28 | def progress(self): 29 | with self.__resource_lock: 30 | return self.__progress 31 | 32 | @property 33 | def downloaded_bytes(self): 34 | with self.__resource_lock: 35 | return self.__downloaded_bytes 36 | 37 | @property 38 | def size(self): 39 | with self.__resource_lock: 40 | return self.__size 41 | 42 | @property 43 | def gid(self): 44 | with self.__resource_lock: 45 | return self.__gid 46 | 47 | @property 48 | def name(self): 49 | with self.__resource_lock: 50 | return self.__name 51 | 52 | @property 53 | def download_speed(self): 54 | if self.gid is not None: 55 | return self.__mega_client.getDownloadInfo(self.gid)["speed"] 56 | 57 | def __onDownloadStart(self, name, size, gid): 58 | self.__periodic = setInterval(self.POLLING_INTERVAL, self.__onInterval) 59 | with download_dict_lock: 60 | download_dict[self.__listener.uid] = MegaDownloadStatus( 61 | self, self.__listener 62 | ) 63 | with self.__resource_lock: 64 | self.__name = name 65 | self.__size = size 66 | self.__gid = gid 67 | self.__listener.onDownloadStarted() 68 | 69 | def __onInterval(self): 70 | dlInfo = self.__mega_client.getDownloadInfo(self.gid) 71 | if ( 72 | dlInfo["state"] 73 | in [ 74 | constants.State.TYPE_STATE_COMPLETED, 75 | constants.State.TYPE_STATE_CANCELED, 76 | constants.State.TYPE_STATE_FAILED, 77 | ] 78 | and self.__periodic is not None 79 | ): 80 | self.__periodic.cancel() 81 | if dlInfo["state"] == constants.State.TYPE_STATE_COMPLETED: 82 | self.__onDownloadComplete() 83 | return 84 | if dlInfo["state"] == constants.State.TYPE_STATE_CANCELED: 85 | self.__onDownloadError("Cancelled by user") 86 | return 87 | if dlInfo["state"] == constants.State.TYPE_STATE_FAILED: 88 | self.__onDownloadError(dlInfo["error_string"]) 89 | return 90 | self.__onDownloadProgress(dlInfo["completed_length"], dlInfo["total_length"]) 91 | 92 | def __onDownloadProgress(self, current, total): 93 | with self.__resource_lock: 94 | self.__downloaded_bytes = current 95 | try: 96 | self.__progress = current / total * 100 97 | except ZeroDivisionError: 98 | self.__progress = 0 99 | 100 | def __onDownloadError(self, error): 101 | self.__listener.onDownloadError(error) 102 | 103 | def __onDownloadComplete(self): 104 | self.__listener.onDownloadComplete() 105 | 106 | def add_download(self, link, path): 107 | Path(path).mkdir(parents=True, exist_ok=True) 108 | dl = self.__mega_client.addDl(link, path) 109 | gid = dl["gid"] 110 | info = self.__mega_client.getDownloadInfo(gid) 111 | file_name = info["name"] 112 | file_size = info["total_length"] 113 | self.__onDownloadStart(file_name, file_size, gid) 114 | LOGGER.info(f"Started mega download with gid: {gid}") 115 | 116 | def cancel_download(self): 117 | LOGGER.info(f"Cancelling download on user request: {self.gid}") 118 | self.__mega_client.cancelDl(self.gid) 119 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/telegram_downloader.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import threading 3 | import time 4 | 5 | from bot import LOGGER, app, download_dict, download_dict_lock 6 | 7 | from ..status_utils.telegram_download_status import TelegramDownloadStatus 8 | from .download_helper import DownloadHelper 9 | 10 | global_lock = threading.Lock() 11 | GLOBAL_GID = set() 12 | 13 | logging.getLogger("pyrogram").setLevel(logging.WARNING) 14 | 15 | 16 | class TelegramDownloadHelper(DownloadHelper): 17 | def __init__(self, listener): 18 | super().__init__() 19 | self.__listener = listener 20 | self.__resource_lock = threading.RLock() 21 | self.__name = "" 22 | self.__gid = "" 23 | self.__start_time = time.time() 24 | self._bot = app 25 | self.__is_cancelled = False 26 | 27 | @property 28 | def gid(self): 29 | with self.__resource_lock: 30 | return self.__gid 31 | 32 | @property 33 | def download_speed(self): 34 | with self.__resource_lock: 35 | return self.downloaded_bytes / (time.time() - self.__start_time) 36 | 37 | def __onDownloadStart(self, name, size, file_id): 38 | with download_dict_lock: 39 | download_dict[self.__listener.uid] = TelegramDownloadStatus( 40 | self, self.__listener 41 | ) 42 | with global_lock: 43 | GLOBAL_GID.add(file_id) 44 | with self.__resource_lock: 45 | self.name = name 46 | self.size = size 47 | self.__gid = file_id 48 | self.__listener.onDownloadStarted() 49 | 50 | def __onDownloadProgress(self, current, total): 51 | if self.__is_cancelled: 52 | self.__onDownloadError("Cancelled by user!") 53 | self._bot.stop_transmission() 54 | return 55 | with self.__resource_lock: 56 | self.downloaded_bytes = current 57 | try: 58 | self.progress = current / self.size * 100 59 | except ZeroDivisionError: 60 | self.progress = 0 61 | 62 | def __onDownloadError(self, error): 63 | with global_lock: 64 | try: 65 | GLOBAL_GID.remove(self.gid) 66 | except KeyError: 67 | pass 68 | self.__listener.onDownloadError(error) 69 | 70 | def __onDownloadComplete(self): 71 | with global_lock: 72 | GLOBAL_GID.remove(self.gid) 73 | self.__listener.onDownloadComplete() 74 | 75 | def __download(self, message, path): 76 | try: 77 | download = self._bot.download_media(message, 78 | progress = self.__onDownloadProgress, 79 | file_name = path 80 | ) 81 | except Exception as e: 82 | LOGGER.error(str(e)) 83 | return self.__onDownloadError(str(e)) 84 | if download is not None: 85 | self.__onDownloadComplete() 86 | elif not self.__is_cancelled: 87 | self.__onDownloadError("Internal error occurred") 88 | 89 | def add_download(self, message, path, filename): 90 | _message = self._bot.get_messages(message.chat.id, reply_to_message_ids=message.message_id) 91 | media = None 92 | media_array = [_message.document, _message.video, _message.audio] 93 | for i in media_array: 94 | if i is not None: 95 | media = i 96 | break 97 | if media is not None: 98 | with global_lock: 99 | # For avoiding locking the thread lock for long time unnecessarily 100 | download = media.file_id not in GLOBAL_GID 101 | if filename == "": 102 | name = media.file_name 103 | else: 104 | name = filename 105 | path = path + name 106 | if download: 107 | self.__onDownloadStart(name, media.file_size, media.file_id) 108 | LOGGER.info(f"Downloading telegram file with id: {media.file_id}") 109 | threading.Thread(target=self.__download, args=(_message, path)).start() 110 | else: 111 | self.__onDownloadError("File already being downloaded!") 112 | else: 113 | self.__onDownloadError("No document in the replied message") 114 | 115 | def cancel_download(self): 116 | LOGGER.info(f"Cancelling download on user request: {self.gid}") 117 | self.__is_cancelled = True 118 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/youtube_dl_download_helper.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | import time 4 | import logging 5 | import re 6 | import threading 7 | 8 | from .download_helper import DownloadHelper 9 | from yt_dlp import YoutubeDL, DownloadError 10 | from bot import download_dict_lock, download_dict 11 | from bot.helper.telegram_helper.message_utils import sendStatusMessage 12 | from ..status_utils.youtube_dl_download_status import YoutubeDLDownloadStatus 13 | 14 | LOGGER = logging.getLogger(__name__) 15 | 16 | 17 | class MyLogger: 18 | def __init__(self, obj): 19 | self.obj = obj 20 | 21 | def debug(self, msg): 22 | # Hack to fix changing extension 23 | match = re.search(r'.Merger..Merging formats into..(.*?).$', msg) # To mkv 24 | if not match and not self.obj.is_playlist: 25 | match = re.search(r'.ExtractAudio..Destination..(.*?)$', msg) # To mp3 26 | if match and not self.obj.is_playlist: 27 | newname = match.group(1) 28 | newname = newname.split("/")[-1] 29 | self.obj.name = newname 30 | 31 | @staticmethod 32 | def warning(msg): 33 | LOGGER.warning(msg) 34 | 35 | @staticmethod 36 | def error(msg): 37 | if msg != "ERROR: Cancelling...": 38 | LOGGER.error(msg) 39 | 40 | 41 | class YoutubeDLHelper(DownloadHelper): 42 | def __init__(self, listener): 43 | super().__init__() 44 | self.name = "" 45 | self.__start_time = time.time() 46 | self.__listener = listener 47 | self.__gid = "" 48 | self.__download_speed = 0 49 | self.downloaded_bytes = 0 50 | self.size = 0 51 | self.is_playlist = False 52 | self.last_downloaded = 0 53 | self.is_cancelled = False 54 | self.downloading = False 55 | self.__resource_lock = threading.RLock() 56 | self.opts = {'progress_hooks': [self.__onDownloadProgress], 57 | 'logger': MyLogger(self), 58 | 'usenetrc': True, 59 | 'embedsubtitles': True, 60 | 'prefer_ffmpeg': True, 61 | 'cookiefile': 'cookies.txt'} 62 | 63 | @property 64 | def download_speed(self): 65 | with self.__resource_lock: 66 | return self.__download_speed 67 | 68 | @property 69 | def gid(self): 70 | with self.__resource_lock: 71 | return self.__gid 72 | 73 | def __onDownloadProgress(self, d): 74 | self.downloading = True 75 | if self.is_cancelled: 76 | raise ValueError("Cancelling...") 77 | if d['status'] == "finished": 78 | if self.is_playlist: 79 | self.last_downloaded = 0 80 | elif d['status'] == "downloading": 81 | with self.__resource_lock: 82 | self.__download_speed = d['speed'] 83 | try: 84 | tbyte = d['total_bytes'] 85 | except KeyError: 86 | tbyte = d['total_bytes_estimate'] 87 | if self.is_playlist: 88 | downloadedBytes = d['downloaded_bytes'] 89 | chunk_size = downloadedBytes - self.last_downloaded 90 | self.last_downloaded = downloadedBytes 91 | self.downloaded_bytes += chunk_size 92 | else: 93 | self.size = tbyte 94 | self.downloaded_bytes = d['downloaded_bytes'] 95 | try: 96 | self.progress = (self.downloaded_bytes / self.size) * 100 97 | except ZeroDivisionError: 98 | pass 99 | 100 | def __onDownloadStart(self): 101 | with download_dict_lock: 102 | download_dict[self.__listener.uid] = YoutubeDLDownloadStatus(self, self.__listener) 103 | 104 | def __onDownloadComplete(self): 105 | self.__listener.onDownloadComplete() 106 | 107 | def onDownloadError(self, error): 108 | self.__listener.onDownloadError(error) 109 | 110 | def extractMetaData(self, link, name, get_info=False): 111 | 112 | if get_info: 113 | self.opts['playlist_items'] = '0' 114 | with YoutubeDL(self.opts) as ydl: 115 | try: 116 | result = ydl.extract_info(link, download=False) 117 | if get_info: 118 | return result 119 | realName = ydl.prepare_filename(result) 120 | except DownloadError as e: 121 | if get_info: 122 | raise e 123 | self.onDownloadError(str(e)) 124 | return 125 | 126 | if 'entries' in result: 127 | for v in result['entries']: 128 | try: 129 | self.size += v['filesize_approx'] 130 | except (KeyError, TypeError): 131 | pass 132 | self.is_playlist = True 133 | if name == "": 134 | self.name = str(realName).split(f" [{result['id']}]")[0] 135 | else: 136 | self.name = name 137 | else: 138 | ext = realName.split('.')[-1] 139 | if name == "": 140 | self.name = str(realName).split(f" [{result['id']}]")[0] + '.' + ext 141 | else: 142 | self.name = f"{name}.{ext}" 143 | 144 | def __download(self, link): 145 | try: 146 | with YoutubeDL(self.opts) as ydl: 147 | ydl.download([link]) 148 | if self.is_cancelled: 149 | raise ValueError 150 | self.__onDownloadComplete() 151 | except DownloadError as e: 152 | self.onDownloadError(str(e)) 153 | except ValueError: 154 | self.onDownloadError("Download Stopped by User!") 155 | 156 | def add_download(self, link, path, name, qual, playlist): 157 | if playlist == "true": 158 | self.opts['ignoreerrors'] = True 159 | if "hotstar" in link or "sonyliv" in link: 160 | self.opts['geo_bypass_country'] = 'IN' 161 | self.__gid = ''.join(random.SystemRandom().choices(string.ascii_letters + string.digits, k=10)) 162 | self.__onDownloadStart() 163 | sendStatusMessage(self.__listener.update, self.__listener.bot) 164 | self.opts['format'] = qual 165 | if qual == 'ba/b': 166 | self.opts['postprocessors'] = [{'key': 'FFmpegExtractAudio','preferredcodec': 'mp3','preferredquality': '340'}] 167 | LOGGER.info(f"Downloading with YT-DL: {link}") 168 | self.extractMetaData(link, name) 169 | if self.is_cancelled: 170 | return 171 | if not self.is_playlist: 172 | self.opts['outtmpl'] = f"{path}/{self.name}" 173 | else: 174 | self.opts['outtmpl'] = f"{path}/{self.name}/%(title)s.%(ext)s" 175 | self.__download(link) 176 | 177 | def cancel_download(self): 178 | self.is_cancelled = True 179 | if not self.downloading: 180 | self.onDownloadError("Download Cancelled by User!") -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshpreets63/Mirror-Bot/08cd0fc9ecb8460d964e8970cfa8a1268f6faade/bot/helper/mirror_utils/status_utils/__init__.py -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/aria_download_status.py: -------------------------------------------------------------------------------- 1 | from bot import DOWNLOAD_DIR, LOGGER, aria2 2 | from bot.helper.ext_utils.bot_utils import MirrorStatus 3 | 4 | from .status import Status 5 | 6 | 7 | def get_download(gid): 8 | return aria2.get_download(gid) 9 | 10 | 11 | class AriaDownloadStatus(Status): 12 | def __init__(self, gid, listener): 13 | super().__init__() 14 | self.upload_name = None 15 | self.is_archiving = False 16 | self.__gid = gid 17 | self.__download = get_download(self.__gid) 18 | self.__uid = listener.uid 19 | self.__listener = listener 20 | self.message = listener.message 21 | self.last = None 22 | self.is_waiting = False 23 | self.is_extracting = False 24 | 25 | def __update(self): 26 | self.__download = get_download(self.__gid) 27 | download = self.__download 28 | if download.followed_by_ids: 29 | self.__gid = download.followed_by_ids[0] 30 | 31 | def progress(self): 32 | """ 33 | Calculates the progress of the mirror (upload or download) 34 | :return: returns progress in percentage 35 | """ 36 | self.__update() 37 | return self.__download.progress_string() 38 | 39 | def size_raw(self): 40 | """ 41 | Gets total size of the mirror file/folder 42 | :return: total size of mirror 43 | """ 44 | return self.aria_download().total_length 45 | 46 | def processed_bytes(self): 47 | return self.aria_download().completed_length 48 | 49 | def speed(self): 50 | return self.aria_download().download_speed_string() 51 | 52 | def name(self): 53 | return self.aria_download().name 54 | 55 | def path(self): 56 | return f"{DOWNLOAD_DIR}{self.__uid}" 57 | 58 | def size(self): 59 | return self.aria_download().total_length_string() 60 | 61 | def eta(self): 62 | return self.aria_download().eta_string() 63 | 64 | def status(self): 65 | download = self.aria_download() 66 | if download.is_waiting: 67 | return MirrorStatus.STATUS_WAITING 68 | elif download.is_paused: 69 | return MirrorStatus.STATUS_CANCELLED 70 | elif download.has_failed: 71 | return MirrorStatus.STATUS_FAILED 72 | else: 73 | return MirrorStatus.STATUS_DOWNLOADING 74 | 75 | def aria_download(self): 76 | self.__update() 77 | return self.__download 78 | 79 | def download(self): 80 | return self 81 | 82 | def updateName(self, name): 83 | self.__name = name 84 | 85 | def updateGid(self, gid): 86 | self.__gid = gid 87 | 88 | def getListener(self): 89 | return self.__listener 90 | 91 | def uid(self): 92 | return self.__uid 93 | 94 | def gid(self): 95 | self.__update() 96 | return self.__gid 97 | 98 | def cancel_download(self): 99 | LOGGER.info(f"Cancelling Download: {self.name()}") 100 | download = self.aria_download() 101 | if download.is_waiting: 102 | self.__listener.onDownloadError("Cancelled by user") 103 | aria2.remove([download], force=True) 104 | return 105 | if len(download.followed_by_ids) != 0: 106 | downloads = aria2.get_downloads(download.followed_by_ids) 107 | self.__listener.onDownloadError('Download stopped by user!') 108 | aria2.remove(downloads, force=True) 109 | aria2.remove([download], force=True) 110 | return 111 | self.__listener.onDownloadError('Download stopped by user!') 112 | aria2.remove([download], force=True) 113 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/clone_status.py: -------------------------------------------------------------------------------- 1 | # All rights reserved 2 | 3 | from .status import Status 4 | from bot.helper.ext_utils.bot_utils import MirrorStatus, get_readable_file_size, get_readable_time 5 | 6 | 7 | class CloneStatus(Status): 8 | def __init__(self, obj, size, update, gid): 9 | self.cobj = obj 10 | self.__csize = size 11 | self.message = update.message 12 | self.__cgid = gid 13 | 14 | def processed_bytes(self): 15 | return self.cobj.transferred_size 16 | 17 | def size_raw(self): 18 | return self.__csize 19 | 20 | def size(self): 21 | return get_readable_file_size(self.__csize) 22 | 23 | def status(self): 24 | return MirrorStatus.STATUS_CLONING 25 | 26 | def name(self): 27 | return self.cobj.name 28 | 29 | def gid(self) -> str: 30 | return self.__cgid 31 | 32 | def progress_raw(self): 33 | try: 34 | return self.cobj.transferred_size / self.__csize * 100 35 | except ZeroDivisionError: 36 | return 0 37 | 38 | def progress(self): 39 | return f'{round(self.progress_raw(), 2)}%' 40 | 41 | def speed_raw(self): 42 | """ 43 | :return: Download speed in Bytes/Seconds 44 | """ 45 | return self.cobj.cspeed() 46 | 47 | def speed(self): 48 | return f'{get_readable_file_size(self.speed_raw())}/s' 49 | 50 | def eta(self): 51 | try: 52 | seconds = (self.__csize - self.cobj.transferred_size) / self.speed_raw() 53 | return f'{get_readable_time(seconds)}' 54 | except ZeroDivisionError: 55 | return '-' 56 | 57 | def download(self): 58 | return self.cobj 59 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/extract_status.py: -------------------------------------------------------------------------------- 1 | from bot.helper.ext_utils.bot_utils import MirrorStatus, get_readable_file_size 2 | 3 | from .status import Status 4 | 5 | 6 | class ExtractStatus(Status): 7 | def __init__(self, name, path, size): 8 | self.__name = name 9 | self.__path = path 10 | self.__size = size 11 | 12 | # The progress of extract function cannot be tracked. So we just return dummy values. 13 | # If this is possible in future,we should implement it 14 | 15 | def progress(self): 16 | return "0" 17 | 18 | def speed(self): 19 | return "0" 20 | 21 | def name(self): 22 | return self.__name 23 | 24 | def path(self): 25 | return self.__path 26 | 27 | def size(self): 28 | return get_readable_file_size(self.__size) 29 | 30 | def eta(self): 31 | return "0s" 32 | 33 | def status(self): 34 | return MirrorStatus.STATUS_EXTRACTING 35 | 36 | def processed_bytes(self): 37 | return 0 38 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/gdownload_status.py: -------------------------------------------------------------------------------- 1 | from bot import DOWNLOAD_DIR 2 | from bot.helper.ext_utils.bot_utils import ( 3 | MirrorStatus, 4 | get_readable_file_size, 5 | get_readable_time, 6 | ) 7 | 8 | from .status import Status 9 | 10 | 11 | class DownloadStatus(Status): 12 | def __init__(self, obj, size, listener, gid): 13 | self.dobj = obj 14 | self.__dsize = size 15 | self.uid = listener.uid 16 | self.message = listener.message 17 | self.__dgid = gid 18 | 19 | def path(self): 20 | return f"{DOWNLOAD_DIR}{self.uid}" 21 | 22 | def processed_bytes(self): 23 | return self.dobj.downloaded_bytes 24 | 25 | def size_raw(self): 26 | return self.__dsize 27 | 28 | def size(self): 29 | return get_readable_file_size(self.__dsize) 30 | 31 | def status(self): 32 | return MirrorStatus.STATUS_DOWNLOADING 33 | 34 | def name(self): 35 | return self.dobj.name 36 | 37 | def gid(self) -> str: 38 | return self.__dgid 39 | 40 | def progress_raw(self): 41 | try: 42 | return self.dobj.downloaded_bytes / self.__dsize * 100 43 | except ZeroDivisionError: 44 | return 0 45 | 46 | def progress(self): 47 | return f"{round(self.progress_raw(), 2)}%" 48 | 49 | def speed_raw(self): 50 | """ 51 | :return: Download speed in Bytes/Seconds 52 | """ 53 | return self.dobj.dspeed() 54 | 55 | def speed(self): 56 | return f"{get_readable_file_size(self.speed_raw())}/s" 57 | 58 | def eta(self): 59 | try: 60 | seconds = (self.__dsize - self.dobj.downloaded_bytes) / self.speed_raw() 61 | return f"{get_readable_time(seconds)}" 62 | except ZeroDivisionError: 63 | return "-" 64 | 65 | def download(self): 66 | return self.dobj 67 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/listeners.py: -------------------------------------------------------------------------------- 1 | class MirrorListeners: 2 | def __init__(self, context, update): 3 | self.bot = context 4 | self.update = update 5 | self.message = update.message 6 | self.uid = self.message.message_id 7 | 8 | def onDownloadStarted(self): 9 | raise NotImplementedError 10 | 11 | def onDownloadProgress(self): 12 | raise NotImplementedError 13 | 14 | def onDownloadComplete(self): 15 | raise NotImplementedError 16 | 17 | def onDownloadError(self, error: str): 18 | raise NotImplementedError 19 | 20 | def onUploadStarted(self): 21 | raise NotImplementedError 22 | 23 | def onUploadProgress(self): 24 | raise NotImplementedError 25 | 26 | def onUploadComplete(self, link: str): 27 | raise NotImplementedError 28 | 29 | def onUploadError(self, error: str): 30 | raise NotImplementedError 31 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/mega_status.py: -------------------------------------------------------------------------------- 1 | from bot import DOWNLOAD_DIR 2 | from bot.helper.ext_utils.bot_utils import ( 3 | MirrorStatus, 4 | get_readable_file_size, 5 | get_readable_time, 6 | ) 7 | 8 | from .status import Status 9 | 10 | 11 | class MegaDownloadStatus(Status): 12 | def __init__(self, obj, listener): 13 | self.obj = obj 14 | self.uid = listener.uid 15 | self.message = listener.message 16 | 17 | def gid(self): 18 | return self.obj.gid 19 | 20 | def path(self): 21 | return f"{DOWNLOAD_DIR}{self.uid}" 22 | 23 | def processed_bytes(self): 24 | return self.obj.downloaded_bytes 25 | 26 | def size_raw(self): 27 | return self.obj.size 28 | 29 | def size(self): 30 | return get_readable_file_size(self.size_raw()) 31 | 32 | def status(self): 33 | return MirrorStatus.STATUS_DOWNLOADING 34 | 35 | def name(self): 36 | return self.obj.name 37 | 38 | def progress_raw(self): 39 | return self.obj.progress 40 | 41 | def progress(self): 42 | return f"{round(self.progress_raw(), 2)}%" 43 | 44 | def speed_raw(self): 45 | """ 46 | :return: Download speed in Bytes/Seconds 47 | """ 48 | return self.obj.download_speed 49 | 50 | def speed(self): 51 | return f"{get_readable_file_size(self.speed_raw())}/s" 52 | 53 | def eta(self): 54 | try: 55 | seconds = (self.size_raw() - self.processed_bytes()) / self.speed_raw() 56 | return f"{get_readable_time(seconds)}" 57 | except ZeroDivisionError: 58 | return "-" 59 | 60 | def download(self): 61 | return self.obj 62 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/split_status.py: -------------------------------------------------------------------------------- 1 | # Implement By - @anasty17 (https://github.com/SlamDevs/slam-mirrorbot/commit/d888a1e7237f4633c066f7c2bbfba030b83ad616) 2 | # (c) https://github.com/SlamDevs/slam-mirrorbot 3 | # All rights reserved 4 | 5 | from .status import Status 6 | from bot.helper.ext_utils.bot_utils import get_readable_file_size, MirrorStatus 7 | 8 | 9 | class SplitStatus(Status): 10 | def __init__(self, name, path, size): 11 | self.__name = name 12 | self.__path = path 13 | self.__size = size 14 | 15 | # The progress of Tar function cannot be tracked. So we just return dummy values. 16 | # If this is possible in future,we should implement it 17 | 18 | def progress(self): 19 | return '0' 20 | 21 | def speed(self): 22 | return '0' 23 | 24 | def name(self): 25 | return self.__name 26 | 27 | def path(self): 28 | return self.__path 29 | 30 | def size(self): 31 | return get_readable_file_size(self.__size) 32 | 33 | def eta(self): 34 | return '0s' 35 | 36 | def status(self): 37 | return MirrorStatus.STATUS_SPLITTING 38 | 39 | def processed_bytes(self): 40 | return 0 41 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/status.py: -------------------------------------------------------------------------------- 1 | # Generic status class. All other status classes must inherit this class 2 | 3 | 4 | class Status: 5 | def progress(self): 6 | """ 7 | Calculates the progress of the mirror (upload or download) 8 | :return: progress in percentage 9 | """ 10 | raise NotImplementedError 11 | 12 | def speed(self): 13 | """:return: speed in bytes per second""" 14 | raise NotImplementedError 15 | 16 | def name(self): 17 | """:return name of file/directory being processed""" 18 | raise NotImplementedError 19 | 20 | def path(self): 21 | """:return path of the file/directory""" 22 | raise NotImplementedError 23 | 24 | def size(self): 25 | """:return Size of file folder""" 26 | raise NotImplementedError 27 | 28 | def eta(self): 29 | """:return ETA of the process to complete""" 30 | raise NotImplementedError 31 | 32 | def status(self): 33 | """:return String describing what is the object of this class will be tracking (upload/download/something 34 | else)""" 35 | raise NotImplementedError 36 | 37 | def processed_bytes(self): 38 | """:return The size of file that has been processed (downloaded/uploaded/archived)""" 39 | raise NotImplementedError 40 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/tar_status.py: -------------------------------------------------------------------------------- 1 | from bot.helper.ext_utils.bot_utils import MirrorStatus, get_readable_file_size 2 | 3 | from .status import Status 4 | 5 | 6 | class TarStatus(Status): 7 | def __init__(self, name, path, size): 8 | self.__name = name 9 | self.__path = path 10 | self.__size = size 11 | 12 | # The progress of zip function cannot be tracked. So we just return dummy values. 13 | # If this is possible in future,we should implement it 14 | 15 | def progress(self): 16 | return "0" 17 | 18 | def speed(self): 19 | return "0" 20 | 21 | def name(self): 22 | return self.__name 23 | 24 | def path(self): 25 | return self.__path 26 | 27 | def size(self): 28 | return get_readable_file_size(self.__size) 29 | 30 | def eta(self): 31 | return "0s" 32 | 33 | def status(self): 34 | return MirrorStatus.STATUS_ARCHIVING 35 | 36 | def processed_bytes(self): 37 | return 0 38 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/telegram_download_status.py: -------------------------------------------------------------------------------- 1 | from bot import DOWNLOAD_DIR 2 | from bot.helper.ext_utils.bot_utils import ( 3 | MirrorStatus, 4 | get_readable_file_size, 5 | get_readable_time, 6 | ) 7 | 8 | from .status import Status 9 | 10 | 11 | class TelegramDownloadStatus(Status): 12 | def __init__(self, obj, listener): 13 | self.obj = obj 14 | self.uid = listener.uid 15 | self.message = listener.message 16 | 17 | def gid(self): 18 | return self.obj.gid 19 | 20 | def path(self): 21 | return f"{DOWNLOAD_DIR}{self.uid}" 22 | 23 | def processed_bytes(self): 24 | return self.obj.downloaded_bytes 25 | 26 | def size_raw(self): 27 | return self.obj.size 28 | 29 | def size(self): 30 | return get_readable_file_size(self.size_raw()) 31 | 32 | def status(self): 33 | return MirrorStatus.STATUS_DOWNLOADING 34 | 35 | def name(self): 36 | return self.obj.name 37 | 38 | def progress_raw(self): 39 | return self.obj.progress 40 | 41 | def progress(self): 42 | return f"{round(self.progress_raw(), 2)}%" 43 | 44 | def speed_raw(self): 45 | """ 46 | :return: Download speed in Bytes/Seconds 47 | """ 48 | return self.obj.download_speed 49 | 50 | def speed(self): 51 | return f"{get_readable_file_size(self.speed_raw())}/s" 52 | 53 | def eta(self): 54 | try: 55 | seconds = (self.size_raw() - self.processed_bytes()) / self.speed_raw() 56 | return f"{get_readable_time(seconds)}" 57 | except ZeroDivisionError: 58 | return "-" 59 | 60 | def download(self): 61 | return self.obj 62 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/tg_upload_status.py: -------------------------------------------------------------------------------- 1 | # Implement By - @anasty17 (https://github.com/SlamDevs/slam-mirrorbot/commit/d888a1e7237f4633c066f7c2bbfba030b83ad616) 2 | # (c) https://github.com/SlamDevs/slam-mirrorbot 3 | # All rights reserved 4 | 5 | from .status import Status 6 | from bot.helper.ext_utils.bot_utils import MirrorStatus, get_readable_file_size, get_readable_time 7 | from bot import DOWNLOAD_DIR 8 | 9 | 10 | class TgUploadStatus(Status): 11 | def __init__(self, obj, size, gid, listener): 12 | self.obj = obj 13 | self.__size = size 14 | self.uid = listener.uid 15 | self.message = listener.message 16 | self.__gid = gid 17 | 18 | def path(self): 19 | return f"{DOWNLOAD_DIR}{self.uid}" 20 | 21 | def processed_bytes(self): 22 | return self.obj.uploaded_bytes 23 | 24 | def size_raw(self): 25 | return self.__size 26 | 27 | def size(self): 28 | return get_readable_file_size(self.__size) 29 | 30 | def status(self): 31 | return MirrorStatus.STATUS_UPLOADING 32 | 33 | def name(self): 34 | return self.obj.name 35 | 36 | def progress_raw(self): 37 | try: 38 | return self.obj.uploaded_bytes / self.__size * 100 39 | except ZeroDivisionError: 40 | return 0 41 | 42 | def progress(self): 43 | return f'{round(self.progress_raw(), 2)}%' 44 | 45 | def speed_raw(self): 46 | """ 47 | :return: Upload speed in Bytes/Seconds 48 | """ 49 | return self.obj.speed() 50 | 51 | def speed(self): 52 | return f'{get_readable_file_size(self.speed_raw())}/s' 53 | 54 | def eta(self): 55 | try: 56 | seconds = (self.__size - self.obj.uploaded_bytes) / self.speed_raw() 57 | return f'{get_readable_time(seconds)}' 58 | except ZeroDivisionError: 59 | return '-' 60 | 61 | 62 | def download(self): 63 | return self.obj 64 | 65 | def gid(self) -> str: 66 | return self.__gid -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/upload_status.py: -------------------------------------------------------------------------------- 1 | from bot import DOWNLOAD_DIR 2 | from bot.helper.ext_utils.bot_utils import ( 3 | MirrorStatus, 4 | get_readable_file_size, 5 | get_readable_time, 6 | ) 7 | 8 | from .status import Status 9 | 10 | 11 | class UploadStatus(Status): 12 | def __init__(self, obj, size, gid, listener): 13 | self.obj = obj 14 | self.__size = size 15 | self.uid = listener.uid 16 | self.message = listener.message 17 | self.__gid = gid 18 | 19 | def path(self): 20 | return f"{DOWNLOAD_DIR}{self.uid}" 21 | 22 | def processed_bytes(self): 23 | return self.obj.uploaded_bytes 24 | 25 | def size_raw(self): 26 | return self.__size 27 | 28 | def size(self): 29 | return get_readable_file_size(self.__size) 30 | 31 | def status(self): 32 | return MirrorStatus.STATUS_UPLOADING 33 | 34 | def name(self): 35 | return self.obj.name 36 | 37 | def progress_raw(self): 38 | try: 39 | return self.obj.uploaded_bytes / self.__size * 100 40 | except ZeroDivisionError: 41 | return 0 42 | 43 | def progress(self): 44 | return f"{round(self.progress_raw(), 2)}%" 45 | 46 | def speed_raw(self): 47 | """ 48 | :return: Upload speed in Bytes/Seconds 49 | """ 50 | return self.obj.speed() 51 | 52 | def speed(self): 53 | return f"{get_readable_file_size(self.speed_raw())}/s" 54 | 55 | def eta(self): 56 | try: 57 | seconds = (self.__size - self.obj.uploaded_bytes) / self.speed_raw() 58 | return f"{get_readable_time(seconds)}" 59 | except ZeroDivisionError: 60 | return "-" 61 | 62 | def gid(self) -> str: 63 | return self.__gid 64 | 65 | def download(self): 66 | return self.obj -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/youtube_dl_download_status.py: -------------------------------------------------------------------------------- 1 | from bot import DOWNLOAD_DIR 2 | from bot.helper.ext_utils.bot_utils import MirrorStatus, get_readable_file_size, get_readable_time 3 | from .status import Status 4 | from bot.helper.ext_utils.fs_utils import get_path_size 5 | 6 | class YoutubeDLDownloadStatus(Status): 7 | def __init__(self, obj, listener): 8 | self.obj = obj 9 | self.uid = listener.uid 10 | self.message = listener.message 11 | 12 | def gid(self): 13 | return self.obj.gid 14 | 15 | def path(self): 16 | return f"{DOWNLOAD_DIR}{self.uid}" 17 | 18 | def processed_bytes(self): 19 | if self.obj.downloaded_bytes != 0: 20 | return self.obj.downloaded_bytes 21 | else: 22 | return get_path_size(f"{DOWNLOAD_DIR}{self.uid}") 23 | 24 | def size_raw(self): 25 | return self.obj.size 26 | 27 | def size(self): 28 | return get_readable_file_size(self.size_raw()) 29 | 30 | def status(self): 31 | return MirrorStatus.STATUS_DOWNLOADING 32 | 33 | def name(self): 34 | return self.obj.name 35 | 36 | def progress_raw(self): 37 | return self.obj.progress 38 | 39 | def progress(self): 40 | return f'{round(self.progress_raw(), 2)}%' 41 | 42 | def speed_raw(self): 43 | """ 44 | :return: Download speed in Bytes/Seconds 45 | """ 46 | return self.obj.download_speed 47 | 48 | def speed(self): 49 | return f'{get_readable_file_size(self.speed_raw())}/s' 50 | 51 | def eta(self): 52 | try: 53 | seconds = (self.size_raw() - self.processed_bytes()) / self.speed_raw() 54 | return f'{get_readable_time(seconds)}' 55 | except: 56 | return '-' 57 | 58 | def download(self): 59 | return self.obj -------------------------------------------------------------------------------- /bot/helper/mirror_utils/upload_utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshpreets63/Mirror-Bot/08cd0fc9ecb8460d964e8970cfa8a1268f6faade/bot/helper/mirror_utils/upload_utils/__init__.py -------------------------------------------------------------------------------- /bot/helper/mirror_utils/upload_utils/pyrogramEngine.py: -------------------------------------------------------------------------------- 1 | # Implement By - @anasty17 (https://github.com/SlamDevs/slam-mirrorbot/commit/d888a1e7237f4633c066f7c2bbfba030b83ad616) 2 | # (c) https://github.com/SlamDevs/slam-mirrorbot 3 | # All rights reserved 4 | 5 | import os 6 | import logging 7 | import time 8 | 9 | from pyrogram.errors import FloodWait, RPCError 10 | from hachoir.parser import createParser 11 | from hachoir.metadata import extractMetadata 12 | 13 | from bot import app, DOWNLOAD_DIR, AS_DOCUMENT, AS_DOC_USERS, AS_MEDIA_USERS, LOGS_CHATS 14 | from bot.helper.ext_utils.fs_utils import take_ss 15 | 16 | LOGGER = logging.getLogger(__name__) 17 | logging.getLogger("pyrogram").setLevel(logging.WARNING) 18 | 19 | VIDEO_SUFFIXES = ("MKV", "MP4", "MOV", "WMV", "3GP", "MPG", "WEBM", "AVI", "FLV", "M4V") 20 | AUDIO_SUFFIXES = ("MP3", "M4A", "M4B", "FLAC", "WAV", "AIF", "OGG", "AAC", "DTS", "MID", "AMR", "MKA") 21 | IMAGE_SUFFIXES = ("JPG", "JPX", "PNG", "GIF", "WEBP", "CR2", "TIF", "BMP", "JXR", "PSD", "ICO", "HEIC") 22 | 23 | 24 | class TgUploader: 25 | 26 | def __init__(self, name=None, listener=None): 27 | self.__listener = listener 28 | self.name = name 29 | self.__app = app 30 | self.total_bytes = 0 31 | self.uploaded_bytes = 0 32 | self.last_uploaded = 0 33 | self.start_time = time.time() 34 | self.is_cancelled = False 35 | self.chat_id = listener.message.chat.id 36 | self.message_id = listener.uid 37 | self.user_id = listener.message.from_user.id 38 | self.as_doc = AS_DOCUMENT 39 | self.thumb = f"Thumbnails/{self.user_id}.jpg" 40 | self.sent_msg = self.__app.get_messages(self.chat_id, self.message_id) 41 | self.corrupted = 0 42 | 43 | def upload(self): 44 | msgs_dict = {} 45 | path = f"{DOWNLOAD_DIR}{self.message_id}" 46 | self.user_settings() 47 | for dirpath, subdir, files in sorted(os.walk(path)): 48 | for file in sorted(files): 49 | if self.is_cancelled: 50 | return 51 | up_path = os.path.join(dirpath, file) 52 | fsize = os.path.getsize(up_path) 53 | if fsize == 0: 54 | LOGGER.error(f"{up_path} size is zero, telegram don't upload zero size files") 55 | self.corrupted += 1 56 | continue 57 | self.upload_file(up_path, file, dirpath) 58 | if self.is_cancelled: 59 | return 60 | msgs_dict[file] = self.sent_msg.message_id 61 | self.last_uploaded = 0 62 | if len(msgs_dict) <= self.corrupted: 63 | return self.__listener.onUploadError('Files Corrupted. Check logs') 64 | LOGGER.info(f"Leech Done: {self.name}") 65 | self.__listener.onUploadComplete(self.name, None, msgs_dict, None, self.corrupted) 66 | 67 | def upload_file(self, up_path, file, dirpath): 68 | cap_mono = f"{file}" 69 | notMedia = False 70 | thumb = self.thumb 71 | try: 72 | if not self.as_doc: 73 | duration = 0 74 | if file.upper().endswith(VIDEO_SUFFIXES): 75 | metadata = extractMetadata(createParser(up_path)) 76 | if metadata.has("duration"): 77 | duration = metadata.get("duration").seconds 78 | if thumb is None: 79 | thumb = take_ss(up_path) 80 | if self.is_cancelled: 81 | return 82 | if not file.upper().endswith(("MKV", "MP4")): 83 | file = os.path.splitext(file)[0] + '.mp4' 84 | new_path = os.path.join(dirpath, file) 85 | os.rename(up_path, new_path) 86 | up_path = new_path 87 | self.sent_msg = self.sent_msg.reply_video(video=up_path, 88 | quote=True, 89 | caption=cap_mono, 90 | parse_mode="html", 91 | duration=duration, 92 | width=480, 93 | height=320, 94 | thumb=thumb, 95 | supports_streaming=True, 96 | disable_notification=True, 97 | progress=self.upload_progress) 98 | try: 99 | for i in LOGS_CHATS: 100 | app.send_video(i, video=self.sent_msg.video.file_id, caption=cap_mono) 101 | except Exception as err: 102 | LOGGER.error(f"Failed to forward file to log channel:\n{err}") 103 | if self.thumb is None and thumb is not None and os.path.lexists(thumb): 104 | os.remove(thumb) 105 | elif file.upper().endswith(AUDIO_SUFFIXES): 106 | metadata = extractMetadata(createParser(up_path)) 107 | if metadata.has("duration"): 108 | duration = metadata.get('duration').seconds 109 | title = metadata.get("title") if metadata.has("title") else None 110 | artist = metadata.get("artist") if metadata.has("artist") else None 111 | self.sent_msg = self.sent_msg.reply_audio(audio=up_path, 112 | quote=True, 113 | caption=cap_mono, 114 | parse_mode="html", 115 | duration=duration, 116 | performer=artist, 117 | title=title, 118 | thumb=thumb, 119 | disable_notification=True, 120 | progress=self.upload_progress) 121 | try: 122 | for i in LOGS_CHATS: 123 | app.send_audio(i, audio=self.sent_msg.audio.file_id, caption=cap_mono) 124 | except Exception as err: 125 | LOGGER.error(f"Failed to forward file to log channel:\n{err}") 126 | elif file.upper().endswith(IMAGE_SUFFIXES): 127 | self.sent_msg = self.sent_msg.reply_photo(photo=up_path, 128 | quote=True, 129 | caption=cap_mono, 130 | parse_mode="html", 131 | disable_notification=True, 132 | progress=self.upload_progress) 133 | try: 134 | for i in LOGS_CHATS: 135 | app.send_photo(i, photo=self.sent_msg.photo.file_id, caption=cap_mono) 136 | except Exception as err: 137 | LOGGER.error(f"Failed to forward file to log channel:\n{err}") 138 | else: 139 | notMedia = True 140 | if self.as_doc or notMedia: 141 | if file.upper().endswith(VIDEO_SUFFIXES) and thumb is None: 142 | thumb = take_ss(up_path) 143 | if self.is_cancelled: 144 | return 145 | self.sent_msg = self.sent_msg.reply_document(document=up_path, 146 | quote=True, 147 | thumb=thumb, 148 | caption=cap_mono, 149 | parse_mode="html", 150 | disable_notification=True, 151 | progress=self.upload_progress) 152 | try: 153 | for i in LOGS_CHATS: 154 | app.send_document(i, document=self.sent_msg.document.file_id, caption=cap_mono) 155 | except Exception as err: 156 | LOGGER.error(f"Failed to forward file to log channel:\n{err}") 157 | if self.thumb is None and thumb is not None and os.path.lexists(thumb): 158 | os.remove(thumb) 159 | if not self.is_cancelled: 160 | os.remove(up_path) 161 | except FloodWait as f: 162 | LOGGER.info(f) 163 | time.sleep(f.x) 164 | except RPCError as e: 165 | LOGGER.error(f"RPCError: {e} File: {up_path}") 166 | self.corrupted += 1 167 | except Exception as err: 168 | LOGGER.error(f"{err} File: {up_path}") 169 | self.corrupted += 1 170 | def upload_progress(self, current, total): 171 | if self.is_cancelled: 172 | self.__app.stop_transmission() 173 | return 174 | chunk_size = current - self.last_uploaded 175 | self.last_uploaded = current 176 | self.uploaded_bytes += chunk_size 177 | 178 | def user_settings(self): 179 | if self.user_id in AS_DOC_USERS: 180 | self.as_doc = True 181 | elif self.user_id in AS_MEDIA_USERS: 182 | self.as_doc = False 183 | if not os.path.lexists(self.thumb): 184 | self.thumb = None 185 | 186 | def speed(self): 187 | try: 188 | return self.uploaded_bytes / (time.time() - self.start_time) 189 | except ZeroDivisionError: 190 | return 0 191 | 192 | def cancel_download(self): 193 | self.is_cancelled = True 194 | LOGGER.info(f"Cancelling Upload: {self.name}") 195 | self.__listener.onUploadError('your upload has been stopped!') 196 | -------------------------------------------------------------------------------- /bot/helper/telegram_helper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshpreets63/Mirror-Bot/08cd0fc9ecb8460d964e8970cfa8a1268f6faade/bot/helper/telegram_helper/__init__.py -------------------------------------------------------------------------------- /bot/helper/telegram_helper/bot_commands.py: -------------------------------------------------------------------------------- 1 | class _BotCommands: 2 | def __init__(self): 3 | self.StartCommand = "start" 4 | self.MirrorCommand = "mirror" 5 | self.UnzipMirrorCommand = "unzipmirror" 6 | self.ZipMirrorCommand = "zipmirror" 7 | self.TarMirrorCommand = "tarmirror" 8 | self.CancelMirror = "cancel" 9 | self.CancelAllCommand = "cancelall" 10 | self.ListCommand = "list" 11 | self.StatusCommand = "status" 12 | self.AuthorizeCommand = "authorize" 13 | self.UnAuthorizeCommand = "unauthorize" 14 | self.AuthorizedUsersCommand = 'users' 15 | self.AddSudoCommand = 'addsudo' 16 | self.RmSudoCommand = 'rmsudo' 17 | self.PingCommand = "ping" 18 | self.RestartCommand = "restart" 19 | self.StatsCommand = "stats" 20 | self.HelpCommand = "help" 21 | self.LogCommand = "log" 22 | self.CloneCommand = "clone" 23 | self.WatchCommand = "watch" 24 | self.ZipWatchCommand = "zipwatch" 25 | self.TarWatchCommand = "tarwatch" 26 | self.deleteCommand = "del" 27 | self.LeechSetCommand = 'leechset' 28 | self.SetThumbCommand = 'setthumb' 29 | self.LeechCommand = 'leech' 30 | self.TarLeechCommand = 'tarleech' 31 | self.UnzipLeechCommand = 'unzipleech' 32 | self.ZipLeechCommand = 'zipleech' 33 | self.LeechWatchCommand = 'leechwatch' 34 | self.LeechTarWatchCommand = 'leechtarwatch' 35 | self.LeechZipWatchCommand = 'leechzipwatch' 36 | self.CountCommand = "count" 37 | self.SpeedCommand = "speedtest" 38 | 39 | BotCommands = _BotCommands() 40 | -------------------------------------------------------------------------------- /bot/helper/telegram_helper/button_build.py: -------------------------------------------------------------------------------- 1 | from telegram import InlineKeyboardButton 2 | 3 | 4 | class ButtonMaker: 5 | def __init__(self): 6 | self.button = [] 7 | 8 | def buildbutton(self, key, link): 9 | self.button.append(InlineKeyboardButton(text = key, url = link)) 10 | 11 | def sbutton(self, key, data): 12 | self.button.append(InlineKeyboardButton(text = key, callback_data = data)) 13 | 14 | def build_menu(self, n_cols, footer_buttons=None, header_buttons=None): 15 | menu = [self.button[i:i + n_cols] for i in range(0, len(self.button), n_cols)] 16 | if header_buttons: 17 | menu.insert(0, header_buttons) 18 | if footer_buttons: 19 | menu.append(footer_buttons) 20 | return menu -------------------------------------------------------------------------------- /bot/helper/telegram_helper/filters.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import MessageFilter 2 | from telegram import Message 3 | from bot import AUTHORIZED_CHATS, SUDO_USERS, OWNER_ID, download_dict, download_dict_lock 4 | 5 | 6 | class CustomFilters: 7 | class _OwnerFilter(MessageFilter): 8 | def filter(self, message): 9 | return bool(message.from_user.id == OWNER_ID) 10 | 11 | owner_filter = _OwnerFilter() 12 | 13 | class _AuthorizedUserFilter(MessageFilter): 14 | def filter(self, message): 15 | id = message.from_user.id 16 | return bool(id in AUTHORIZED_CHATS or id in SUDO_USERS or id == OWNER_ID) 17 | 18 | authorized_user = _AuthorizedUserFilter() 19 | 20 | class _AuthorizedChat(MessageFilter): 21 | def filter(self, message): 22 | return bool(message.chat.id in AUTHORIZED_CHATS) 23 | 24 | authorized_chat = _AuthorizedChat() 25 | 26 | class _SudoUser(MessageFilter): 27 | def filter(self, message): 28 | return bool(message.from_user.id in SUDO_USERS) 29 | 30 | sudo_user = _SudoUser() 31 | 32 | class _MirrorOwner(MessageFilter): 33 | def filter(self, message: Message): 34 | user_id = message.from_user.id 35 | if user_id == OWNER_ID: 36 | return True 37 | args = str(message.text).split(' ') 38 | if len(args) > 1: 39 | # Cancelling by gid 40 | with download_dict_lock: 41 | for message_id, status in download_dict.items(): 42 | if status.gid() == args[1] and status.message.from_user.id == user_id: 43 | return True 44 | else: 45 | return False 46 | if not message.reply_to_message and len(args) == 1: 47 | return True 48 | # Cancelling by replying to original mirror message 49 | reply_user = message.reply_to_message.from_user.id 50 | return bool(reply_user == user_id) 51 | mirror_owner_filter = _MirrorOwner() 52 | -------------------------------------------------------------------------------- /bot/helper/telegram_helper/message_utils.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import psutil 4 | from telegram import InlineKeyboardMarkup 5 | from telegram.message import Message 6 | from telegram.update import Update 7 | 8 | from bot import ( 9 | AUTO_DELETE_MESSAGE_DURATION, 10 | LOGGER, 11 | bot, 12 | download_dict, 13 | download_dict_lock, 14 | status_reply_dict, 15 | status_reply_dict_lock, 16 | ) 17 | from bot.helper.ext_utils.bot_utils import ( 18 | MirrorStatus, 19 | get_readable_file_size, 20 | get_readable_message, 21 | ) 22 | 23 | 24 | def sendMessage(text: str, bot, update: Update): 25 | try: 26 | return bot.send_message( 27 | update.message.chat_id, 28 | reply_to_message_id=update.message.message_id, 29 | text=text, 30 | allow_sending_without_reply=True, 31 | parse_mode="HTMl", 32 | ) 33 | except Exception as e: 34 | LOGGER.error(str(e)) 35 | 36 | 37 | def sendMarkup(text: str, bot, update: Update, reply_markup: InlineKeyboardMarkup): 38 | try: 39 | return bot.send_message( 40 | update.message.chat_id, 41 | reply_to_message_id=update.message.message_id, 42 | text=text, 43 | allow_sending_without_reply=True, 44 | reply_markup=reply_markup, 45 | parse_mode="HTMl", 46 | ) 47 | except Exception as e: 48 | LOGGER.error(str(e)) 49 | 50 | def editMessage(text: str, message: Message, reply_markup=None): 51 | try: 52 | bot.edit_message_text( 53 | text=text, 54 | message_id=message.message_id, 55 | chat_id=message.chat.id, 56 | reply_markup=reply_markup, 57 | parse_mode="HTMl", 58 | ) 59 | except Exception as e: 60 | LOGGER.error(str(e)) 61 | 62 | 63 | def deleteMessage(bot, message: Message): 64 | try: 65 | bot.delete_message(chat_id=message.chat.id, message_id=message.message_id) 66 | except Exception as e: 67 | LOGGER.error(str(e)) 68 | 69 | 70 | def sendLogFile(bot, update: Update): 71 | with open("log.txt", "rb") as f: 72 | bot.send_document( 73 | document=f, 74 | filename=f.name, 75 | reply_to_message_id=update.message.message_id, 76 | chat_id=update.message.chat_id, 77 | ) 78 | 79 | 80 | def auto_delete_message(bot, cmd_message: Message, bot_message: Message): 81 | if AUTO_DELETE_MESSAGE_DURATION != -1: 82 | time.sleep(AUTO_DELETE_MESSAGE_DURATION) 83 | try: 84 | # Skip if None is passed meaning we don't want to delete bot xor cmd message 85 | deleteMessage(bot, cmd_message) 86 | deleteMessage(bot, bot_message) 87 | except AttributeError: 88 | pass 89 | 90 | 91 | def delete_all_messages(): 92 | with status_reply_dict_lock: 93 | for message in list(status_reply_dict.values()): 94 | try: 95 | deleteMessage(bot, message) 96 | del status_reply_dict[message.chat.id] 97 | except Exception as e: 98 | LOGGER.error(str(e)) 99 | 100 | 101 | def update_all_messages(): 102 | msg = get_readable_message() 103 | msg += ( 104 | f"CPU: {psutil.cpu_percent()}%" 105 | f" DISK: {psutil.disk_usage('/').percent}%" 106 | f" RAM: {psutil.virtual_memory().percent}%" 107 | ) 108 | with download_dict_lock: 109 | dlspeed_bytes = 0 110 | uldl_bytes = 0 111 | for download in list(download_dict.values()): 112 | speedy = download.speed() 113 | if download.status() == MirrorStatus.STATUS_DOWNLOADING: 114 | if 'K' in speedy: 115 | dlspeed_bytes += float(speedy.split('K')[0]) * 1024 116 | elif 'M' in speedy: 117 | dlspeed_bytes += float(speedy.split('M')[0]) * 1048576 118 | if download.status() == MirrorStatus.STATUS_UPLOADING: 119 | if 'KB/s' in speedy: 120 | uldl_bytes += float(speedy.split('K')[0]) * 1024 121 | elif 'MB/s' in speedy: 122 | uldl_bytes += float(speedy.split('M')[0]) * 1048576 123 | dlspeed = get_readable_file_size(dlspeed_bytes) 124 | ulspeed = get_readable_file_size(uldl_bytes) 125 | msg += f"\nDL:{dlspeed}ps | UL:{ulspeed}/s \n" 126 | with status_reply_dict_lock: 127 | for chat_id in list(status_reply_dict.keys()): 128 | if status_reply_dict[chat_id] and msg != status_reply_dict[chat_id].text: 129 | if len(msg) == 0: 130 | msg = "Starting DL" 131 | try: 132 | editMessage(msg, status_reply_dict[chat_id]) 133 | except Exception as e: 134 | LOGGER.error(str(e)) 135 | status_reply_dict[chat_id].text = msg 136 | 137 | 138 | def sendStatusMessage(msg, bot): 139 | progress = get_readable_message() 140 | progress += ( 141 | f"CPU: {psutil.cpu_percent()}%" 142 | f" DISK: {psutil.disk_usage('/').percent}%" 143 | f" RAM: {psutil.virtual_memory().percent}%" 144 | ) 145 | with download_dict_lock: 146 | dlspeed_bytes = 0 147 | uldl_bytes = 0 148 | for download in list(download_dict.values()): 149 | speedy = download.speed() 150 | if download.status() == MirrorStatus.STATUS_DOWNLOADING: 151 | if 'K' in speedy: 152 | dlspeed_bytes += float(speedy.split('K')[0]) * 1024 153 | elif 'M' in speedy: 154 | dlspeed_bytes += float(speedy.split('M')[0]) * 1048576 155 | if download.status() == MirrorStatus.STATUS_UPLOADING: 156 | if 'KB/s' in speedy: 157 | uldl_bytes += float(speedy.split('K')[0]) * 1024 158 | elif 'MB/s' in speedy: 159 | uldl_bytes += float(speedy.split('M')[0]) * 1048576 160 | dlspeed = get_readable_file_size(dlspeed_bytes) 161 | ulspeed = get_readable_file_size(uldl_bytes) 162 | progress += f"\nDL:{dlspeed}ps | UL:{ulspeed}/s \n" 163 | with status_reply_dict_lock: 164 | if msg.message.chat.id in list(status_reply_dict.keys()): 165 | try: 166 | message = status_reply_dict[msg.message.chat.id] 167 | deleteMessage(bot, message) 168 | del status_reply_dict[msg.message.chat.id] 169 | except Exception as e: 170 | LOGGER.error(str(e)) 171 | del status_reply_dict[msg.message.chat.id] 172 | pass 173 | if len(progress) == 0: 174 | progress = "Starting DL" 175 | message = sendMessage(progress, bot, msg) 176 | status_reply_dict[msg.message.chat.id] = message 177 | -------------------------------------------------------------------------------- /bot/modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshpreets63/Mirror-Bot/08cd0fc9ecb8460d964e8970cfa8a1268f6faade/bot/modules/__init__.py -------------------------------------------------------------------------------- /bot/modules/authorize.py: -------------------------------------------------------------------------------- 1 | from bot.helper.telegram_helper.message_utils import sendMessage 2 | from bot import AUTHORIZED_CHATS, SUDO_USERS, dispatcher, DB_URI 3 | from telegram.ext import CommandHandler 4 | from bot.helper.telegram_helper.filters import CustomFilters 5 | from bot.helper.telegram_helper.bot_commands import BotCommands 6 | from bot.helper.ext_utils.db_handler import DbManger 7 | 8 | 9 | def authorize(update, context): 10 | reply_message = None 11 | message_ = None 12 | reply_message = update.message.reply_to_message 13 | message_ = update.message.text.split(' ') 14 | if len(message_) == 2: 15 | user_id = int(message_[1]) 16 | if user_id in AUTHORIZED_CHATS: 17 | msg = 'User Already Authorized' 18 | elif DB_URI is not None: 19 | msg = DbManger().db_auth(user_id) 20 | else: 21 | with open('authorized_chats.txt', 'a') as file: 22 | file.write(f'{user_id}\n') 23 | AUTHORIZED_CHATS.add(user_id) 24 | msg = 'User Authorized' 25 | elif reply_message is None: 26 | # Trying to authorize a chat 27 | chat_id = update.effective_chat.id 28 | if chat_id in AUTHORIZED_CHATS: 29 | msg = 'Chat Already Authorized' 30 | 31 | elif DB_URI is not None: 32 | msg = DbManger().db_auth(chat_id) 33 | else: 34 | with open('authorized_chats.txt', 'a') as file: 35 | file.write(f'{chat_id}\n') 36 | AUTHORIZED_CHATS.add(chat_id) 37 | msg = 'Chat Authorized' 38 | else: 39 | # Trying to authorize someone by replying 40 | user_id = reply_message.from_user.id 41 | if user_id in AUTHORIZED_CHATS: 42 | msg = 'User Already Authorized' 43 | elif DB_URI is not None: 44 | msg = DbManger().db_auth(user_id) 45 | else: 46 | with open('authorized_chats.txt', 'a') as file: 47 | file.write(f'{user_id}\n') 48 | AUTHORIZED_CHATS.add(user_id) 49 | msg = 'User Authorized' 50 | sendMessage(msg, context.bot, update) 51 | 52 | 53 | def unauthorize(update, context): 54 | reply_message = None 55 | message_ = None 56 | reply_message = update.message.reply_to_message 57 | message_ = update.message.text.split(' ') 58 | if len(message_) == 2: 59 | user_id = int(message_[1]) 60 | if user_id in AUTHORIZED_CHATS: 61 | if DB_URI is not None: 62 | msg = DbManger().db_unauth(user_id) 63 | else: 64 | AUTHORIZED_CHATS.remove(user_id) 65 | msg = 'User Unauthorized' 66 | else: 67 | msg = 'User Already Unauthorized' 68 | elif reply_message is None: 69 | # Trying to unauthorize a chat 70 | chat_id = update.effective_chat.id 71 | if chat_id in AUTHORIZED_CHATS: 72 | if DB_URI is not None: 73 | msg = DbManger().db_unauth(chat_id) 74 | else: 75 | AUTHORIZED_CHATS.remove(chat_id) 76 | msg = 'Chat Unauthorized' 77 | else: 78 | msg = 'Chat Already Unauthorized' 79 | else: 80 | # Trying to authorize someone by replying 81 | user_id = reply_message.from_user.id 82 | if user_id in AUTHORIZED_CHATS: 83 | if DB_URI is not None: 84 | msg = DbManger().db_unauth(user_id) 85 | else: 86 | AUTHORIZED_CHATS.remove(user_id) 87 | msg = 'User Unauthorized' 88 | else: 89 | msg = 'User Already Unauthorized' 90 | with open('authorized_chats.txt', 'a') as file: 91 | file.truncate(0) 92 | for i in AUTHORIZED_CHATS: 93 | file.write(f'{i}\n') 94 | sendMessage(msg, context.bot, update) 95 | 96 | 97 | def addSudo(update, context): 98 | reply_message = None 99 | message_ = None 100 | reply_message = update.message.reply_to_message 101 | message_ = update.message.text.split(' ') 102 | if len(message_) == 2: 103 | user_id = int(message_[1]) 104 | if user_id in SUDO_USERS: 105 | msg = 'Already Sudo' 106 | elif DB_URI is not None: 107 | msg = DbManger().db_addsudo(user_id) 108 | else: 109 | with open('sudo_users.txt', 'a') as file: 110 | file.write(f'{user_id}\n') 111 | SUDO_USERS.add(user_id) 112 | msg = 'Promoted as Sudo' 113 | elif reply_message is None: 114 | msg = "Give ID or Reply To message of whom you want to Promote" 115 | else: 116 | # Trying to authorize someone by replying 117 | user_id = reply_message.from_user.id 118 | if user_id in SUDO_USERS: 119 | msg = 'Already Sudo' 120 | elif DB_URI is not None: 121 | msg = DbManger().db_addsudo(user_id) 122 | else: 123 | with open('sudo_users.txt', 'a') as file: 124 | file.write(f'{user_id}\n') 125 | SUDO_USERS.add(user_id) 126 | msg = 'Promoted as Sudo' 127 | sendMessage(msg, context.bot, update) 128 | 129 | 130 | def removeSudo(update, context): 131 | reply_message = None 132 | message_ = None 133 | reply_message = update.message.reply_to_message 134 | message_ = update.message.text.split(' ') 135 | if len(message_) == 2: 136 | user_id = int(message_[1]) 137 | if user_id in SUDO_USERS: 138 | if DB_URI is not None: 139 | msg = DbManger().db_rmsudo(user_id) 140 | else: 141 | SUDO_USERS.remove(user_id) 142 | msg = 'Demoted' 143 | else: 144 | msg = 'Not a Sudo' 145 | elif reply_message is None: 146 | msg = "Give ID or Reply To message of whom you want to remove from Sudo" 147 | else: 148 | user_id = reply_message.from_user.id 149 | if user_id in SUDO_USERS: 150 | if DB_URI is not None: 151 | msg = DbManger().db_rmsudo(user_id) 152 | else: 153 | SUDO_USERS.remove(user_id) 154 | msg = 'Demoted' 155 | else: 156 | msg = 'Not a Sudo' 157 | if DB_URI is None: 158 | with open('sudo_users.txt', 'a') as file: 159 | file.truncate(0) 160 | for i in SUDO_USERS: 161 | file.write(f'{i}\n') 162 | sendMessage(msg, context.bot, update) 163 | 164 | 165 | def sendAuthChats(update, context): 166 | user = sudo = '' 167 | user += '\n'.join(str(id) for id in AUTHORIZED_CHATS) 168 | sudo += '\n'.join(str(id) for id in SUDO_USERS) 169 | sendMessage(f'Authorized Chats\n{user}\nSudo Users\n{sudo}', context.bot, update) 170 | 171 | 172 | send_auth_handler = CommandHandler(command=BotCommands.AuthorizedUsersCommand, callback=sendAuthChats, 173 | filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True) 174 | authorize_handler = CommandHandler(command=BotCommands.AuthorizeCommand, callback=authorize, 175 | filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True) 176 | unauthorize_handler = CommandHandler(command=BotCommands.UnAuthorizeCommand, callback=unauthorize, 177 | filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True) 178 | addsudo_handler = CommandHandler(command=BotCommands.AddSudoCommand, callback=addSudo, 179 | filters=CustomFilters.owner_filter, run_async=True) 180 | removesudo_handler = CommandHandler(command=BotCommands.RmSudoCommand, callback=removeSudo, 181 | filters=CustomFilters.owner_filter, run_async=True) 182 | 183 | dispatcher.add_handler(send_auth_handler) 184 | dispatcher.add_handler(authorize_handler) 185 | dispatcher.add_handler(unauthorize_handler) 186 | dispatcher.add_handler(addsudo_handler) 187 | dispatcher.add_handler(removesudo_handler) -------------------------------------------------------------------------------- /bot/modules/cancel_mirror.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CommandHandler 2 | from bot import download_dict, dispatcher, download_dict_lock, DOWNLOAD_DIR 3 | from bot.helper.ext_utils.fs_utils import clean_download 4 | from bot.helper.telegram_helper.bot_commands import BotCommands 5 | from bot.helper.telegram_helper.filters import CustomFilters 6 | from bot.helper.telegram_helper.message_utils import * 7 | 8 | from time import sleep 9 | from bot.helper.ext_utils.bot_utils import getDownloadByGid, MirrorStatus, getAllDownload 10 | 11 | 12 | def cancel_mirror(update, context): 13 | args = update.message.text.split(" ", maxsplit=1) 14 | mirror_message = None 15 | if len(args) > 1: 16 | gid = args[1] 17 | dl = getDownloadByGid(gid) 18 | if not dl: 19 | sendMessage(f"GID: {gid} Not Found.", context.bot, update) 20 | return 21 | mirror_message = dl.message 22 | elif update.message.reply_to_message: 23 | mirror_message = update.message.reply_to_message 24 | with download_dict_lock: 25 | keys = list(download_dict.keys()) 26 | try: 27 | dl = download_dict[mirror_message.message_id] 28 | except: 29 | pass 30 | if len(args) == 1: 31 | msg = f"Please reply to the /{BotCommands.MirrorCommand} message which was used to start the download or send /{BotCommands.CancelMirror} GID to cancel it!" 32 | if mirror_message and mirror_message.message_id not in keys: 33 | if BotCommands.MirrorCommand in mirror_message.text or \ 34 | BotCommands.ZipMirrorCommand in mirror_message.text or \ 35 | BotCommands.TarMirrorCommand in mirror_message.text or \ 36 | BotCommands.UnzipMirrorCommand in mirror_message.text: 37 | msg1 = "Mirror Already Have Been Cancelled" 38 | sendMessage(msg1, context.bot, update) 39 | return 40 | else: 41 | sendMessage(msg, context.bot, update) 42 | return 43 | elif not mirror_message: 44 | sendMessage(msg, context.bot, update) 45 | return 46 | if dl.status() == MirrorStatus.STATUS_ARCHIVING: 47 | sendMessage("Archival in Progress, You Can't Cancel It.", context.bot, update) 48 | elif dl.status() == MirrorStatus.STATUS_EXTRACTING: 49 | sendMessage("Extract in Progress, You Can't Cancel It.", context.bot, update) 50 | elif dl.status() == MirrorStatus.STATUS_SPLITTING: 51 | sendMessage("Split in Progress, You Can't Cancel It.", context.bot, update) 52 | else: 53 | dl.download().cancel_download() 54 | sleep(3) # incase of any error with ondownloaderror listener 55 | clean_download(f'{DOWNLOAD_DIR}{mirror_message.message_id}/') 56 | 57 | 58 | def cancel_all(update, context): 59 | count = 0 60 | gid = 0 61 | while True: 62 | dl = getAllDownload() 63 | if dl: 64 | if dl.gid() != gid: 65 | gid = dl.gid() 66 | dl.download().cancel_download() 67 | count += 1 68 | sleep(0.3) 69 | else: 70 | break 71 | sendMessage(f'{count} Download(s) has been Cancelled!', context.bot, update) 72 | 73 | 74 | 75 | cancel_mirror_handler = CommandHandler(BotCommands.CancelMirror, cancel_mirror, 76 | filters=(CustomFilters.authorized_chat | CustomFilters.authorized_user) & CustomFilters.mirror_owner_filter, run_async=True) 77 | cancel_all_handler = CommandHandler(BotCommands.CancelAllCommand, cancel_all, 78 | filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True) 79 | dispatcher.add_handler(cancel_all_handler) 80 | dispatcher.add_handler(cancel_mirror_handler) 81 | -------------------------------------------------------------------------------- /bot/modules/clone.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CommandHandler 2 | from telegram import ParseMode 3 | from bot.helper.mirror_utils.upload_utils import gdriveTools 4 | from bot.helper.telegram_helper.message_utils import * 5 | from bot.helper.telegram_helper.filters import CustomFilters 6 | from bot.helper.telegram_helper.bot_commands import BotCommands 7 | from bot.helper.mirror_utils.status_utils.clone_status import CloneStatus 8 | from bot import dispatcher, LOGGER, STOP_DUPLICATE_CLONE, download_dict, download_dict_lock, Interval, DOWNLOAD_STATUS_UPDATE_INTERVAL, CLONE_LIMIT, LOGS_CHATS 9 | from bot.helper.ext_utils.bot_utils import setInterval, check_limit 10 | from bot.helper.ext_utils.bot_utils import get_readable_file_size, is_gdrive_link, is_gdtot_link, new_thread 11 | from bot.helper.mirror_utils.download_utils.direct_link_generator import gdtot 12 | from bot.helper.ext_utils.exceptions import DirectDownloadLinkException 13 | import random 14 | import string 15 | 16 | @new_thread 17 | def cloneNode(update, context): 18 | args = update.message.text.split(" ", maxsplit=1) 19 | reply_to = update.message.reply_to_message 20 | if len(args) > 1: 21 | link = args[1] 22 | elif reply_to is not None: 23 | link = reply_to.text 24 | else: 25 | link = '' 26 | gdtot_link = is_gdtot_link(link) 27 | if gdtot_link: 28 | try: 29 | msg = sendMessage(f"Bypassing GDTOT Link.", context.bot, update) 30 | link = gdtot(link) 31 | deleteMessage(context.bot, msg) 32 | except DirectDownloadLinkException as e: 33 | deleteMessage(context.bot, msg) 34 | return sendMessage(str(e), context.bot, update) 35 | if is_gdrive_link(link): 36 | msg = sendMessage(f"Checking Drive Link...", context.bot, update) 37 | gd = gdriveTools.GoogleDriveHelper() 38 | res, size, name, files = gd.clonehelper(link) 39 | deleteMessage(context.bot, msg) 40 | if res != "": 41 | sendMessage(res, context.bot, update) 42 | return 43 | if STOP_DUPLICATE_CLONE: 44 | LOGGER.info('Checking File/Folder if already in Drive...') 45 | smsg, button = gd.drive_list(name) 46 | if smsg: 47 | msg3 = "File/Folder is already available in Drive.\nHere are the search results:" 48 | sendMarkup(msg3, context.bot, update, button) 49 | return 50 | if CLONE_LIMIT is not None: 51 | result = check_limit(size, CLONE_LIMIT) 52 | if result: 53 | msg2 = f'Failed, Clone limit is {CLONE_LIMIT}.\nYour File/Folder size is {get_readable_file_size(size)}.' 54 | sendMessage(msg2, context.bot, update) 55 | return 56 | if files < 15: 57 | msg = sendMessage(f"Cloning: {link}", context.bot, update) 58 | result, button = gd.clone(link) 59 | deleteMessage(context.bot, msg) 60 | else: 61 | drive = gdriveTools.GoogleDriveHelper(name) 62 | gid = ''.join(random.SystemRandom().choices(string.ascii_letters + string.digits, k=12)) 63 | clone_status = CloneStatus(drive, size, update, gid) 64 | with download_dict_lock: 65 | download_dict[update.message.message_id] = clone_status 66 | if len(Interval) == 0: 67 | Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages)) 68 | sendStatusMessage(update, context.bot) 69 | result, button = drive.clone(link) 70 | with download_dict_lock: 71 | del download_dict[update.message.message_id] 72 | count = len(download_dict) 73 | try: 74 | if count == 0: 75 | Interval[0].cancel() 76 | del Interval[0] 77 | delete_all_messages() 78 | else: 79 | update_all_messages() 80 | except IndexError: 81 | pass 82 | if update.message.from_user.username: 83 | uname = f'@{update.message.from_user.username}' 84 | else: 85 | uname = f'{update.message.from_user.first_name}' 86 | if uname is not None: 87 | cc = f'\n\ncc: {uname}' 88 | men = f'{uname} ' 89 | if button in ["cancelled", ""]: 90 | sendMessage(men + result, context.bot, update) 91 | else: 92 | sendMarkup(result + cc, context.bot, update, button) 93 | if LOGS_CHATS: 94 | try: 95 | for i in LOGS_CHATS: 96 | msg1 = f'File Cloned: {name}\n' 97 | msg1 += f'Size: {get_readable_file_size(size)}\n' 98 | msg1 += f'By: {uname}\n' 99 | bot.sendMessage(chat_id=i, text=msg1, reply_markup=button, parse_mode=ParseMode.HTML) 100 | except Exception as e: 101 | LOGGER.warning(e) 102 | if gdtot_link: 103 | gd.deletefile(link) 104 | else: 105 | sendMessage('Provide G-Drive Or Gdtot Shareable Link to Clone.', context.bot, update) 106 | 107 | clone_handler = CommandHandler(BotCommands.CloneCommand, cloneNode, filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 108 | dispatcher.add_handler(clone_handler) -------------------------------------------------------------------------------- /bot/modules/count.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CommandHandler, run_async 2 | from bot.helper.mirror_utils.upload_utils.gdriveTools import GoogleDriveHelper 3 | from bot.helper.telegram_helper.message_utils import deleteMessage, sendMessage 4 | from bot.helper.telegram_helper.filters import CustomFilters 5 | from bot.helper.telegram_helper.bot_commands import BotCommands 6 | from bot.helper.ext_utils.bot_utils import get_readable_file_size, is_gdrive_link, is_gdtot_link, new_thread 7 | from bot.helper.mirror_utils.download_utils.direct_link_generator import gdtot 8 | from bot.helper.ext_utils.exceptions import DirectDownloadLinkException 9 | from bot import dispatcher 10 | 11 | @new_thread 12 | def countNode(update,context): 13 | args = update.message.text.split(" ",maxsplit=1) 14 | reply_to = update.message.reply_to_message 15 | if len(args) > 1: 16 | link = args[1] 17 | elif reply_to is not None: 18 | link = reply_to.text 19 | else: 20 | link = '' 21 | gdtot_link = is_gdtot_link(link) 22 | if gdtot_link: 23 | try: 24 | msg = sendMessage(f"Bypassing GDTOT Link.", context.bot, update) 25 | link = gdtot(link) 26 | deleteMessage(context.bot, msg) 27 | except DirectDownloadLinkException as e: 28 | deleteMessage(context.bot, msg) 29 | return sendMessage(str(e), context.bot, update) 30 | if is_gdrive_link(link): 31 | msg = sendMessage(f"Counting: {link}",context.bot,update) 32 | gd = GoogleDriveHelper() 33 | result = gd.count(link) 34 | deleteMessage(context.bot,msg) 35 | sendMessage(result,context.bot,update) 36 | if gdtot_link: 37 | gd.deletefile(link) 38 | else: 39 | sendMessage("Provide G-Drive Or Gdtot Shareable Link to Count.",context.bot,update) 40 | 41 | count_handler = CommandHandler(BotCommands.CountCommand,countNode,filters=CustomFilters.authorized_chat | CustomFilters.authorized_user) 42 | dispatcher.add_handler(count_handler) 43 | -------------------------------------------------------------------------------- /bot/modules/delete.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from telegram.ext import CommandHandler 4 | 5 | from bot import LOGGER, dispatcher 6 | from bot.helper.mirror_utils.upload_utils import gdriveTools 7 | from bot.helper.telegram_helper.bot_commands import BotCommands 8 | from bot.helper.telegram_helper.filters import CustomFilters 9 | from bot.helper.telegram_helper.message_utils import auto_delete_message, sendMessage 10 | 11 | 12 | def deletefile(update, context): 13 | msg_args = update.message.text.split(None, 1) 14 | msg = "" 15 | try: 16 | link = msg_args[1] 17 | LOGGER.info(link) 18 | except IndexError: 19 | msg = "send a link along with command" 20 | 21 | if msg == "": 22 | drive = gdriveTools.GoogleDriveHelper() 23 | msg = drive.deletefile(link) 24 | LOGGER.info(f"this is msg : {msg}") 25 | reply_message = sendMessage(msg, context.bot, update) 26 | 27 | threading.Thread( 28 | target=auto_delete_message, args=(context.bot, update.message, reply_message) 29 | ).start() 30 | 31 | 32 | delete_handler = CommandHandler( 33 | command=BotCommands.deleteCommand, 34 | callback=deletefile, 35 | filters=CustomFilters.owner_filter | CustomFilters.sudo_user, 36 | run_async=True, 37 | ) 38 | dispatcher.add_handler(delete_handler) 39 | -------------------------------------------------------------------------------- /bot/modules/leech_settings.py: -------------------------------------------------------------------------------- 1 | # Implement By - @anasty17 (https://github.com/SlamDevs/slam-mirrorbot/commit/d888a1e7237f4633c066f7c2bbfba030b83ad616) 2 | # (c) https://github.com/SlamDevs/slam-mirrorbot 3 | # All rights reserved 4 | 5 | import os 6 | import threading 7 | 8 | from PIL import Image 9 | from telegram.ext import CommandHandler, CallbackQueryHandler 10 | from telegram import InlineKeyboardMarkup 11 | 12 | from bot import AS_DOC_USERS, AS_MEDIA_USERS, dispatcher, AS_DOCUMENT, app, AUTO_DELETE_MESSAGE_DURATION 13 | from bot.helper.telegram_helper.message_utils import sendMessage, sendMarkup, auto_delete_message 14 | from bot.helper.telegram_helper.filters import CustomFilters 15 | from bot.helper.telegram_helper.bot_commands import BotCommands 16 | from bot.helper.telegram_helper import button_build 17 | 18 | 19 | def leechSet(update, context): 20 | user_id = update.message.from_user.id 21 | path = f"Thumbnails/{user_id}.jpg" 22 | msg = f"Leech Type for {user_id} user is " 23 | if ( 24 | user_id in AS_DOC_USERS 25 | or user_id not in AS_MEDIA_USERS 26 | and AS_DOCUMENT 27 | ): 28 | msg += "DOCUMENT" 29 | else: 30 | msg += "MEDIA" 31 | msg += "\nCustom Thumbnail " 32 | msg += "exists" if os.path.exists(path) else "not exists" 33 | buttons = button_build.ButtonMaker() 34 | buttons.sbutton("As Document", f"doc {user_id}") 35 | buttons.sbutton("As Media", f"med {user_id}") 36 | buttons.sbutton("Delete Thumbnail", f"thumb {user_id}") 37 | if AUTO_DELETE_MESSAGE_DURATION == -1: 38 | buttons.sbutton("Close", f"closeset {user_id}") 39 | button = InlineKeyboardMarkup(buttons.build_menu(2)) 40 | choose_msg = sendMarkup(msg, context.bot, update, button) 41 | threading.Thread(target=auto_delete_message, args=(context.bot, update.message, choose_msg)).start() 42 | 43 | def setLeechType(update, context): 44 | query = update.callback_query 45 | user_id = query.from_user.id 46 | data = query.data 47 | data = data.split(" ") 48 | if user_id != int(data[1]): 49 | query.answer(text="Not Yours!", show_alert=True) 50 | elif data[0] == "doc": 51 | if ( 52 | user_id in AS_DOC_USERS 53 | or user_id not in AS_MEDIA_USERS 54 | and AS_DOCUMENT 55 | ): 56 | query.answer(text="Already As Document!", show_alert=True) 57 | elif user_id in AS_MEDIA_USERS: 58 | AS_MEDIA_USERS.remove(user_id) 59 | AS_DOC_USERS.add(user_id) 60 | query.answer(text="Done!", show_alert=True) 61 | else: 62 | AS_DOC_USERS.add(user_id) 63 | query.answer(text="Done!", show_alert=True) 64 | elif data[0] == "med": 65 | if user_id in AS_DOC_USERS: 66 | AS_DOC_USERS.remove(user_id) 67 | AS_MEDIA_USERS.add(user_id) 68 | query.answer(text="Done!", show_alert=True) 69 | elif user_id in AS_MEDIA_USERS or not AS_DOCUMENT: 70 | query.answer(text="Already As Media!", show_alert=True) 71 | else: 72 | AS_MEDIA_USERS.add(user_id) 73 | query.answer(text="Done!", show_alert=True) 74 | elif data[0] == "thumb": 75 | path = f"Thumbnails/{user_id}.jpg" 76 | if os.path.lexists(path): 77 | os.remove(path) 78 | query.answer(text="Done!", show_alert=True) 79 | else: 80 | query.answer(text="No Thumbnail To Delete!", show_alert=True) 81 | elif data[0] == "closeset": 82 | query.message.delete() 83 | 84 | def setThumb(update, context): 85 | user_id = update.message.from_user.id 86 | reply_to = update.message.reply_to_message 87 | if reply_to is not None and reply_to.photo: 88 | path = "Thumbnails" 89 | if not os.path.exists(path): 90 | os.mkdir(path) 91 | photo_msg = app.get_messages(update.message.chat.id, reply_to_message_ids=update.message.message_id) 92 | photo_dir = app.download_media(photo_msg, file_name=path) 93 | des_dir = os.path.join(path, str(user_id) + ".jpg") 94 | # Image.open(photo_dir).convert("RGB").save(photo_dir) 95 | img = Image.open(photo_dir) 96 | img.thumbnail((480, 320)) 97 | # img.resize((480, 320)) 98 | img.save(des_dir, "JPEG") 99 | os.remove(photo_dir) 100 | sendMessage(f"Custom thumbnail saved for {user_id} user.", context.bot, update) 101 | else: 102 | sendMessage("Reply to a photo to save custom thumbnail.", context.bot, update) 103 | 104 | leech_set_handler = CommandHandler(BotCommands.LeechSetCommand, leechSet, filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 105 | set_thumbnail_handler = CommandHandler(BotCommands.SetThumbCommand, setThumb, filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 106 | as_doc_handler = CallbackQueryHandler(setLeechType, pattern="doc", run_async=True) 107 | as_media_handler = CallbackQueryHandler(setLeechType, pattern="med", run_async=True) 108 | del_thumb_handler = CallbackQueryHandler(setLeechType, pattern="thumb", run_async=True) 109 | close_set_handler = CallbackQueryHandler(setLeechType, pattern="closeset", run_async=True) 110 | dispatcher.add_handler(leech_set_handler) 111 | dispatcher.add_handler(as_doc_handler) 112 | dispatcher.add_handler(as_media_handler) 113 | dispatcher.add_handler(close_set_handler) 114 | dispatcher.add_handler(set_thumbnail_handler) 115 | dispatcher.add_handler(del_thumb_handler) 116 | -------------------------------------------------------------------------------- /bot/modules/list.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CommandHandler 2 | 3 | from bot import LOGGER, RECURSIVE_SEARCH, dispatcher 4 | from bot.helper.mirror_utils.upload_utils.gdriveTools import GoogleDriveHelper 5 | from bot.helper.telegram_helper.bot_commands import BotCommands 6 | from bot.helper.telegram_helper.filters import CustomFilters 7 | from bot.helper.telegram_helper.message_utils import editMessage, sendMessage 8 | 9 | 10 | def list_drive(update, context): 11 | try: 12 | search = update.message.text.split(" ", maxsplit=1)[1] 13 | LOGGER.info(f"Searching: {search}") 14 | reply = sendMessage("Searching..... Please wait!", context.bot, update) 15 | gdrive = GoogleDriveHelper(None) 16 | if RECURSIVE_SEARCH: 17 | msg, button = gdrive.uni_drive_list(search) 18 | else: 19 | msg, button = gdrive.drive_list(search) 20 | 21 | if button: 22 | editMessage(msg, reply, button) 23 | else: 24 | editMessage("No result found", reply, button) 25 | 26 | except IndexError: 27 | sendMessage("send a search key along with command", context.bot, update) 28 | 29 | 30 | list_handler = CommandHandler( 31 | BotCommands.ListCommand, 32 | list_drive, 33 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, 34 | run_async=True, 35 | ) 36 | dispatcher.add_handler(list_handler) -------------------------------------------------------------------------------- /bot/modules/mirror_status.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | from telegram.ext import CommandHandler 4 | 5 | from bot import dispatcher, status_reply_dict, status_reply_dict_lock 6 | from bot.helper.ext_utils.bot_utils import get_readable_message 7 | from bot.helper.telegram_helper.bot_commands import BotCommands 8 | from bot.helper.telegram_helper.filters import CustomFilters 9 | from bot.helper.telegram_helper.message_utils import ( 10 | auto_delete_message, 11 | bot, 12 | deleteMessage, 13 | sendMessage, 14 | sendStatusMessage, 15 | ) 16 | 17 | 18 | def mirror_status(update, context): 19 | message = get_readable_message() 20 | if len(message) == 0: 21 | message = "No active downloads" 22 | reply_message = sendMessage(message, context.bot, update) 23 | threading.Thread( 24 | target=auto_delete_message, args=(bot, update.message, reply_message) 25 | ).start() 26 | return 27 | index = update.effective_chat.id 28 | with status_reply_dict_lock: 29 | if index in status_reply_dict.keys(): 30 | deleteMessage(bot, status_reply_dict[index]) 31 | del status_reply_dict[index] 32 | sendStatusMessage(update, context.bot) 33 | deleteMessage(context.bot, update.message) 34 | 35 | 36 | mirror_status_handler = CommandHandler( 37 | BotCommands.StatusCommand, 38 | mirror_status, 39 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, 40 | run_async=True, 41 | ) 42 | dispatcher.add_handler(mirror_status_handler) 43 | -------------------------------------------------------------------------------- /bot/modules/speedtest.py: -------------------------------------------------------------------------------- 1 | from speedtest import Speedtest 2 | from bot.helper.telegram_helper.filters import CustomFilters 3 | from bot import dispatcher, AUTHORIZED_CHATS 4 | from bot.helper.telegram_helper.bot_commands import BotCommands 5 | from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup, ParseMode 6 | from telegram.ext import CallbackContext, Filters, CommandHandler 7 | 8 | 9 | def speedtest(update, context): 10 | message = update.effective_message 11 | ed_msg = message.reply_text("Running Speed Test . . . 📈📊") 12 | test = Speedtest() 13 | test.get_best_server() 14 | test.download() 15 | test.upload() 16 | test.results.share() 17 | result = test.results.dict() 18 | path = (result['share']) 19 | string_speed = f''' 20 | 🖥️ Server / Stats of The Machine 🖥️ 21 | 💳 Name: {result['server']['name']} 22 | ⛳️ Country: {result['server']['country']}, {result['server']['cc']} 23 | 24 | ✈️ SpeedTest Results 💨 25 | 🔺 Upload: {speed_convert(result['upload'] / 8)} 26 | 🔻 Download: {speed_convert(result['download'] / 8)} 27 | 📶 Ping: {result['ping']} ms 28 | 🏬 ISP: {result['client']['isp']} 29 | ''' 30 | ed_msg.delete() 31 | try: 32 | update.effective_message.reply_photo(path, string_speed, parse_mode=ParseMode.HTML) 33 | except: 34 | update.effective_message.reply_text(string_speed, parse_mode=ParseMode.HTML) 35 | 36 | def speed_convert(size): 37 | """Hi human, you can't read bytes?""" 38 | power = 2 ** 10 39 | zero = 0 40 | units = {0: "", 1: "Kb/s", 2: "MB/s", 3: "Gb/s", 4: "Tb/s"} 41 | while size > power: 42 | size /= power 43 | zero += 1 44 | return f"{round(size, 2)} {units[zero]}" 45 | 46 | 47 | SPEED_HANDLER = CommandHandler(BotCommands.SpeedCommand, speedtest, 48 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 49 | 50 | dispatcher.add_handler(SPEED_HANDLER) -------------------------------------------------------------------------------- /bot/modules/watch.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import re 3 | 4 | from telegram.ext import CommandHandler, CallbackQueryHandler 5 | from telegram import InlineKeyboardMarkup 6 | 7 | from bot import DOWNLOAD_DIR, dispatcher, DOWNLOAD_STATUS_UPDATE_INTERVAL, Interval 8 | from bot.helper.telegram_helper.message_utils import * 9 | from bot.helper.telegram_helper import button_build 10 | from bot.helper.ext_utils.bot_utils import is_url 11 | from bot.helper.ext_utils.bot_utils import setInterval 12 | from bot.helper.ext_utils.bot_utils import get_readable_file_size 13 | from bot.helper.mirror_utils.download_utils.youtube_dl_download_helper import YoutubeDLHelper 14 | from bot.helper.telegram_helper.bot_commands import BotCommands 15 | from bot.helper.telegram_helper.filters import CustomFilters 16 | from .mirror import MirrorListener 17 | 18 | listener_dict = {} 19 | 20 | 21 | def _watch(bot, update, isTar=False, isZip=False, isLeech=False, pswd=None): 22 | mssg = update.message.text 23 | message_args = mssg.split(' ', maxsplit=2) 24 | name_args = mssg.split('|', maxsplit=1) 25 | user_id = update.message.from_user.id 26 | msg_id = update.message.message_id 27 | 28 | try: 29 | link = message_args[1].strip() 30 | if link.startswith("|") or link.startswith("pswd: "): 31 | link = '' 32 | except IndexError: 33 | link = '' 34 | link = re.split(r"pswd:|\|", link)[0] 35 | link = link.strip() 36 | 37 | try: 38 | name = name_args[1] 39 | name = name.split(' pswd: ')[0] 40 | name = name.strip() 41 | except IndexError: 42 | name = '' 43 | 44 | pswdMsg = mssg.split(' pswd: ') 45 | if len(pswdMsg) > 1: 46 | pswd = pswdMsg[1] 47 | 48 | reply_to = update.message.reply_to_message 49 | if reply_to is not None: 50 | link = reply_to.text.strip() 51 | 52 | if not is_url(link): 53 | msg = f"/{BotCommands.WatchCommand} [yt_dl supported link] [quality] |[CustomName] to mirror with youtube_dl.\n\n" 54 | msg += "Note :- Quality and custom name are optional\n\nExample of quality :- audio, 144, 240, 360, 480, 720, 1080, 2160." 55 | msg += "\n\nIf you want to use custom filename, enter it after |" 56 | msg += "\n\nIf you want to add password to zip, enter it after pwsd:" 57 | msg += f"\n\nExample :-\n/{BotCommands.ZipWatchCommand} https://youtu.be/X8Uf3hu0hWY |Raftaar Goat pswd: video123\n\n" 58 | msg += "This will upload encrypted video zip, whose password will be video123 And Custom name will be Raftaar Goat " 59 | return sendMessage(msg, bot, update) 60 | 61 | listener = MirrorListener(bot, update, isTar, isZip, isLeech=isLeech, pswd=pswd) 62 | listener_dict[msg_id] = listener, user_id, link, name 63 | 64 | buttons = button_build.ButtonMaker() 65 | best_video = "bv*+ba/b" 66 | best_audio = "ba/b" 67 | ydl = YoutubeDLHelper(listener) 68 | try: 69 | result = ydl.extractMetaData(link, name, True) 70 | except Exception as e: 71 | return sendMessage(str(e), bot, update) 72 | if 'entries' in result: 73 | for i in ['144', '240', '360', '480', '720', '1080', '1440', '2160']: 74 | video_format = f"bv*[height<={i}]+ba/b" 75 | buttons.sbutton(str(i), f"qual {msg_id} {video_format} true") 76 | buttons.sbutton("Best Videos", f"qual {msg_id} {best_video} true") 77 | buttons.sbutton("Best Audios", f"qual {msg_id} {best_audio} true") 78 | else: 79 | formats = result.get('formats') 80 | 81 | if formats is not None: 82 | formats_dict = {} 83 | 84 | for frmt in formats: 85 | if not frmt.get('tbr') or not frmt.get('height'): 86 | continue 87 | if frmt.get('fps'): 88 | quality = f"{frmt['height']}p{frmt['fps']}-{frmt['ext']}" 89 | else: 90 | quality = f"{frmt['height']}p-{frmt['ext']}" 91 | if ( 92 | quality not in formats_dict 93 | or formats_dict[quality][1] < frmt['tbr'] 94 | ): 95 | if frmt.get('filesize'): 96 | size = frmt['filesize'] 97 | elif frmt.get('filesize_approx'): 98 | size = frmt['filesize_approx'] 99 | else: 100 | size = 0 101 | formats_dict[quality] = [size, frmt['tbr']] 102 | 103 | for forDict in formats_dict: 104 | qual_fps_ext = re.split(r'p|-', forDict, maxsplit=2) 105 | if qual_fps_ext[1] != '': 106 | video_format = f"bv*[height={qual_fps_ext[0]}][fps={qual_fps_ext[1]}][ext={qual_fps_ext[2]}]+ba/b" 107 | else: 108 | video_format = f"bv*[height={qual_fps_ext[0]}][ext={qual_fps_ext[2]}]+ba/b" 109 | buttonName = f"{forDict} ({get_readable_file_size(formats_dict[forDict][0])})" 110 | buttons.sbutton(str(buttonName), f"qual {msg_id} {video_format} f") 111 | buttons.sbutton("Best Video", f"qual {msg_id} {best_video} f") 112 | buttons.sbutton("Best Audio", f"qual {msg_id} {best_audio} f") 113 | 114 | buttons.sbutton("Cancel", f"qual {msg_id} cancel f") 115 | YTBUTTONS = InlineKeyboardMarkup(buttons.build_menu(2)) 116 | sendMarkup('Choose video/playlist quality', bot, update, YTBUTTONS) 117 | 118 | 119 | def select_format(update, context): 120 | query = update.callback_query 121 | user_id = query.from_user.id 122 | data = query.data 123 | data = data.split(" ") 124 | task_id = int(data[1]) 125 | listener, uid, link, name = listener_dict[task_id] 126 | if user_id != uid: 127 | return query.answer(text="Don't waste your time!", show_alert=True) 128 | elif data[2] != "cancel": 129 | query.answer() 130 | qual = data[2] 131 | playlist = data[3] 132 | ydl = YoutubeDLHelper(listener) 133 | threading.Thread(target=ydl.add_download,args=(link, f'{DOWNLOAD_DIR}{task_id}', name, qual, playlist)).start() 134 | if len(Interval) == 0: 135 | Interval.append( 136 | setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages) 137 | ) 138 | del listener_dict[task_id] 139 | query.message.delete() 140 | 141 | def watch(update, context): 142 | _watch(context.bot, update) 143 | 144 | def watchTar(update, context): 145 | _watch(context.bot, update, True) 146 | 147 | def watchZip(update, context): 148 | _watch(context.bot, update, True, True) 149 | 150 | def leechWatch(update, context): 151 | _watch(context.bot, update, isLeech=True) 152 | 153 | def leechWatchZip(update, context): 154 | _watch(context.bot, update, True, True, isLeech=True) 155 | 156 | def leechWatchTar(update, context): 157 | _watch(context.bot, update, True, isLeech=True) 158 | 159 | watch_handler = CommandHandler(BotCommands.WatchCommand, watch, 160 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 161 | zip_watch_handler = CommandHandler(BotCommands.ZipWatchCommand, watchZip, 162 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 163 | tar_watch_handler = CommandHandler(BotCommands.TarWatchCommand, watchTar, 164 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 165 | leech_watch_handler = CommandHandler(BotCommands.LeechWatchCommand, leechWatch, 166 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 167 | leech_zip_watch_handler = CommandHandler(BotCommands.LeechZipWatchCommand, leechWatchZip, 168 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 169 | leech_tar_watch_handler = CommandHandler(BotCommands.LeechTarWatchCommand, leechWatchTar, 170 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 171 | quality_handler = CallbackQueryHandler(select_format, pattern="qual", run_async=True) 172 | 173 | dispatcher.add_handler(watch_handler) 174 | dispatcher.add_handler(zip_watch_handler) 175 | dispatcher.add_handler(tar_watch_handler) 176 | dispatcher.add_handler(leech_watch_handler) 177 | dispatcher.add_handler(leech_zip_watch_handler) 178 | dispatcher.add_handler(leech_tar_watch_handler) 179 | dispatcher.add_handler(quality_handler) -------------------------------------------------------------------------------- /captain-definition: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 2, 3 | "dockerfilePath": "./Dockerfile" 4 | } 5 | -------------------------------------------------------------------------------- /config_sample.env: -------------------------------------------------------------------------------- 1 | #Remove this line before deploying 2 | _____REMOVE_THIS_LINE_____=True 3 | 4 | # ENTER BOT TOKEN (Get your BOT_TOKEN by talking to @botfather) 5 | BOT_TOKEN = "" 6 | GDRIVE_FOLDER_ID = "" 7 | OWNER_ID = "" 8 | DOWNLOAD_DIR = "/home/username/mirror-bot/downloads" 9 | DOWNLOAD_STATUS_UPDATE_INTERVAL = 5 10 | AUTO_DELETE_MESSAGE_DURATION = 20 11 | IS_TEAM_DRIVE = "" 12 | TELEGRAM_API = "" 13 | TELEGRAM_HASH = "" 14 | USE_SERVICE_ACCOUNTS = "" 15 | 16 | # Optional config 17 | ACCOUNTS_ZIP_URL = "" #Enter Direct Links TO Import Service Accounts Directly From Urls Instead Of Adding Files To Repo.( Archive the accounts folder to a zip file.) 18 | TOKEN_PICKLE_URL = "" #Enter Direct Links TO Import Credentials Directly From Urls Instead Of Adding Files To Repo. 19 | AUTHORIZED_CHATS = "" #Separated by space 20 | SUDO_USERS = "" #Separated by space 21 | LOGS_CHATS = "" #Separated by space 22 | DATABASE_URL = "" 23 | IGNORE_PENDING_REQUESTS = "" 24 | INDEX_URL = "" 25 | MEGA_KEY = "" 26 | MEGA_USERNAME = "" 27 | MEGA_PASSWORD = "" 28 | BLOCK_MEGA_LINKS = "" 29 | SHORTENER = "" 30 | SHORTENER_API = "" 31 | STOP_DUPLICATE_CLONE = "" 32 | CLONE_LIMIT = "" 33 | TG_SPLIT_SIZE = "" # leave it empty for max size(2GB) 34 | AS_DOCUMENT = "" 35 | RECURSIVE_SEARCH = "" #T/F And Fill drive_folder File Using Driveid.py Script. 36 | UPTOBOX_TOKEN = "" 37 | # View Link button to open file Index Link in browser instead of direct download link 38 | # You can figure out if it's compatible with your Index code or not, open any video from you Index and check if its URL ends with ?a=view, if yes make it True it will work (Compatible with Bhadoo Drive Index) 39 | VIEW_LINK = "" 40 | # GDTOT COOKIES 41 | PHPSESSID = "" 42 | CRYPT = "" 43 | # Add more buttons (two buttons are already added of file link and index link, you can add extra buttons too, these are optional) 44 | # If you don't know what are below entries, simply leave them, Don't fill anything in them. 45 | BUTTON_THREE_NAME = "" 46 | BUTTON_THREE_URL = "" 47 | BUTTON_FOUR_NAME = "" 48 | BUTTON_FOUR_URL = "" 49 | BUTTON_FIVE_NAME = "" 50 | BUTTON_FIVE_URL = "" 51 | -------------------------------------------------------------------------------- /drive_folder: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshpreets63/Mirror-Bot/08cd0fc9ecb8460d964e8970cfa8a1268f6faade/drive_folder -------------------------------------------------------------------------------- /driveid.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | print("\n\n"\ 4 | " Bot can search files recursively, but you have to add the list of drives you want to search.\n"\ 5 | " Use the following format: (You can use 'root' in the ID in case you wan to use main drive.)\n"\ 6 | " teamdrive NAME --> anything that u likes\n"\ 7 | " teamdrive ID --> id of teamdrives in which u likes to search ('root' for main drive)\n"\ 8 | " teamdrive INDEX URL --> enter index url for this drive.\n" \ 9 | " goto the respective drive and copy the url from address bar\n") 10 | msg = '' 11 | if os.path.exists('drive_folder'): 12 | with open('drive_folder', 'r+') as f: 13 | lines = f.read() 14 | if not re.match(r'^\s*$', lines): 15 | print(lines) 16 | print("\n\n"\ 17 | " DO YOU WISH TO KEEP THE ABOVE DETAILS THAT YOU PREVIOUSLY ADDED???? ENTER (y/n)\n"\ 18 | " IF NOTHING SHOWS ENTER n") 19 | while (1): 20 | choice = input() 21 | if choice == 'y' or choice == 'Y': 22 | msg = f'{lines}' 23 | break 24 | elif choice == 'n' or choice == 'N': 25 | break 26 | else: 27 | print("\n\n DO YOU WISH TO KEEP THE ABOVE DETAILS ???? y/n <=== this is option ..... OPEN YOUR EYES & READ...") 28 | num = int(input(" How Many Drive/Folder You Likes To Add : ")) 29 | count = 1 30 | while count <= num : 31 | print(f"\n > DRIVE - {count}\n") 32 | name = input(" Enter Drive NAME (anything) : ") 33 | id = input(" Enter Drive ID : ") 34 | index = input(" Enter Drive INDEX URL (optional) : ") 35 | if not name or not id: 36 | print("\n\n ERROR : Dont leave the name/id without filling.") 37 | exit(1) 38 | name=name.replace(" ", "_") 39 | if index: 40 | if index[-1] == "/": 41 | index = index[:-1] 42 | else: 43 | index = '' 44 | count+=1 45 | msg += f"{name} {id} {index}\n" 46 | with open('drive_folder', 'w') as file: 47 | file.truncate(0) 48 | file.write(msg) 49 | print("\n\n Done!") 50 | -------------------------------------------------------------------------------- /extract: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -lt 1 ]; then 4 | echo "Usage: $(basename $0) FILES" 5 | exit 1 6 | fi 7 | 8 | extract() { 9 | arg="$1" 10 | cd "$(dirname "$arg")" || exit 11 | case "$arg" in 12 | *.tar.bz2) 13 | tar xjf "$arg" --one-top-level 14 | local code=$? 15 | ;; 16 | *.tar.gz) 17 | tar xzf "$arg" --one-top-level 18 | local code=$? 19 | ;; 20 | *.bz2) 21 | bunzip2 "$arg" 22 | local code=$? 23 | ;; 24 | *.gz) 25 | gunzip "$arg" 26 | local code=$? 27 | ;; 28 | *.tar) 29 | tar xf "$arg" --one-top-level 30 | local code=$? 31 | ;; 32 | *.tbz2) 33 | (tar xjf "$arg" --one-top-level) 34 | local code=$? 35 | ;; 36 | *.tgz) 37 | tar xzf "$arg" --one-top-level 38 | local code=$? 39 | ;; 40 | *.tar.xz) 41 | a_dir=$(expr "$arg" : '\(.*\).tar.xz') 42 | 7z x "$arg" -o"$a_dir" 43 | local code=$? 44 | ;; 45 | *.zip) 46 | a_dir=$(expr "$arg" : '\(.*\).zip') 47 | 7z x "$arg" -o"$a_dir" 48 | local code=$? 49 | ;; 50 | *.7z) 51 | a_dir=$(expr "$arg" : '\(.*\).7z') 52 | 7z x "$arg" -o"$a_dir" 53 | local code=$? 54 | ;; 55 | *.Z) 56 | uncompress "$arg" 57 | local code=$? 58 | ;; 59 | *.rar) 60 | a_dir=$(expr "$arg" : '\(.*\).rar') 61 | mkdir "$a_dir" 62 | 7z x "$arg" -o"$a_dir" 63 | local code=$? 64 | ;; 65 | *.iso) 66 | a_dir=$(expr "$arg" : '\(.*\).iso') 67 | 7z x "$arg" -o"$a_dir" 68 | local code=$? 69 | ;; 70 | *.wim) 71 | a_dir=$(expr "$arg" : '\(.*\).wim') 72 | 7z x "$arg" -o"$a_dir" 73 | local code=$? 74 | ;; 75 | *.cab) 76 | a_dir=$(expr "$arg" : '\(.*\).cab') 77 | 7z x "$arg" -o"$a_dir" 78 | local code=$? 79 | ;; 80 | *.apm) 81 | a_dir=$(expr "$arg" : '\(.*\).apm') 82 | 7z x "$arg" -o"$a_dir" 83 | local code=$? 84 | ;; 85 | *.arj) 86 | a_dir=$(expr "$arg" : '\(.*\).arj') 87 | 7z x "$arg" -o"$a_dir" 88 | local code=$? 89 | ;; 90 | *.chm) 91 | a_dir=$(expr "$arg" : '\(.*\).chm') 92 | 7z x "$arg" -o"$a_dir" 93 | local code=$? 94 | ;; 95 | *.cpio) 96 | a_dir=$(expr "$arg" : '\(.*\).cpio') 97 | 7z x "$arg" -o"$a_dir" 98 | local code=$? 99 | ;; 100 | *.cramfs) 101 | a_dir=$(expr "$arg" : '\(.*\).cramfs') 102 | 7z x "$arg" -o"$a_dir" 103 | local code=$? 104 | ;; 105 | *.deb) 106 | a_dir=$(expr "$arg" : '\(.*\).deb') 107 | 7z x "$arg" -o"$a_dir" 108 | local code=$? 109 | ;; 110 | *.dmg) 111 | a_dir=$(expr "$arg" : '\(.*\).dmg') 112 | 7z x "$arg" -o"$a_dir" 113 | local code=$? 114 | ;; 115 | *.fat) 116 | a_dir=$(expr "$arg" : '\(.*\).fat') 117 | 7z x "$arg" -o"$a_dir" 118 | local code=$? 119 | ;; 120 | *.hfs) 121 | a_dir=$(expr "$arg" : '\(.*\).hfs') 122 | 7z x "$arg" -o"$a_dir" 123 | local code=$? 124 | ;; 125 | *.lzh) 126 | a_dir=$(expr "$arg" : '\(.*\).lzh') 127 | 7z x "$arg" -o"$a_dir" 128 | local code=$? 129 | ;; 130 | *.lzma) 131 | a_dir=$(expr "$arg" : '\(.*\).lzma') 132 | 7z x "$arg" -o"$a_dir" 133 | local code=$? 134 | ;; 135 | *.lzma2) 136 | a_dir=$(expr "$arg" : '\(.*\).lzma2') 137 | 7z x "$arg" -o"$a_dir" 138 | local code=$? 139 | ;; 140 | *.mbr) 141 | a_dir=$(expr "$arg" : '\(.*\).mbr') 142 | 7z x "$arg" -o"$a_dir" 143 | local code=$? 144 | ;; 145 | *.msi) 146 | a_dir=$(expr "$arg" : '\(.*\).msi') 147 | 7z x "$arg" -o"$a_dir" 148 | local code=$? 149 | ;; 150 | *.mslz) 151 | a_dir=$(expr "$arg" : '\(.*\).mslz') 152 | 7z x "$arg" -o"$a_dir" 153 | local code=$? 154 | ;; 155 | *.nsis) 156 | a_dir=$(expr "$arg" : '\(.*\).nsis') 157 | 7z x "$arg" -o"$a_dir" 158 | local code=$? 159 | ;; 160 | *.ntfs) 161 | a_dir=$(expr "$arg" : '\(.*\).ntfs') 162 | 7z x "$arg" -o"$a_dir" 163 | local code=$? 164 | ;; 165 | *.rpm) 166 | a_dir=$(expr "$arg" : '\(.*\).rpm') 167 | 7z x "$arg" -o"$a_dir" 168 | local code=$? 169 | ;; 170 | *.squashfs) 171 | a_dir=$(expr "$arg" : '\(.*\).squashfs') 172 | 7z x "$arg" -o"$a_dir" 173 | local code=$? 174 | ;; 175 | *.udf) 176 | a_dir=$(expr "$arg" : '\(.*\).udf') 177 | 7z x "$arg" -o"$a_dir" 178 | local code=$? 179 | ;; 180 | *.vhd) 181 | a_dir=$(expr "$arg" : '\(.*\).vhd') 182 | 7z x "$arg" -o"$a_dir" 183 | local code=$? 184 | ;; 185 | *.xar) 186 | a_dir=$(expr "$arg" : '\(.*\).xar') 187 | 7z x "$arg" -o"$a_dir" 188 | local code=$? 189 | ;; 190 | *) 191 | echo "'$arg' cannot be extracted via extract()" 1>&2 192 | exit 1 193 | ;; 194 | esac 195 | cd - || exit $? 196 | exit $code 197 | } 198 | 199 | extract "$1" 200 | -------------------------------------------------------------------------------- /gen_sa_accounts.py: -------------------------------------------------------------------------------- 1 | import errno 2 | import os 3 | import pickle 4 | import sys 5 | from argparse import ArgumentParser 6 | from base64 import b64decode 7 | from glob import glob 8 | from json import loads 9 | from random import choice 10 | from time import sleep 11 | 12 | from google.auth.transport.requests import Request 13 | from google_auth_oauthlib.flow import InstalledAppFlow 14 | from googleapiclient.discovery import build 15 | from googleapiclient.errors import HttpError 16 | 17 | SCOPES = [ 18 | "https://www.googleapis.com/auth/drive", 19 | "https://www.googleapis.com/auth/cloud-platform", 20 | "https://www.googleapis.com/auth/iam", 21 | ] 22 | project_create_ops = [] 23 | current_key_dump = [] 24 | sleep_time = 30 25 | 26 | 27 | # Create count SAs in project 28 | def _create_accounts(service, project, count): 29 | batch = service.new_batch_http_request(callback=_def_batch_resp) 30 | for _ in range(count): 31 | aid = _generate_id("mfc-") 32 | batch.add( 33 | service.projects() 34 | .serviceAccounts() 35 | .create( 36 | name="projects/" + project, 37 | body={"accountId": aid, "serviceAccount": {"displayName": aid}}, 38 | ) 39 | ) 40 | batch.execute() 41 | 42 | 43 | # Create accounts needed to fill project 44 | def _create_remaining_accounts(iam, project): 45 | print("Creating accounts in %s" % project) 46 | sa_count = len(_list_sas(iam, project)) 47 | while sa_count != 100: 48 | _create_accounts(iam, project, 100 - sa_count) 49 | sa_count = len(_list_sas(iam, project)) 50 | 51 | 52 | # Generate a random id 53 | def _generate_id(prefix="saf-"): 54 | chars = "-abcdefghijklmnopqrstuvwxyz1234567890" 55 | return prefix + "".join(choice(chars) for _ in range(25)) + choice(chars[1:]) 56 | 57 | 58 | # List projects using service 59 | def _get_projects(service): 60 | return [i["projectId"] for i in service.projects().list().execute()["projects"]] 61 | 62 | 63 | # Default batch callback handler 64 | def _def_batch_resp(id, resp, exception): 65 | if exception is not None: 66 | if str(exception).startswith(" 0: 242 | current_count = len(_get_projects(cloud)) 243 | if current_count + create_projects <= max_projects: 244 | print("Creating %d projects" % (create_projects)) 245 | nprjs = _create_projects(cloud, create_projects) 246 | selected_projects = nprjs 247 | else: 248 | sys.exit( 249 | "No, you cannot create %d new project (s).\n" 250 | "Please reduce value of --quick-setup.\n" 251 | "Remember that you can totally create %d projects (%d already).\n" 252 | "Please do not delete existing projects unless you know what you are doing" 253 | % (create_projects, max_projects, current_count) 254 | ) 255 | else: 256 | print( 257 | "Will overwrite all service accounts in existing projects.\n" 258 | "So make sure you have some projects already." 259 | ) 260 | input("Press Enter to continue...") 261 | 262 | if enable_services: 263 | ste = [enable_services] 264 | if enable_services == "~": 265 | ste = selected_projects 266 | elif enable_services == "*": 267 | ste = _get_projects(cloud) 268 | services = [i + ".googleapis.com" for i in services] 269 | print("Enabling services") 270 | _enable_services(serviceusage, ste, services) 271 | if create_sas: 272 | stc = [create_sas] 273 | if create_sas == "~": 274 | stc = selected_projects 275 | elif create_sas == "*": 276 | stc = _get_projects(cloud) 277 | for i in stc: 278 | _create_remaining_accounts(iam, i) 279 | if download_keys: 280 | try: 281 | os.mkdir(path) 282 | except OSError as e: 283 | if e.errno != errno.EEXIST: 284 | raise 285 | std = [download_keys] 286 | if download_keys == "~": 287 | std = selected_projects 288 | elif download_keys == "*": 289 | std = _get_projects(cloud) 290 | _create_sa_keys(iam, std, path) 291 | if delete_sas: 292 | std = [] 293 | std.append(delete_sas) 294 | if delete_sas == "~": 295 | std = selected_projects 296 | elif delete_sas == "*": 297 | std = _get_projects(cloud) 298 | for i in std: 299 | print("Deleting service accounts in %s" % i) 300 | _delete_sas(iam, i) 301 | 302 | 303 | if __name__ == "__main__": 304 | parse = ArgumentParser(description="A tool to create Google service accounts.") 305 | parse.add_argument( 306 | "--path", 307 | "-p", 308 | default="accounts", 309 | help="Specify an alternate directory to output the credential files.", 310 | ) 311 | parse.add_argument( 312 | "--token", default="token_sa.pickle", help="Specify the pickle token file path." 313 | ) 314 | parse.add_argument( 315 | "--credentials", 316 | default="credentials.json", 317 | help="Specify the credentials file path.", 318 | ) 319 | parse.add_argument( 320 | "--list-projects", 321 | default=False, 322 | action="store_true", 323 | help="List projects viewable by the user.", 324 | ) 325 | parse.add_argument( 326 | "--list-sas", default=False, help="List service accounts in a project." 327 | ) 328 | parse.add_argument( 329 | "--create-projects", type=int, default=None, help="Creates up to N projects." 330 | ) 331 | parse.add_argument( 332 | "--max-projects", 333 | type=int, 334 | default=12, 335 | help="Max amount of project allowed. Default: 12", 336 | ) 337 | parse.add_argument( 338 | "--enable-services", 339 | default=None, 340 | help="Enables services on the project. Default: IAM and Drive", 341 | ) 342 | parse.add_argument( 343 | "--services", 344 | nargs="+", 345 | default=["iam", "drive"], 346 | help="Specify a different set of services to enable. Overrides the default.", 347 | ) 348 | parse.add_argument( 349 | "--create-sas", default=None, help="Create service accounts in a project." 350 | ) 351 | parse.add_argument( 352 | "--delete-sas", default=None, help="Delete service accounts in a project." 353 | ) 354 | parse.add_argument( 355 | "--download-keys", 356 | default=None, 357 | help="Download keys for all the service accounts in a project.", 358 | ) 359 | parse.add_argument( 360 | "--quick-setup", 361 | default=None, 362 | type=int, 363 | help="Create projects, enable services, create service accounts and download keys. ", 364 | ) 365 | parse.add_argument( 366 | "--new-only", 367 | default=False, 368 | action="store_true", 369 | help="Do not use exisiting projects.", 370 | ) 371 | args = parse.parse_args() 372 | # If credentials file is invalid, search for one. 373 | if not os.path.exists(args.credentials): 374 | options = glob("*.json") 375 | print( 376 | "No credentials found at %s. Please enable the Drive API in:\n" 377 | "https://developers.google.com/drive/api/v3/quickstart/python\n" 378 | "and save the json file as credentials.json" % args.credentials 379 | ) 380 | if len(options) < 1: 381 | exit(-1) 382 | else: 383 | print("Select a credentials file below.") 384 | inp_options = [str(i) for i in list(range(1, len(options) + 1))] + options 385 | for i in range(len(options)): 386 | print(" %d) %s" % (i + 1, options[i])) 387 | inp = None 388 | while True: 389 | inp = input("> ") 390 | if inp in inp_options: 391 | break 392 | args.credentials = inp if inp in options else options[int(inp) - 1] 393 | print( 394 | "Use --credentials %s next time to use this credentials file." 395 | % args.credentials 396 | ) 397 | if args.quick_setup: 398 | opt = "*" 399 | if args.new_only: 400 | opt = "~" 401 | args.services = ["iam", "drive"] 402 | args.create_projects = args.quick_setup 403 | args.enable_services = opt 404 | args.create_sas = opt 405 | args.download_keys = opt 406 | resp = serviceaccountfactory( 407 | path=args.path, 408 | token=args.token, 409 | credentials=args.credentials, 410 | list_projects=args.list_projects, 411 | list_sas=args.list_sas, 412 | create_projects=args.create_projects, 413 | max_projects=args.max_projects, 414 | create_sas=args.create_sas, 415 | delete_sas=args.delete_sas, 416 | enable_services=args.enable_services, 417 | services=args.services, 418 | download_keys=args.download_keys, 419 | ) 420 | if resp is not None: 421 | if args.list_projects: 422 | if resp: 423 | print("Projects (%d):" % len(resp)) 424 | for i in resp: 425 | print(" " + i) 426 | else: 427 | print("No projects.") 428 | elif args.list_sas: 429 | if resp: 430 | print("Service accounts in %s (%d):" % (args.list_sas, len(resp))) 431 | for i in resp: 432 | print(" {} ({})".format(i["email"], i["uniqueId"])) 433 | else: 434 | print("No service accounts.") 435 | -------------------------------------------------------------------------------- /generate_drive_token.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pickle 3 | 4 | from google.auth.transport.requests import Request 5 | from google_auth_oauthlib.flow import InstalledAppFlow 6 | 7 | credentials = None 8 | __G_DRIVE_TOKEN_FILE = "token.pickle" 9 | __OAUTH_SCOPE = ["https://www.googleapis.com/auth/drive"] 10 | if os.path.exists(__G_DRIVE_TOKEN_FILE): 11 | with open(__G_DRIVE_TOKEN_FILE, "rb") as f: 12 | credentials = pickle.load(f) 13 | if ( 14 | (credentials is None or not credentials.valid) 15 | and credentials 16 | and credentials.expired 17 | and credentials.refresh_token 18 | ): 19 | credentials.refresh(Request()) 20 | else: 21 | flow = InstalledAppFlow.from_client_secrets_file("credentials.json", __OAUTH_SCOPE) 22 | credentials = flow.run_console(port=0) 23 | 24 | # Save the credentials for the next run 25 | with open(__G_DRIVE_TOKEN_FILE, "wb") as token: 26 | pickle.dump(credentials, token) 27 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | worker: Dockerfile 4 | run: 5 | worker: bash start.sh -------------------------------------------------------------------------------- /heroku_push.sh: -------------------------------------------------------------------------------- 1 | #bin/bash 2 | appname=elephant$RANDOM 3 | if ! command -v heroku 4 | then 5 | echo "Heroku could not be found" 6 | exit 7 | fi 8 | echo "Are You Logged In On Heroku CLi? Type Y OR N." 9 | read login 10 | if ! ( [ "$login" == "Y" ] || [ "$login" == "y" ] ) 11 | then 12 | echo "Login First" 13 | exit 14 | fi 15 | echo "Passing Fake Git UserName" 16 | git config --global user.name Your Name 17 | git config --global user.email you@example.com 18 | echo "1. Type 1 If You Want To Host A New Bot." 19 | echo "2. Type 2 If You Want To Update Old Bot." 20 | read update 21 | if ! [ "$update" == "2" ] 22 | then 23 | echo "Hosting A New Bot" 24 | if ! [ -f config.env ] 25 | then 26 | echo "Config Not Found" 27 | exit 28 | fi 29 | echo -e "Making a New App\n" 30 | echo -e "Want To Enter Your Own App Name? (Random Name:-$appname Will Be Selected By Default.)\n" 31 | echo -e "Enter An Unique App Name Starting With Lowercase Letter.\n" 32 | echo -e "Dont Enter Anything For Random App Name.(Just Press Enter And Leave It Blank.)\n" 33 | read name 34 | name="${name:=$appname}" 35 | appname=$name 36 | clear 37 | echo -e "Choose The Server Region\n" 38 | echo -e "Enter 1 For US\nEnter 2 For EU\n\nJust Press Enter For US Region(Default)" 39 | read region 40 | region="${region:=1}" 41 | if [ $region == 1 ] 42 | then 43 | region=us 44 | elif [ $region == 2 ] 45 | then 46 | region=eu 47 | else 48 | echo -e "Wrong Input Detected" 49 | echo -e "US Server Is Selected" 50 | region=us 51 | fi 52 | echo "Using $appname As Name." 53 | echo "Using $region As Region For The Bot." 54 | heroku create --region $region $appname 55 | heroku git:remote -a $appname 56 | heroku stack:set container -a $appname 57 | echo "Done" 58 | echo "Pushing" 59 | if ! [ -d accounts/ ] 60 | then 61 | git add . 62 | git add -f token.pickle config.env drive_folder 63 | git commit -m "changes" 64 | git push heroku 65 | else 66 | echo "Pushing Accounts Folder Too" 67 | git add . 68 | git add -f token.pickle config.env drive_folder accounts accounts/* 69 | git commit -m "changes" 70 | git push heroku 71 | fi 72 | clear 73 | echo "Avoiding suspension." 74 | heroku apps:destroy --confirm $appname 75 | heroku create --region $region $appname 76 | heroku git:remote -a $appname 77 | heroku stack:set container -a $appname 78 | echo "Done" 79 | echo "Pushing" 80 | if ! [ -d accounts/ ] 81 | then 82 | git add . 83 | git add -f token.pickle config.env drive_folder 84 | git commit -m "changes" 85 | git push heroku 86 | else 87 | echo "Pushing Accounts Folder Too" 88 | git add . 89 | git add -f token.pickle config.env drive_folder accounts accounts/* 90 | git commit -m "changes" 91 | git push heroku 92 | fi 93 | heroku ps:scale worker=0 -a $appname 94 | heroku ps:scale worker=1 -a $appname 95 | echo "Done" 96 | echo "Enjoy" 97 | else 98 | echo "Updating Bot." 99 | git add . 100 | if [ -d accounts/ ] 101 | then 102 | git add -f token.pickle config.env drive_folder accounts accounts/* 103 | git commit -m "changes" 104 | git push heroku 105 | else 106 | git add -f token.pickle config.env drive_folder 107 | git commit -m "changes" 108 | git push heroku 109 | fi 110 | fi 111 | echo "Done" 112 | echo "Type" 113 | echo "heroku logs -t" 114 | echo "To Check Bot Logs Here." -------------------------------------------------------------------------------- /pextract: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -lt 1 ]; then 4 | echo "Usage: $(basename $0) FILES" 5 | exit 1 6 | fi 7 | 8 | extract() { 9 | arg="$1" 10 | pswd="$2" 11 | cd "$(dirname "$arg")" || exit 12 | case "$arg" in 13 | *.tar.bz2) 14 | tar xjf "$arg" --one-top-level 15 | local code=$? 16 | ;; 17 | *.tar.gz) 18 | tar xzf "$arg" --one-top-level 19 | local code=$? 20 | ;; 21 | *.bz2) 22 | bunzip2 "$arg" 23 | local code=$? 24 | ;; 25 | *.gz) 26 | gunzip "$arg" 27 | local code=$? 28 | ;; 29 | *.tar) 30 | tar xf "$arg" --one-top-level 31 | local code=$? 32 | ;; 33 | *.tbz2) 34 | (tar xjf "$arg" --one-top-level) 35 | local code=$? 36 | ;; 37 | *.tgz) 38 | tar xzf "$arg" --one-top-level 39 | local code=$? 40 | ;; 41 | *.tar.xz) 42 | a_dir=$(expr "$arg" : '\(.*\).tar.xz') 43 | 7z x "$arg" -o"$a_dir" -p"$pswd" 44 | local code=$? 45 | ;; 46 | *.zip) 47 | a_dir=$(expr "$arg" : '\(.*\).zip') 48 | 7z x "$arg" -o"$a_dir" -p"$pswd" 49 | local code=$? 50 | ;; 51 | *.7z) 52 | a_dir=$(expr "$arg" : '\(.*\).7z') 53 | 7z x "$arg" -o"$a_dir" -p"$pswd" 54 | local code=$? 55 | ;; 56 | *.Z) 57 | uncompress "$arg" 58 | local code=$? 59 | ;; 60 | *.rar) 61 | a_dir=$(expr "$arg" : '\(.*\).rar') 62 | mkdir "$a_dir" 63 | 7z x "$arg" -o"$a_dir" -p"$pswd" 64 | local code=$? 65 | ;; 66 | *.iso) 67 | a_dir=$(expr "$arg" : '\(.*\).iso') 68 | 7z x "$arg" -o"$a_dir" -p"$pswd" 69 | local code=$? 70 | ;; 71 | *.wim) 72 | a_dir=$(expr "$arg" : '\(.*\).wim') 73 | 7z x "$arg" -o"$a_dir" -p"$pswd" 74 | local code=$? 75 | ;; 76 | *.cab) 77 | a_dir=$(expr "$arg" : '\(.*\).cab') 78 | 7z x "$arg" -o"$a_dir" -p"$pswd" 79 | local code=$? 80 | ;; 81 | *.apm) 82 | a_dir=$(expr "$arg" : '\(.*\).apm') 83 | 7z x "$arg" -o"$a_dir" -p"$pswd" 84 | local code=$? 85 | ;; 86 | *.arj) 87 | a_dir=$(expr "$arg" : '\(.*\).arj') 88 | 7z x "$arg" -o"$a_dir" -p"$pswd" 89 | local code=$? 90 | ;; 91 | *.chm) 92 | a_dir=$(expr "$arg" : '\(.*\).chm') 93 | 7z x "$arg" -o"$a_dir" -p"$pswd" 94 | local code=$? 95 | ;; 96 | *.cpio) 97 | a_dir=$(expr "$arg" : '\(.*\).cpio') 98 | 7z x "$arg" -o"$a_dir" -p"$pswd" 99 | local code=$? 100 | ;; 101 | *.cramfs) 102 | a_dir=$(expr "$arg" : '\(.*\).cramfs') 103 | 7z x "$arg" -o"$a_dir" -p"$pswd" 104 | local code=$? 105 | ;; 106 | *.deb) 107 | a_dir=$(expr "$arg" : '\(.*\).deb') 108 | 7z x "$arg" -o"$a_dir" -p"$pswd" 109 | local code=$? 110 | ;; 111 | *.dmg) 112 | a_dir=$(expr "$arg" : '\(.*\).dmg') 113 | 7z x "$arg" -o"$a_dir" -p"$pswd" 114 | local code=$? 115 | ;; 116 | *.fat) 117 | a_dir=$(expr "$arg" : '\(.*\).fat') 118 | 7z x "$arg" -o"$a_dir" -p"$pswd" 119 | local code=$? 120 | ;; 121 | *.hfs) 122 | a_dir=$(expr "$arg" : '\(.*\).hfs') 123 | 7z x "$arg" -o"$a_dir" -p"$pswd" 124 | local code=$? 125 | ;; 126 | *.lzh) 127 | a_dir=$(expr "$arg" : '\(.*\).lzh') 128 | 7z x "$arg" -o"$a_dir" -p"$pswd" 129 | local code=$? 130 | ;; 131 | *.lzma) 132 | a_dir=$(expr "$arg" : '\(.*\).lzma') 133 | 7z x "$arg" -o"$a_dir" -p"$pswd" 134 | local code=$? 135 | ;; 136 | *.lzma2) 137 | a_dir=$(expr "$arg" : '\(.*\).lzma2') 138 | 7z x "$arg" -o"$a_dir" -p"$pswd" 139 | local code=$? 140 | ;; 141 | *.mbr) 142 | a_dir=$(expr "$arg" : '\(.*\).mbr') 143 | 7z x "$arg" -o"$a_dir" -p"$pswd" 144 | local code=$? 145 | ;; 146 | *.msi) 147 | a_dir=$(expr "$arg" : '\(.*\).msi') 148 | 7z x "$arg" -o"$a_dir" -p"$pswd" 149 | local code=$? 150 | ;; 151 | *.mslz) 152 | a_dir=$(expr "$arg" : '\(.*\).mslz') 153 | 7z x "$arg" -o"$a_dir" -p"$pswd" 154 | local code=$? 155 | ;; 156 | *.nsis) 157 | a_dir=$(expr "$arg" : '\(.*\).nsis') 158 | 7z x "$arg" -o"$a_dir" -p"$pswd" 159 | local code=$? 160 | ;; 161 | *.ntfs) 162 | a_dir=$(expr "$arg" : '\(.*\).ntfs') 163 | 7z x "$arg" -o"$a_dir" -p"$pswd" 164 | local code=$? 165 | ;; 166 | *.rpm) 167 | a_dir=$(expr "$arg" : '\(.*\).rpm') 168 | 7z x "$arg" -o"$a_dir" -p"$pswd" 169 | local code=$? 170 | ;; 171 | *.squashfs) 172 | a_dir=$(expr "$arg" : '\(.*\).squashfs') 173 | 7z x "$arg" -o"$a_dir" -p"$pswd" 174 | local code=$? 175 | ;; 176 | *.udf) 177 | a_dir=$(expr "$arg" : '\(.*\).udf') 178 | 7z x "$arg" -o"$a_dir" -p"$pswd" 179 | local code=$? 180 | ;; 181 | *.vhd) 182 | a_dir=$(expr "$arg" : '\(.*\).vhd') 183 | 7z x "$arg" -o"$a_dir" -p"$pswd" 184 | local code=$? 185 | ;; 186 | *.xar) 187 | a_dir=$(expr "$arg" : '\(.*\).xar') 188 | 7z x "$arg" -o"$a_dir" -p"$pswd" 189 | local code=$? 190 | ;; 191 | *) 192 | echo "'$arg' cannot be extracted via extract()" 1>&2 193 | exit 1 194 | ;; 195 | esac 196 | cd - || exit $? 197 | exit $code 198 | } 199 | 200 | extract "$1" "$2" 201 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | appdirs 2 | aria2p 3 | beautifulsoup4 4 | google-api-python-client 5 | google-auth-httplib2 6 | google-auth-oauthlib 7 | js2py 8 | lk21 9 | lxml 10 | megasdkrestclient 11 | psutil 12 | pybase64 13 | hachoir 14 | pillow 15 | filesplit 16 | speedtest-cli 17 | cfscrape 18 | Pyrogram==0.18.0 19 | python-dotenv 20 | python-magic 21 | python-telegram-bot 22 | requests 23 | telegraph 24 | tenacity 25 | TgCrypto 26 | youtube_dl -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | ./aria.sh; python3 -m bot 2 | --------------------------------------------------------------------------------