├── .netrc ├── bot ├── modules │ ├── __init__.py │ ├── delete.py │ ├── list.py │ ├── mirror_status.py │ ├── count.py │ ├── shell.py │ ├── mediainfo.py │ ├── speedtest.py │ ├── watch.py │ ├── updates.py │ ├── usage.py │ ├── clone.py │ ├── cancel_mirror.py │ ├── eval.py │ ├── authorize.py │ ├── config.py │ └── search.py ├── helper │ ├── ext_utils │ │ ├── __init__.py │ │ ├── exceptions.py │ │ ├── db_handler.py │ │ ├── fs_utils.py │ │ └── bot_utils.py │ ├── mirror_utils │ │ ├── __init__.py │ │ ├── status_utils │ │ │ ├── __init__.py │ │ │ ├── listeners.py │ │ │ ├── tar_status.py │ │ │ ├── extract_status.py │ │ │ ├── status.py │ │ │ ├── upload_status.py │ │ │ ├── telegram_download_status.py │ │ │ ├── youtube_dl_download_status.py │ │ │ ├── gdownload_status.py │ │ │ ├── mega_download_status.py │ │ │ └── aria_download_status.py │ │ ├── upload_utils │ │ │ └── __init__.py │ │ └── download_utils │ │ │ ├── __init__.py │ │ │ ├── download_helper.py │ │ │ ├── telegram_downloader.py │ │ │ ├── direct_link_generator_license.md │ │ │ ├── aria2_download.py │ │ │ ├── youtube_dl_download_helper.py │ │ │ ├── mega_downloader.py │ │ │ └── direct_link_generator.py │ ├── telegram_helper │ │ ├── __init__.py │ │ ├── button_build.py │ │ ├── bot_commands.py │ │ ├── filters.py │ │ └── message_utils.py │ ├── custom_filters.py │ └── __init__.py ├── __init__.py └── __main__.py ├── captain-definition ├── heroku.yml ├── .gitmodules ├── requirements-cli.txt ├── .gitignore ├── aria.bat ├── vendor └── cmrudl.py │ ├── .gitignore │ ├── .editorconfig │ └── Readme.md ├── start.sh ├── requirements.txt ├── Dockerfile ├── generate_drive_token.py ├── aria.sh ├── config_sample.env ├── add_to_team_drive.py ├── extract ├── pextract ├── app.json └── gen_sa_accounts.py /.netrc: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bot/modules/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bot/helper/ext_utils/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bot/helper/telegram_helper/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/upload_utils/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /captain-definition: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 2, 3 | "dockerfilePath": "./Dockerfile" 4 | } 5 | -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | build: 2 | docker: 3 | worker: Dockerfile 4 | run: 5 | worker: bash start.sh -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/cmrudl.py"] 2 | path = vendor/cmrudl.py 3 | url = https://github.com/JrMasterModelBuilder/cmrudl.py.git 4 | -------------------------------------------------------------------------------- /requirements-cli.txt: -------------------------------------------------------------------------------- 1 | oauth2client 2 | google-api-python-client 3 | progress 4 | progressbar2 5 | httplib2shim 6 | google_auth_oauthlib 7 | pyrogram 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /vendor/cmrudl.py/.gitignore: -------------------------------------------------------------------------------- 1 | # OS files 2 | .DS_Store 3 | .DS_Store? 4 | ._* 5 | .Spotlight-V100 6 | .Trashes 7 | Icon? 8 | ehthumbs.db 9 | Thumbs.db 10 | 11 | # Python files 12 | __pycache__/ 13 | *.py[cod] 14 | *$py.class 15 | -------------------------------------------------------------------------------- /vendor/cmrudl.py/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = tab 7 | indent_size = 4 8 | charset = utf8 9 | 10 | [*.{yml,yaml}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /bot/helper/ext_utils/exceptions.py: -------------------------------------------------------------------------------- 1 | class DirectDownloadLinkException(Exception): 2 | """Not method found for extracting direct download link from the http link""" 3 | pass 4 | 5 | 6 | class NotSupportedExtractionArchive(Exception): 7 | """The archive format use is trying to extract is not supported""" 8 | pass 9 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | if [[ -n $TOKEN_PICKLE_URL ]]; then 2 | wget -q $TOKEN_PICKLE_URL -O /usr/src/app/token.pickle 3 | fi 4 | 5 | if [[ -n $ACCOUNTS_ZIP_URL ]]; then 6 | wget -q $ACCOUNTS_ZIP_URL -O /usr/src/app/accounts.zip 7 | unzip accounts.zip -d /usr/src/app/accounts 8 | rm accounts.zip 9 | fi 10 | 11 | ./aria.sh; python3 -m bot 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | aria2p 3 | appdirs 4 | beautifulsoup4 5 | feedparser 6 | gitpython 7 | google-api-python-client 8 | google-auth-httplib2 9 | google-auth-oauthlib 10 | heroku3 11 | html-telegraph-poster 12 | js2py 13 | lk21 14 | lxml 15 | messages 16 | psutil 17 | psycopg2-binary 18 | pybase64 19 | pyrogram 20 | python-dotenv 21 | python-magic 22 | python-telegram-bot 23 | pytz 24 | requests 25 | speedtest-cli 26 | telegraph 27 | tenacity 28 | TgCrypto 29 | youtube_dl 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM breakdowns/mega-sdk-python:latest 2 | 3 | WORKDIR /usr/src/app 4 | RUN chmod 777 /usr/src/app 5 | 6 | COPY requirements.txt . 7 | RUN pip3 install --no-cache-dir -r requirements.txt 8 | 9 | COPY extract /usr/local/bin 10 | COPY pextract /usr/local/bin 11 | RUN chmod +x /usr/local/bin/extract && chmod +x /usr/local/bin/pextract 12 | COPY . . 13 | COPY .netrc /root/.netrc 14 | RUN chmod 600 /usr/src/app/.netrc 15 | RUN chmod +x aria.sh 16 | 17 | CMD ["bash","start.sh"] 18 | -------------------------------------------------------------------------------- /bot/helper/telegram_helper/button_build.py: -------------------------------------------------------------------------------- 1 | from telegram import InlineKeyboardButton 2 | 3 | class ButtonMaker: 4 | def __init__(self): 5 | self.button = [] 6 | 7 | def buildbutton(self, key, link): 8 | self.button.append(InlineKeyboardButton(text = key, url = link)) 9 | 10 | def build_menu(self, n_cols, footer_buttons=None, header_buttons=None): 11 | menu = [self.button[i:i + n_cols] for i in range(0, len(self.button), n_cols)] 12 | if header_buttons: 13 | menu.insert(0, header_buttons) 14 | if footer_buttons: 15 | menu.append(footer_buttons) 16 | return menu 17 | -------------------------------------------------------------------------------- /bot/helper/custom_filters.py: -------------------------------------------------------------------------------- 1 | from pyrogram import filters 2 | 3 | def callback_data(data): 4 | def func(flt, client, callback_query): 5 | return callback_query.data in flt.data 6 | 7 | data = data if isinstance(data, list) else [data] 8 | return filters.create( 9 | func, 10 | 'CustomCallbackDataFilter', 11 | data=data 12 | ) 13 | 14 | def callback_chat(chats): 15 | def func(flt, client, callback_query): 16 | return callback_query.message.chat.id in flt.chats 17 | 18 | chats = chats if isinstance(chats, list) else [chats] 19 | return filters.create( 20 | func, 21 | 'CustomCallbackChatsFilter', 22 | chats=chats 23 | ) 24 | -------------------------------------------------------------------------------- /generate_drive_token.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import os 3 | from google_auth_oauthlib.flow import InstalledAppFlow 4 | from google.auth.transport.requests import Request 5 | 6 | credentials = None 7 | __G_DRIVE_TOKEN_FILE = "token.pickle" 8 | __OAUTH_SCOPE = ["https://www.googleapis.com/auth/drive"] 9 | if os.path.exists(__G_DRIVE_TOKEN_FILE): 10 | with open(__G_DRIVE_TOKEN_FILE, 'rb') as f: 11 | credentials = pickle.load(f) 12 | if credentials is None or not credentials.valid: 13 | if credentials and credentials.expired and credentials.refresh_token: 14 | credentials.refresh(Request()) 15 | else: 16 | flow = InstalledAppFlow.from_client_secrets_file( 17 | 'credentials.json', __OAUTH_SCOPE) 18 | credentials = flow.run_console(port=0) 19 | 20 | # Save the credentials for the next run 21 | with open(__G_DRIVE_TOKEN_FILE, 'wb') as token: 22 | pickle.dump(credentials, token) -------------------------------------------------------------------------------- /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/tar_status.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | from bot.helper.ext_utils.bot_utils import get_readable_file_size, MirrorStatus 3 | 4 | 5 | class TarStatus(Status): 6 | def __init__(self, name, path, size): 7 | self.__name = name 8 | self.__path = path 9 | self.__size = size 10 | 11 | # The progress of Tar function cannot be tracked. So we just return dummy values. 12 | # If this is possible in future,we should implement it 13 | 14 | def progress(self): 15 | return '0' 16 | 17 | def speed(self): 18 | return '0' 19 | 20 | def name(self): 21 | return self.__name 22 | 23 | def path(self): 24 | return self.__path 25 | 26 | def size(self): 27 | return get_readable_file_size(self.__size) 28 | 29 | def eta(self): 30 | return '0s' 31 | 32 | def status(self): 33 | return MirrorStatus.STATUS_ARCHIVING 34 | 35 | def processed_bytes(self): 36 | return 0 37 | -------------------------------------------------------------------------------- /vendor/cmrudl.py/Readme.md: -------------------------------------------------------------------------------- 1 | # cmrudl.py 2 | cmrudl.py - Cloud Mail.RU download script 3 | # Usage 4 | ``` 5 | usage: cmrudl.py [-h] [-v] [-V] [-D] [-B BUFFER] [-t TIMEOUT] [-M] [-d DIR] 6 | url [file] 7 | cmrudl.py 1.0.2 8 | cmrudl.py 1.0.3 9 | Copyright (c) 2019 JrMasterModelBuilder MPL-2.0 10 | positional arguments: 11 | url URL 12 | file FILE 13 | optional arguments: 14 | -h, --help show this help message and exit 15 | -v, --version Print version 16 | -V, --verbose Verbose mode 17 | -D, --debug Debug output 18 | -B BUFFER, --buffer BUFFER 19 | Buffer size 20 | -t TIMEOUT, --timeout TIMEOUT 21 | Request timeout in seconds 22 | -M, --mtime Use server modified time 23 | -d DIR, --dir DIR Output directory 24 | ``` 25 | # Bugs 26 | If you find a bug or have compatibility issues, please open a ticket under issues section for this repository. -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/extract_status.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | from bot.helper.ext_utils.bot_utils import get_readable_file_size, MirrorStatus 3 | 4 | 5 | class ExtractStatus(Status): 6 | def __init__(self, name, path, size): 7 | self.__name = name 8 | self.__path = path 9 | self.__size = size 10 | 11 | # The progress of extract function cannot be tracked. So we just return dummy values. 12 | # If this is possible in future,we should implement it 13 | 14 | def progress(self): 15 | return '0' 16 | 17 | def speed(self): 18 | return '0' 19 | 20 | def name(self): 21 | return self.__name 22 | 23 | def path(self): 24 | return self.__path 25 | 26 | def size(self): 27 | return get_readable_file_size(self.__size) 28 | 29 | def eta(self): 30 | return '0s' 31 | 32 | def status(self): 33 | return MirrorStatus.STATUS_EXTRACTING 34 | 35 | def processed_bytes(self): 36 | return 0 37 | -------------------------------------------------------------------------------- /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 = '' # Name of the download; empty string if no download has been started 13 | self.size = 0.0 # Size of the download 14 | self.downloaded_bytes = 0.0 # Bytes downloaded 15 | self.speed = 0.0 # Download speed in bytes per second 16 | self.progress = 0.0 17 | self.progress_string = '0.00%' 18 | self.eta = 0 # Estimated time of download complete 19 | self.eta_string = '0s' # A listener class which have event callbacks 20 | self._resource_lock = threading.Lock() 21 | 22 | def add_download(self, link: str, path): 23 | raise MethodNotImplementedError 24 | 25 | def cancel_download(self): 26 | # Returns None if successfully cancelled, else error string 27 | raise MethodNotImplementedError 28 | -------------------------------------------------------------------------------- /bot/modules/delete.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CommandHandler 2 | import threading 3 | from telegram import Update 4 | from bot import dispatcher, LOGGER 5 | from bot.helper.telegram_helper.message_utils import auto_delete_message, sendMessage 6 | from bot.helper.telegram_helper.filters import CustomFilters 7 | from bot.helper.telegram_helper.bot_commands import BotCommands 8 | from bot.helper.mirror_utils.upload_utils import gdriveTools 9 | 10 | 11 | def deletefile(update, context): 12 | msg_args = update.message.text.split(None, 1) 13 | msg = '' 14 | try: 15 | link = msg_args[1] 16 | LOGGER.info(msg_args[1]) 17 | except IndexError: 18 | msg = 'Send a link along with command' 19 | 20 | if msg == '' : 21 | drive = gdriveTools.GoogleDriveHelper() 22 | msg = drive.deletefile(link) 23 | LOGGER.info(f"DeleteFileCmd : {msg}") 24 | reply_message = sendMessage(msg, context.bot, update) 25 | 26 | threading.Thread(target=auto_delete_message, args=(context.bot, update.message, reply_message)).start() 27 | 28 | delete_handler = CommandHandler(command=BotCommands.DeleteCommand, callback=deletefile, filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True) 29 | dispatcher.add_handler(delete_handler) 30 | -------------------------------------------------------------------------------- /bot/modules/list.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CommandHandler 2 | from bot.helper.mirror_utils.upload_utils.gdriveTools import GoogleDriveHelper 3 | from bot import LOGGER, dispatcher 4 | from bot.helper.telegram_helper.message_utils import sendMessage, sendMarkup, editMessage 5 | from bot.helper.telegram_helper.filters import CustomFilters 6 | import threading 7 | from bot.helper.telegram_helper.bot_commands import BotCommands 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 | msg, button = gdrive.drive_list(search) 17 | 18 | if button: 19 | editMessage(msg, reply, button) 20 | else: 21 | editMessage('No result found', reply, button) 22 | 23 | except IndexError: 24 | sendMessage('Send a search key along with command', context.bot, update) 25 | 26 | 27 | list_handler = CommandHandler(BotCommands.ListCommand, list_drive, filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 28 | dispatcher.add_handler(list_handler) 29 | -------------------------------------------------------------------------------- /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 | 6 | def progress(self): 7 | """ 8 | Calculates the progress of the mirror (upload or download) 9 | :return: progress in percentage 10 | """ 11 | raise NotImplementedError 12 | 13 | def speed(self): 14 | """:return: speed in bytes per second""" 15 | raise NotImplementedError 16 | 17 | def name(self): 18 | """:return name of file/directory being processed""" 19 | raise NotImplementedError 20 | 21 | def path(self): 22 | """:return path of the file/directory""" 23 | raise NotImplementedError 24 | 25 | def size(self): 26 | """:return Size of file folder""" 27 | raise NotImplementedError 28 | 29 | def eta(self): 30 | """:return ETA of the process to complete""" 31 | raise NotImplementedError 32 | 33 | def status(self): 34 | """:return String describing what is the object of this class will be tracking (upload/download/something 35 | else) """ 36 | raise NotImplementedError 37 | 38 | def processed_bytes(self): 39 | """:return The size of file that has been processed (downloaded/uploaded/archived)""" 40 | raise NotImplementedError 41 | -------------------------------------------------------------------------------- /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.TarMirrorCommand = 'tarmirror' 7 | self.CancelMirror = 'cancel' 8 | self.CancelAllCommand = 'cancelall' 9 | self.ListCommand = 'list' 10 | self.StatusCommand = 'status' 11 | self.AuthorizedUsersCommand = 'users' 12 | self.AuthorizeCommand = 'authorize' 13 | self.UnAuthorizeCommand = 'unauthorize' 14 | self.AddSudoCommand = 'addsudo' 15 | self.RmSudoCommand = 'rmsudo' 16 | self.PingCommand = 'ping' 17 | self.RestartCommand = 'restart' 18 | self.StatsCommand = 'stats' 19 | self.HelpCommand = 'help' 20 | self.LogCommand = 'log' 21 | self.SpeedCommand = 'speedtest' 22 | self.CloneCommand = 'clone' 23 | self.CountCommand = 'count' 24 | self.WatchCommand = 'watch' 25 | self.TarWatchCommand = 'tarwatch' 26 | self.DeleteCommand = 'del' 27 | self.UsageCommand = 'usage' 28 | self.MediaInfoCommand = 'mediainfo' 29 | self.ConfigMenuCommand = 'config' 30 | self.ShellCommand = 'shell' 31 | self.UpdateCommand = 'update' 32 | 33 | BotCommands = _BotCommands() 34 | -------------------------------------------------------------------------------- /bot/modules/mirror_status.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CommandHandler 2 | from bot import dispatcher, status_reply_dict, DOWNLOAD_STATUS_UPDATE_INTERVAL, status_reply_dict_lock 3 | from bot.helper.telegram_helper.message_utils import * 4 | from time import sleep 5 | from bot.helper.ext_utils.bot_utils import get_readable_message 6 | from telegram.error import BadRequest 7 | from bot.helper.telegram_helper.filters import CustomFilters 8 | from bot.helper.telegram_helper.bot_commands import BotCommands 9 | import threading 10 | 11 | 12 | def mirror_status(update, context): 13 | message = get_readable_message() 14 | if len(message) == 0: 15 | message = "No active downloads" 16 | reply_message = sendMessage(message, context.bot, update) 17 | threading.Thread(target=auto_delete_message, args=(bot, update.message, reply_message)).start() 18 | return 19 | index = update.effective_chat.id 20 | with status_reply_dict_lock: 21 | if index in status_reply_dict.keys(): 22 | deleteMessage(bot, status_reply_dict[index]) 23 | del status_reply_dict[index] 24 | sendStatusMessage(update, context.bot) 25 | deleteMessage(context.bot, update.message) 26 | 27 | 28 | mirror_status_handler = CommandHandler(BotCommands.StatusCommand, mirror_status, 29 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 30 | dispatcher.add_handler(mirror_status_handler) 31 | -------------------------------------------------------------------------------- /aria.sh: -------------------------------------------------------------------------------- 1 | export MAX_DOWNLOAD_SPEED=0 2 | tracker_list=$(curl -Ns https://raw.githubusercontent.com/XIU2/TrackersListCollection/master/all.txt https://ngosang.github.io/trackerslist/trackers_all_http.txt https://newtrackon.com/api/all https://raw.githubusercontent.com/DeSireFire/animeTrackerList/master/AT_all.txt https://raw.githubusercontent.com/hezhijie0327/Trackerslist/main/trackerslist_tracker.txt https://raw.githubusercontent.com/hezhijie0327/Trackerslist/main/trackerslist_exclude.txt | awk '$0' | tr '\n\n' ',') 3 | export MAX_CONCURRENT_DOWNLOADS=7 4 | 5 | aria2c --enable-rpc --rpc-listen-all=false --rpc-listen-port 6800 --check-certificate=false \ 6 | --max-connection-per-server=10 --rpc-max-request-size=1024M \ 7 | --bt-tracker="[$tracker_list]" --bt-max-peers=0 --bt-tracker-connect-timeout=300 --bt-stop-timeout=1200 --min-split-size=10M \ 8 | --follow-torrent=mem --split=10 \ 9 | --daemon=true --allow-overwrite=true --max-overall-download-limit=$MAX_DOWNLOAD_SPEED \ 10 | --max-overall-upload-limit=1K --max-concurrent-downloads=$MAX_CONCURRENT_DOWNLOADS \ 11 | --peer-id-prefix=-qB4350- --user-agent=qBittorrent/4.3.5 --peer-agent=qBittorrent/4.3.5 \ 12 | --disk-cache=64M --file-allocation=prealloc --continue=true \ 13 | --max-file-not-found=5 --max-tries=20 --auto-file-renaming=true \ 14 | --bt-enable-lpd=true --seed-time=0.01 --seed-ratio=1.0 \ 15 | --content-disposition-default-utf8=true --http-accept-gzip=true --reuse-uri=true --netrc-path=/usr/src/app/.netrc 16 | -------------------------------------------------------------------------------- /bot/modules/count.py: -------------------------------------------------------------------------------- 1 | # Implement By https://github.com/anasty17 2 | # © https://github.com/breakdowns/slam-mirrorbot 3 | 4 | from telegram.ext import CommandHandler 5 | from bot.helper.mirror_utils.upload_utils.gdriveTools import GoogleDriveHelper 6 | from bot.helper.telegram_helper.message_utils import deleteMessage, sendMessage 7 | from bot.helper.telegram_helper.filters import CustomFilters 8 | from bot.helper.telegram_helper.bot_commands import BotCommands 9 | from bot import dispatcher 10 | 11 | 12 | def countNode(update, context): 13 | args = update.message.text.split(" ", maxsplit=1) 14 | if len(args) > 1: 15 | link = args[1] 16 | msg = sendMessage(f"Counting: {link}", context.bot, update) 17 | gd = GoogleDriveHelper() 18 | result = gd.count(link) 19 | deleteMessage(context.bot, msg) 20 | if update.message.from_user.username: 21 | uname = f'@{update.message.from_user.username}' 22 | else: 23 | uname = f'{update.message.from_user.first_name}' 24 | if uname is not None: 25 | cc = f'\n\ncc: {uname}' 26 | sendMessage(result + cc, context.bot, update) 27 | else: 28 | sendMessage("Provide G-Drive Shareable Link to Count.", context.bot, update) 29 | 30 | count_handler = CommandHandler(BotCommands.CountCommand, countNode, filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 31 | dispatcher.add_handler(count_handler) 32 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/upload_status.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | from bot.helper.ext_utils.bot_utils import MirrorStatus, get_readable_file_size, get_readable_time 3 | from bot import DOWNLOAD_DIR 4 | 5 | 6 | class UploadStatus(Status): 7 | def __init__(self, obj, size, listener): 8 | self.obj = obj 9 | self.__size = size 10 | self.uid = listener.uid 11 | self.message = listener.message 12 | 13 | def path(self): 14 | return f"{DOWNLOAD_DIR}{self.uid}" 15 | 16 | def processed_bytes(self): 17 | return self.obj.uploaded_bytes 18 | 19 | def size_raw(self): 20 | return self.__size 21 | 22 | def size(self): 23 | return get_readable_file_size(self.__size) 24 | 25 | def status(self): 26 | return MirrorStatus.STATUS_UPLOADING 27 | 28 | def name(self): 29 | return self.obj.name 30 | 31 | def progress_raw(self): 32 | try: 33 | return self.obj.uploaded_bytes / self.__size * 100 34 | except ZeroDivisionError: 35 | return 0 36 | 37 | def progress(self): 38 | return f'{round(self.progress_raw(), 2)}%' 39 | 40 | def speed_raw(self): 41 | """ 42 | :return: Upload speed in Bytes/Seconds 43 | """ 44 | return self.obj.speed() 45 | 46 | def speed(self): 47 | return f'{get_readable_file_size(self.speed_raw())}/s' 48 | 49 | def eta(self): 50 | try: 51 | seconds = (self.__size - self.obj.uploaded_bytes) / self.speed_raw() 52 | return f'{get_readable_time(seconds)}' 53 | except ZeroDivisionError: 54 | return '-' 55 | -------------------------------------------------------------------------------- /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 MirrorStatus, get_readable_file_size, get_readable_time 3 | from .status import Status 4 | 5 | 6 | class TelegramDownloadStatus(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 | return self.obj.downloaded_bytes 20 | 21 | def size_raw(self): 22 | return self.obj.size 23 | 24 | def size(self): 25 | return get_readable_file_size(self.size_raw()) 26 | 27 | def status(self): 28 | return MirrorStatus.STATUS_DOWNLOADING 29 | 30 | def name(self): 31 | return self.obj.name 32 | 33 | def progress_raw(self): 34 | return self.obj.progress 35 | 36 | def progress(self): 37 | return f'{round(self.progress_raw(), 2)}%' 38 | 39 | def speed_raw(self): 40 | """ 41 | :return: Download speed in Bytes/Seconds 42 | """ 43 | return self.obj.download_speed 44 | 45 | def speed(self): 46 | return f'{get_readable_file_size(self.speed_raw())}/s' 47 | 48 | def eta(self): 49 | try: 50 | seconds = (self.size_raw() - self.processed_bytes()) / self.speed_raw() 51 | return f'{get_readable_time(seconds)}' 52 | except ZeroDivisionError: 53 | return '-' 54 | 55 | def download(self): 56 | return self.obj 57 | -------------------------------------------------------------------------------- /bot/modules/shell.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from bot import LOGGER, dispatcher 3 | from telegram import ParseMode 4 | from telegram.ext import CommandHandler 5 | from bot.helper.telegram_helper.filters import CustomFilters 6 | from bot.helper.telegram_helper.bot_commands import BotCommands 7 | 8 | 9 | def shell(update, context): 10 | message = update.effective_message 11 | cmd = message.text.split(' ', 1) 12 | if len(cmd) == 1: 13 | message.reply_text('No command to execute was given.') 14 | return 15 | cmd = cmd[1] 16 | process = subprocess.Popen( 17 | cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 18 | stdout, stderr = process.communicate() 19 | reply = '' 20 | stderr = stderr.decode() 21 | stdout = stdout.decode() 22 | if stdout: 23 | reply += f"*Stdout*\n`{stdout}`\n" 24 | LOGGER.info(f"Shell - {cmd} - {stdout}") 25 | if stderr: 26 | reply += f"*Stderr*\n`{stderr}`\n" 27 | LOGGER.error(f"Shell - {cmd} - {stderr}") 28 | if len(reply) > 3000: 29 | with open('shell_output.txt', 'w') as file: 30 | file.write(reply) 31 | with open('shell_output.txt', 'rb') as doc: 32 | context.bot.send_document( 33 | document=doc, 34 | filename=doc.name, 35 | reply_to_message_id=message.message_id, 36 | chat_id=message.chat_id) 37 | else: 38 | message.reply_text(reply, parse_mode=ParseMode.MARKDOWN) 39 | 40 | 41 | SHELL_HANDLER = CommandHandler(BotCommands.ShellCommand, shell, 42 | filters=CustomFilters.owner_filter, run_async=True) 43 | dispatcher.add_handler(SHELL_HANDLER) 44 | -------------------------------------------------------------------------------- /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 60 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/gdownload_status.py: -------------------------------------------------------------------------------- 1 | from .status import Status 2 | from bot.helper.ext_utils.bot_utils import MirrorStatus, get_readable_file_size, get_readable_time 3 | from bot import DOWNLOAD_DIR 4 | 5 | 6 | class DownloadStatus(Status): 7 | def __init__(self, obj, size, listener, gid): 8 | self.dobj = obj 9 | self.__dsize = size 10 | self.uid = listener.uid 11 | self.message = listener.message 12 | self.__dgid = gid 13 | 14 | def path(self): 15 | return f"{DOWNLOAD_DIR}{self.uid}" 16 | 17 | def processed_bytes(self): 18 | return self.dobj.downloaded_bytes 19 | 20 | def size_raw(self): 21 | return self.__dsize 22 | 23 | def size(self): 24 | return get_readable_file_size(self.__dsize) 25 | 26 | def status(self): 27 | return MirrorStatus.STATUS_DOWNLOADING 28 | 29 | def name(self): 30 | return self.dobj.name 31 | 32 | def gid(self) -> str: 33 | return self.__dgid 34 | 35 | def progress_raw(self): 36 | try: 37 | return self.dobj.downloaded_bytes / self.__dsize * 100 38 | except ZeroDivisionError: 39 | return 0 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.dobj.dspeed() 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.__dsize - self.dobj.downloaded_bytes) / self.speed_raw() 56 | return f'{get_readable_time(seconds)}' 57 | except ZeroDivisionError: 58 | return '-' 59 | 60 | def download(self): 61 | return self.dobj 62 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/mega_download_status.py: -------------------------------------------------------------------------------- 1 | from bot.helper.ext_utils.bot_utils import get_readable_file_size,MirrorStatus, get_readable_time 2 | from bot import DOWNLOAD_DIR 3 | from .status import Status 4 | 5 | 6 | class MegaDownloadStatus(Status): 7 | 8 | def __init__(self, obj, listener): 9 | self.uid = obj.uid 10 | self.listener = listener 11 | self.obj = obj 12 | self.message = listener.message 13 | 14 | def name(self) -> str: 15 | return self.obj.name 16 | 17 | def progress_raw(self): 18 | try: 19 | return round(self.processed_bytes() / self.obj.size * 100,2) 20 | except ZeroDivisionError: 21 | return 0.0 22 | 23 | def progress(self): 24 | """Progress of download in percentage""" 25 | return f"{self.progress_raw()}%" 26 | 27 | def status(self) -> str: 28 | return MirrorStatus.STATUS_DOWNLOADING 29 | 30 | def processed_bytes(self): 31 | return self.obj.downloaded_bytes 32 | 33 | def eta(self): 34 | try: 35 | seconds = (self.size_raw() - self.processed_bytes()) / self.speed_raw() 36 | return f'{get_readable_time(seconds)}' 37 | except ZeroDivisionError: 38 | return '-' 39 | 40 | def size_raw(self): 41 | return self.obj.size 42 | 43 | def size(self) -> str: 44 | return get_readable_file_size(self.size_raw()) 45 | 46 | def downloaded(self) -> str: 47 | return get_readable_file_size(self.obj.downloadedBytes) 48 | 49 | def speed_raw(self): 50 | return self.obj.speed 51 | 52 | def speed(self) -> str: 53 | return f'{get_readable_file_size(self.speed_raw())}/s' 54 | 55 | def gid(self) -> str: 56 | return self.obj.gid 57 | 58 | def path(self) -> str: 59 | return f"{DOWNLOAD_DIR}{self.uid}" 60 | 61 | def download(self): 62 | return self.obj 63 | -------------------------------------------------------------------------------- /bot/modules/mediainfo.py: -------------------------------------------------------------------------------- 1 | # Suggested by - @d0n0t (https://github.com/code-rgb/USERGE-X/issues/9) 2 | # Copyright (C) 2020 BY - GitHub.com/code-rgb [TG - @deleteduser420] 3 | # All rights reserved. 4 | 5 | import os 6 | from pyrogram import filters 7 | from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup 8 | from bot import app 9 | from bot.helper.telegram_helper.bot_commands import BotCommands 10 | from bot.helper import post_to_telegraph, runcmd, safe_filename 11 | 12 | @app.on_message(filters.command(BotCommands.MediaInfoCommand)) 13 | async def mediainfo(client, message): 14 | reply = message.reply_to_message 15 | if not reply: 16 | await message.reply_text("Reply to Media first") 17 | return 18 | process = await message.reply_text("`Processing...`") 19 | x_media = None 20 | available_media = ( 21 | "audio", 22 | "document", 23 | "photo", 24 | "sticker", 25 | "animation", 26 | "video", 27 | "voice", 28 | "video_note", 29 | "new_chat_photo", 30 | ) 31 | for kind in available_media: 32 | x_media = getattr(reply, kind, None) 33 | if x_media is not None: 34 | break 35 | if x_media is None: 36 | await process.edit_text("Reply To a Valid Media Format") 37 | return 38 | media_type = str(type(x_media)).split("'")[1] 39 | file_path = safe_filename(await reply.download()) 40 | output_ = await runcmd(f'mediainfo "{file_path}"') 41 | out = None 42 | if len(output_) != 0: 43 | out = output_[0] 44 | body_text = f""" 45 |

JSON

46 |
{x_media}
47 |
48 | 49 |

DETAILS

50 |
{out or 'Not Supported'}
51 | """ 52 | text_ = media_type.split(".")[-1].upper() 53 | link = post_to_telegraph(media_type, body_text) 54 | markup = InlineKeyboardMarkup([[InlineKeyboardButton(text=text_, url=link)]]) 55 | await process.edit_text("ℹ️ MEDIA INFO", reply_markup=markup) 56 | -------------------------------------------------------------------------------- /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 == 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/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, ParseMode 6 | from telegram.ext import 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 21 | Name: {result['server']['name']} 22 | Country: {result['server']['country']}, {result['server']['cc']} 23 | Sponsor: {result['server']['sponsor']} 24 | Latency: {result['server']['latency']} 25 | 26 | SpeedTest Results 27 | Upload: {speed_convert(result['upload'] / 8)} 28 | Download: {speed_convert(result['download'] / 8)} 29 | Ping: {result['ping']} ms 30 | ISP: {result['client']['isp']} 31 | ''' 32 | ed_msg.delete() 33 | try: 34 | update.effective_message.reply_photo(path, string_speed, parse_mode=ParseMode.HTML) 35 | except: 36 | update.effective_message.reply_text(string_speed, parse_mode=ParseMode.HTML) 37 | 38 | def speed_convert(size): 39 | """Hi human, you can't read bytes?""" 40 | power = 2 ** 10 41 | zero = 0 42 | units = {0: "", 1: "Kb/s", 2: "MB/s", 3: "Gb/s", 4: "Tb/s"} 43 | while size > power: 44 | size /= power 45 | zero += 1 46 | return f"{round(size, 2)} {units[zero]}" 47 | 48 | 49 | SPEED_HANDLER = CommandHandler(BotCommands.SpeedCommand, speedtest, 50 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 51 | 52 | dispatcher.add_handler(SPEED_HANDLER) 53 | -------------------------------------------------------------------------------- /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 = "/usr/src/app/downloads" 9 | DOWNLOAD_STATUS_UPDATE_INTERVAL = 5 10 | AUTO_DELETE_MESSAGE_DURATION = 20 11 | IS_TEAM_DRIVE = "" 12 | TELEGRAM_API = 13 | TELEGRAM_HASH = "" 14 | DATABASE_URL = "" 15 | UPSTREAM_REPO = "https://github.com/breakdowns/slam-mirrorbot" 16 | UPSTREAM_BRANCH = "master" 17 | # OPTIONAL CONFIG 18 | AUTHORIZED_CHATS = "" 19 | IGNORE_PENDING_REQUESTS = "" 20 | USE_SERVICE_ACCOUNTS = "" 21 | INDEX_URL = "" 22 | UPTOBOX_TOKEN = "" 23 | MEGA_API_KEY = "" 24 | MEGA_EMAIL_ID = "" 25 | MEGA_PASSWORD = "" 26 | BLOCK_MEGA_FOLDER = "" 27 | BLOCK_MEGA_LINKS = "" 28 | STOP_DUPLICATE_MIRROR = "" 29 | STOP_DUPLICATE_CLONE = "" 30 | STOP_DUPLICATE_MEGA = "" 31 | SHORTENER = "" 32 | SHORTENER_API = "" 33 | IMAGE_URL = "https://telegra.ph/file/db03910496f06094f1f7a.jpg" 34 | # If you want to use Credentials externally from Index Links, fill these vars with the direct links 35 | # These are optional, if you don't know, simply leave them, don't fill anything in them. 36 | ACCOUNTS_ZIP_URL = "" 37 | TOKEN_PICKLE_URL = "" 38 | # To use limit leave space between number and unit. Available units is (gb or GB, tb or TB) 39 | TORRENT_DIRECT_LIMIT = "" 40 | CLONE_LIMIT = "" 41 | MEGA_LIMIT = "" 42 | # Fill only if you deploying with heroku-cli and Goorm IDE 43 | HEROKU_API_KEY = "" 44 | HEROKU_APP_NAME = "" 45 | # View Link button to open file Index Link in browser instead of direct download link, you can figure out if it's compatible with your Index code or not, open any video from you Index and check if the END of link from browser link bar is ?a=view, if yes make it "True" it will work (Compatible with Bhadoo Index Code) 46 | VIEW_LINK = "False" 47 | # Add more buttons (Three buttons are already added of Drive Link, Index Link, and View Link, you can add extra buttons too, these are optional) 48 | # If you don't know what are below entries, simply leave them, Don't fill anything in them. 49 | BUTTON_FOUR_NAME = "" 50 | BUTTON_FOUR_URL = "" 51 | BUTTON_FIVE_NAME = "" 52 | BUTTON_FIVE_URL = "" 53 | BUTTON_SIX_NAME = "" 54 | BUTTON_SIX_URL = "" 55 | -------------------------------------------------------------------------------- /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 | else: 26 | sql = 'INSERT INTO users VALUES ({});'.format(chat_id) 27 | self.cur.execute(sql) 28 | self.conn.commit() 29 | self.disconnect() 30 | AUTHORIZED_CHATS.add(chat_id) 31 | return 'Authorized successfully' 32 | 33 | def db_unauth(self,chat_id: int): 34 | self.connect() 35 | if self.err : 36 | return "There's some error check log for details" 37 | else: 38 | sql = 'DELETE from users where uid = {};'.format(chat_id) 39 | self.cur.execute(sql) 40 | self.conn.commit() 41 | self.disconnect() 42 | AUTHORIZED_CHATS.remove(chat_id) 43 | if chat_id in SUDO_USERS: 44 | SUDO_USERS.remove(chat_id) 45 | return 'Unauthorized successfully' 46 | 47 | def db_addsudo(self,chat_id: int): 48 | self.connect() 49 | if self.err : 50 | return "There's some error check log for details" 51 | else: 52 | if chat_id in AUTHORIZED_CHATS: 53 | sql = 'UPDATE users SET sudo = TRUE where uid = {};'.format(chat_id) 54 | self.cur.execute(sql) 55 | self.conn.commit() 56 | self.disconnect() 57 | SUDO_USERS.add(chat_id) 58 | return 'Successfully promoted as Sudo' 59 | else: 60 | sql = 'INSERT INTO users VALUES ({},TRUE);'.format(chat_id) 61 | self.cur.execute(sql) 62 | self.conn.commit() 63 | self.disconnect() 64 | AUTHORIZED_CHATS.add(chat_id) 65 | SUDO_USERS.add(chat_id) 66 | return 'Successfully Authorized and promoted as Sudo' 67 | 68 | def db_rmsudo(self,chat_id: int): 69 | self.connect() 70 | if self.err : 71 | return "There's some error check log for details" 72 | else: 73 | sql = 'UPDATE users SET sudo = FALSE where uid = {};'.format(chat_id) 74 | self.cur.execute(sql) 75 | self.conn.commit() 76 | self.disconnect() 77 | SUDO_USERS.remove(chat_id) 78 | return 'Successfully removed from Sudo' 79 | -------------------------------------------------------------------------------- /bot/modules/watch.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CommandHandler 2 | from telegram import Bot, Update 3 | from bot import Interval, DOWNLOAD_DIR, DOWNLOAD_STATUS_UPDATE_INTERVAL, dispatcher, LOGGER 4 | from bot.helper.ext_utils.bot_utils import setInterval 5 | from bot.helper.telegram_helper.message_utils import update_all_messages, sendMessage, sendStatusMessage 6 | from .mirror import MirrorListener 7 | from bot.helper.mirror_utils.download_utils.youtube_dl_download_helper import YoutubeDLHelper 8 | from bot.helper.telegram_helper.bot_commands import BotCommands 9 | from bot.helper.telegram_helper.filters import CustomFilters 10 | import threading 11 | 12 | 13 | def _watch(bot: Bot, update, isTar=False): 14 | mssg = update.message.text 15 | message_args = mssg.split(' ') 16 | name_args = mssg.split('|') 17 | try: 18 | link = message_args[1] 19 | except IndexError: 20 | msg = f"/{BotCommands.WatchCommand} [youtube-dl supported link] [quality] |[CustomName] to mirror with youtube-dl.\n\n" 21 | msg += "Note: Quality and custom name are optional\n\nExample of quality: audio, 144, 240, 360, 480, 720, 1080, 2160." 22 | msg += "\n\nIf you want to use custom filename, enter it after |" 23 | msg += f"\n\nExample:\n/{BotCommands.WatchCommand} https://youtu.be/Pk_TthHfLeE 720 |Slam\n\n" 24 | msg += "This file will be downloaded in 720p quality and it's name will be Slam" 25 | sendMessage(msg, bot, update) 26 | return 27 | try: 28 | if "|" in mssg: 29 | mssg = mssg.split("|") 30 | qual = mssg[0].split(" ")[2] 31 | if qual == "": 32 | raise IndexError 33 | else: 34 | qual = message_args[2] 35 | if qual != "audio": 36 | qual = f'bestvideo[height<={qual}]+bestaudio/best[height<={qual}]' 37 | except IndexError: 38 | qual = "bestvideo+bestaudio/best" 39 | try: 40 | name = name_args[1] 41 | except IndexError: 42 | name = "" 43 | reply_to = update.message.reply_to_message 44 | if reply_to is not None: 45 | tag = reply_to.from_user.username 46 | else: 47 | tag = None 48 | pswd = "" 49 | listener = MirrorListener(bot, update, pswd, isTar, tag) 50 | ydl = YoutubeDLHelper(listener) 51 | threading.Thread(target=ydl.add_download,args=(link, f'{DOWNLOAD_DIR}{listener.uid}', qual, name)).start() 52 | sendStatusMessage(update, bot) 53 | if len(Interval) == 0: 54 | Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages)) 55 | 56 | 57 | def watchTar(update, context): 58 | _watch(context.bot, update, True) 59 | 60 | 61 | def watch(update, context): 62 | _watch(context.bot, update) 63 | 64 | 65 | mirror_handler = CommandHandler(BotCommands.WatchCommand, watch, 66 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 67 | tar_mirror_handler = CommandHandler(BotCommands.TarWatchCommand, watchTar, 68 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 69 | dispatcher.add_handler(mirror_handler) 70 | dispatcher.add_handler(tar_mirror_handler) 71 | -------------------------------------------------------------------------------- /add_to_team_drive.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from google.oauth2.service_account import Credentials 3 | import googleapiclient.discovery, json, progress.bar, glob, sys, argparse, time 4 | from google_auth_oauthlib.flow import InstalledAppFlow 5 | from google.auth.transport.requests import Request 6 | import os, pickle 7 | 8 | stt = time.time() 9 | 10 | parse = argparse.ArgumentParser( 11 | description='A tool to add service accounts to a shared drive from a folder containing credential files.') 12 | parse.add_argument('--path', '-p', default='accounts', 13 | help='Specify an alternative path to the service accounts folder.') 14 | parse.add_argument('--credentials', '-c', default='./credentials.json', 15 | help='Specify the relative path for the credentials file.') 16 | parse.add_argument('--yes', '-y', default=False, action='store_true', help='Skips the sanity prompt.') 17 | parsereq = parse.add_argument_group('required arguments') 18 | parsereq.add_argument('--drive-id', '-d', help='The ID of the Shared Drive.', required=True) 19 | 20 | args = parse.parse_args() 21 | acc_dir = args.path 22 | did = args.drive_id 23 | credentials = glob.glob(args.credentials) 24 | 25 | try: 26 | open(credentials[0], 'r') 27 | print('>> Found credentials.') 28 | except IndexError: 29 | print('>> No credentials found.') 30 | sys.exit(0) 31 | 32 | if not args.yes: 33 | # input('Make sure the following client id is added to the shared drive as Manager:\n' + json.loads((open( 34 | # credentials[0],'r').read()))['installed']['client_id']) 35 | input('>> Make sure the **Google account** that has generated credentials.json\n is added into your Team Drive ' 36 | '(shared drive) as Manager\n>> (Press any key to continue)') 37 | 38 | creds = None 39 | if os.path.exists('token_sa.pickle'): 40 | with open('token_sa.pickle', 'rb') as token: 41 | creds = pickle.load(token) 42 | # If there are no (valid) credentials available, let the user log in. 43 | if not creds or not creds.valid: 44 | if creds and creds.expired and creds.refresh_token: 45 | creds.refresh(Request()) 46 | else: 47 | flow = InstalledAppFlow.from_client_secrets_file(credentials[0], scopes=[ 48 | 'https://www.googleapis.com/auth/admin.directory.group', 49 | 'https://www.googleapis.com/auth/admin.directory.group.member' 50 | ]) 51 | # creds = flow.run_local_server(port=0) 52 | creds = flow.run_console() 53 | # Save the credentials for the next run 54 | with open('token_sa.pickle', 'wb') as token: 55 | pickle.dump(creds, token) 56 | 57 | drive = googleapiclient.discovery.build("drive", "v3", credentials=creds) 58 | batch = drive.new_batch_http_request() 59 | 60 | aa = glob.glob('%s/*.json' % acc_dir) 61 | pbar = progress.bar.Bar("Readying accounts", max=len(aa)) 62 | for i in aa: 63 | ce = json.loads(open(i, 'r').read())['client_email'] 64 | batch.add(drive.permissions().create(fileId=did, supportsAllDrives=True, body={ 65 | "role": "fileOrganizer", 66 | "type": "user", 67 | "emailAddress": ce 68 | })) 69 | pbar.next() 70 | pbar.finish() 71 | print('Adding...') 72 | batch.execute() 73 | 74 | print('Complete.') 75 | hours, rem = divmod((time.time() - stt), 3600) 76 | minutes, sec = divmod(rem, 60) 77 | print("Elapsed Time:\n{:0>2}:{:0>2}:{:05.2f}".format(int(hours), int(minutes), sec)) -------------------------------------------------------------------------------- /bot/modules/updates.py: -------------------------------------------------------------------------------- 1 | # Implement By https://github.com/jusidama18 2 | # Based on this https://github.com/DevsExpo/FridayUserbot/blob/master/plugins/updater.py 3 | 4 | import sys 5 | import subprocess 6 | import heroku3 7 | 8 | from datetime import datetime 9 | from os import environ, execle, path, remove 10 | 11 | from git import Repo 12 | from git.exc import GitCommandError, InvalidGitRepositoryError, NoSuchPathError 13 | 14 | from pyrogram import filters 15 | 16 | from bot import app, OWNER_ID, UPSTREAM_REPO, UPSTREAM_BRANCH 17 | from bot.helper import runcmd, get_text, HEROKU_URL 18 | from bot.helper.telegram_helper.bot_commands import BotCommands 19 | 20 | REPO_ = UPSTREAM_REPO 21 | BRANCH_ = UPSTREAM_BRANCH 22 | 23 | # Update Command 24 | 25 | @app.on_message(filters.command(BotCommands.UpdateCommand) & filters.user(OWNER_ID)) 26 | async def update_it(client, message): 27 | msg_ = await message.reply_text("`Updating Please Wait!`") 28 | try: 29 | repo = Repo() 30 | except GitCommandError: 31 | return await msg_.edit( 32 | "**Invalid Git Command. Please Report This Bug To [Support Group](https://t.me/SlamMirrorSupport)**" 33 | ) 34 | except InvalidGitRepositoryError: 35 | repo = Repo.init() 36 | if "upstream" in repo.remotes: 37 | origin = repo.remote("upstream") 38 | else: 39 | origin = repo.create_remote("upstream", REPO_) 40 | origin.fetch() 41 | repo.create_head(UPSTREAM_BRANCH, origin.refs.master) 42 | repo.heads.master.set_tracking_branch(origin.refs.master) 43 | repo.heads.master.checkout(True) 44 | if repo.active_branch.name != UPSTREAM_BRANCH: 45 | return await msg_.edit( 46 | f"`Seems Like You Are Using Custom Branch - {repo.active_branch.name}! Please Switch To {UPSTREAM_BRANCH} To Make This Updater Function!`" 47 | ) 48 | try: 49 | repo.create_remote("upstream", REPO_) 50 | except BaseException: 51 | pass 52 | ups_rem = repo.remote("upstream") 53 | ups_rem.fetch(UPSTREAM_BRANCH) 54 | if not HEROKU_URL: 55 | try: 56 | ups_rem.pull(UPSTREAM_BRANCH) 57 | except GitCommandError: 58 | repo.git.reset("--hard", "FETCH_HEAD") 59 | await runcmd("pip3 install --no-cache-dir -r requirements.txt") 60 | await msg_.edit("`Updated Sucessfully! Give Me Some Time To Restart!`") 61 | with open("./aria.sh", 'rb') as file: 62 | script = file.read() 63 | subprocess.call("./aria.sh", shell=True) 64 | args = [sys.executable, "-m", "bot"] 65 | execle(sys.executable, *args, environ) 66 | exit() 67 | return 68 | else: 69 | await msg_.edit("`Heroku Detected! Pushing, Please wait!`") 70 | ups_rem.fetch(UPSTREAM_BRANCH) 71 | repo.git.reset("--hard", "FETCH_HEAD") 72 | if "heroku" in repo.remotes: 73 | remote = repo.remote("heroku") 74 | remote.set_url(HEROKU_URL) 75 | else: 76 | remote = repo.create_remote("heroku", HEROKU_URL) 77 | try: 78 | remote.push(refspec="HEAD:refs/heads/master", force=True) 79 | except BaseException as error: 80 | await msg_.edit(f"**Updater Error** \nTraceBack : `{error}`") 81 | return repo.__del__() 82 | await msg_.edit(f"`Updated Sucessfully! \n\nCheck your config with` `/{BotCommands.ConfigMenuCommand}`") 83 | -------------------------------------------------------------------------------- /bot/modules/usage.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import requests 4 | import heroku3 5 | 6 | from bot import dispatcher, HEROKU_APP_NAME, HEROKU_API_KEY 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 sendMessage 10 | from telegram import update 11 | from telegram.ext import CommandHandler 12 | 13 | 14 | def dyno_usage(update, context): 15 | heroku_api = "https://api.heroku.com" 16 | if HEROKU_API_KEY is not None and HEROKU_APP_NAME is not None: 17 | Heroku = heroku3.from_key(HEROKU_API_KEY) 18 | app = Heroku.app(HEROKU_APP_NAME) 19 | else: 20 | sendMessage( 21 | "Please insert your HEROKU_APP_NAME and HEROKU_API_KEY in Vars", 22 | context.bot, 23 | update 24 | ) 25 | useragent = ( 26 | "Mozilla/5.0 (Linux; Android 10; SM-G975F) " 27 | "AppleWebKit/537.36 (KHTML, like Gecko) " 28 | "Chrome/81.0.4044.117 Mobile Safari/537.36" 29 | ) 30 | user_id = Heroku.account().id 31 | headers = { 32 | "User-Agent": useragent, 33 | "Authorization": f"Bearer {HEROKU_API_KEY}", 34 | "Accept": "application/vnd.heroku+json; version=3.account-quotas", 35 | } 36 | path = "/accounts/" + user_id + "/actions/get-quota" 37 | session = requests.Session() 38 | with session as ses: 39 | with ses.get(heroku_api + path, headers=headers) as r: 40 | result = r.json() 41 | """Account Quota.""" 42 | quota = result["account_quota"] 43 | quota_used = result["quota_used"] 44 | quota_remain = quota - quota_used 45 | quota_percent = math.floor(quota_remain / quota * 100) 46 | minutes_remain = quota_remain / 60 47 | hours = math.floor(minutes_remain / 60) 48 | minutes = math.floor(minutes_remain % 60) 49 | day = math.floor(hours / 24) 50 | 51 | """App Quota.""" 52 | Apps = result["apps"] 53 | for apps in Apps: 54 | if apps.get("app_uuid") == app.id: 55 | AppQuotaUsed = apps.get("quota_used") / 60 56 | AppPercent = math.floor(apps.get("quota_used") * 100 / quota) 57 | break 58 | else: 59 | AppQuotaUsed = 0 60 | AppPercent = 0 61 | 62 | AppHours = math.floor(AppQuotaUsed / 60) 63 | AppMinutes = math.floor(AppQuotaUsed % 60) 64 | 65 | sendMessage( 66 | f"Dyno Usage for {app.name}:\n" 67 | f"• {AppHours} Hours and {AppMinutes} Minutes - {AppPercent}%\n\n" 68 | "Dyno Remaining this month:\n" 69 | f"• {hours} Hours and {minutes} Minutes - {quota_percent}%\n\n" 70 | "Estimated Dyno Expired:\n" 71 | f"• {day} Days", 72 | context.bot, 73 | update 74 | ) 75 | return True 76 | 77 | 78 | dyno_usage_handler = CommandHandler(command=BotCommands.UsageCommand, callback=dyno_usage, 79 | filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True) 80 | 81 | dispatcher.add_handler(dyno_usage_handler) 82 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/status_utils/aria_download_status.py: -------------------------------------------------------------------------------- 1 | from bot import aria2, DOWNLOAD_DIR, LOGGER 2 | from bot.helper.ext_utils.bot_utils import MirrorStatus 3 | from .status import Status 4 | 5 | 6 | def get_download(gid): 7 | return aria2.get_download(gid) 8 | 9 | 10 | class AriaDownloadStatus(Status): 11 | 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 | 28 | def progress(self): 29 | """ 30 | Calculates the progress of the mirror (upload or download) 31 | :return: returns progress in percentage 32 | """ 33 | self.__update() 34 | return self.__download.progress_string() 35 | 36 | def size_raw(self): 37 | """ 38 | Gets total size of the mirror file/folder 39 | :return: total size of mirror 40 | """ 41 | return self.aria_download().total_length 42 | 43 | def processed_bytes(self): 44 | return self.aria_download().completed_length 45 | 46 | def speed(self): 47 | return self.aria_download().download_speed_string() 48 | 49 | def name(self): 50 | return self.aria_download().name 51 | 52 | def path(self): 53 | return f"{DOWNLOAD_DIR}{self.__uid}" 54 | 55 | def size(self): 56 | return self.aria_download().total_length_string() 57 | 58 | def eta(self): 59 | return self.aria_download().eta_string() 60 | 61 | def status(self): 62 | download = self.aria_download() 63 | if download.is_waiting: 64 | status = MirrorStatus.STATUS_WAITING 65 | elif download.has_failed: 66 | status = MirrorStatus.STATUS_FAILED 67 | else: 68 | status = MirrorStatus.STATUS_DOWNLOADING 69 | return status 70 | 71 | def aria_download(self): 72 | self.__update() 73 | return self.__download 74 | 75 | def download(self): 76 | return self 77 | 78 | def updateName(self,name): 79 | self.__name = name 80 | 81 | def updateGid(self,gid): 82 | self.__gid = gid 83 | 84 | def getListener(self): 85 | return self.__listener 86 | 87 | def uid(self): 88 | return self.__uid 89 | 90 | def gid(self): 91 | self.__update() 92 | return self.__gid 93 | 94 | def cancel_download(self): 95 | LOGGER.info(f"Cancelling Download: {self.name()}") 96 | download = self.aria_download() 97 | if download.is_waiting: 98 | aria2.remove([download]) 99 | self.__listener.onDownloadError("Cancelled by user") 100 | return 101 | if len(download.followed_by_ids) != 0: 102 | downloads = aria2.get_downloads(download.followed_by_ids) 103 | aria2.remove(downloads) 104 | self.__listener.onDownloadError("Download stopped by user!") 105 | aria2.remove([download]) 106 | self.__listener.onDownloadError("Download stopped by user!") 107 | -------------------------------------------------------------------------------- /bot/modules/clone.py: -------------------------------------------------------------------------------- 1 | from telegram.ext import CommandHandler 2 | from bot.helper.mirror_utils.upload_utils.gdriveTools import GoogleDriveHelper 3 | from bot.helper.telegram_helper.message_utils import sendMarkup, deleteMessage, sendMessage 4 | from bot.helper.telegram_helper.filters import CustomFilters 5 | from bot.helper.telegram_helper.bot_commands import BotCommands 6 | from bot import dispatcher, LOGGER, CLONE_LIMIT, STOP_DUPLICATE_CLONE 7 | from bot.helper.ext_utils.bot_utils import get_readable_file_size 8 | 9 | 10 | def cloneNode(update, context): 11 | args = update.message.text.split(" ", maxsplit=1) 12 | if len(args) > 1: 13 | link = args[1] 14 | gd = GoogleDriveHelper() 15 | if CLONE_LIMIT is not None or STOP_DUPLICATE_CLONE: 16 | msg1 = sendMessage(f"Checking Your Link...", context.bot, update) 17 | res, clonesize, name = gd.clonehelper(link) 18 | if res != "": 19 | deleteMessage(context.bot, msg1) 20 | sendMessage(res, context.bot, update) 21 | return 22 | if STOP_DUPLICATE_CLONE: 23 | LOGGER.info(f"Checking File/Folder if already in Drive...") 24 | smsg, button = gd.drive_list(name) 25 | if smsg: 26 | deleteMessage(context.bot, msg1) 27 | msg3 = "File/Folder is already available in Drive.\nHere are the search results:" 28 | sendMarkup(msg3, context.bot, update, button) 29 | return 30 | else: 31 | if CLONE_LIMIT is None: 32 | deleteMessage(context.bot, msg1) 33 | if CLONE_LIMIT is not None: 34 | LOGGER.info(f"Checking File/Folder Size...") 35 | limit = CLONE_LIMIT 36 | limit = limit.split(' ', maxsplit=1) 37 | limitint = int(limit[0]) 38 | msg2 = f'Failed, Clone limit is {CLONE_LIMIT}.\nYour File/Folder size is {get_readable_file_size(clonesize)}.' 39 | if 'GB' in limit or 'gb' in limit: 40 | if clonesize > limitint * 1024**3: 41 | deleteMessage(context.bot, msg1) 42 | sendMessage(msg2, context.bot, update) 43 | return 44 | else: 45 | deleteMessage(context.bot, msg1) 46 | elif 'TB' in limit or 'tb' in limit: 47 | if clonesize > limitint * 1024**4: 48 | deleteMessage(context.bot, msg1) 49 | sendMessage(msg2, context.bot, update) 50 | return 51 | else: 52 | deleteMessage(context.bot, msg1) 53 | msg = sendMessage(f"Cloning: {link}", context.bot, update) 54 | result, button = gd.clone(link) 55 | deleteMessage(context.bot, msg) 56 | if button == "": 57 | sendMessage(result, context.bot, update) 58 | else: 59 | if update.message.from_user.username: 60 | uname = f'@{update.message.from_user.username}' 61 | else: 62 | uname = f'{update.message.from_user.first_name}' 63 | if uname is not None: 64 | cc = f'\n\ncc: {uname}' 65 | sendMarkup(result + cc, context.bot, update, button) 66 | else: 67 | sendMessage('Provide G-Drive Shareable Link to Clone.', context.bot, update) 68 | 69 | clone_handler = CommandHandler(BotCommands.CloneCommand, cloneNode, filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 70 | dispatcher.add_handler(clone_handler) 71 | -------------------------------------------------------------------------------- /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.TarMirrorCommand in mirror_message.text or \ 35 | BotCommands.UnzipMirrorCommand in mirror_message.text: 36 | msg1 = "Mirror Already Have Been Cancelled" 37 | sendMessage(msg1, context.bot, update) 38 | return 39 | else: 40 | sendMessage(msg, context.bot, update) 41 | return 42 | elif not mirror_message: 43 | sendMessage(msg, context.bot, update) 44 | return 45 | if dl.status() == "Uploading...📤": 46 | sendMessage("Upload in Progress, You Can't Cancel It.", context.bot, update) 47 | return 48 | elif dl.status() == "Archiving...🔐": 49 | sendMessage("Archival in Progress, You Can't Cancel It.", context.bot, update) 50 | return 51 | elif dl.status() == "Extracting...📂": 52 | sendMessage("Extract in Progress, You Can't Cancel It.", context.bot, update) 53 | return 54 | else: 55 | dl.download().cancel_download() 56 | sleep(3) # incase of any error with ondownloaderror listener, clean_download will delete the folder but the download will stuck in status msg. 57 | clean_download(f'{DOWNLOAD_DIR}{mirror_message.message_id}/') 58 | 59 | 60 | def cancel_all(update, context): 61 | count = 0 62 | gid = 1 63 | while True: 64 | dl = getAllDownload() 65 | if dl: 66 | if dl.gid() == gid: 67 | continue 68 | else: 69 | gid = dl.gid() 70 | dl.download().cancel_download() 71 | sleep(0.5) 72 | count += 1 73 | else: 74 | break 75 | delete_all_messages() 76 | sendMessage(f'{count} Download(s) has been Cancelled!', context.bot, update) 77 | 78 | 79 | 80 | cancel_mirror_handler = CommandHandler(BotCommands.CancelMirror, cancel_mirror, 81 | filters=(CustomFilters.authorized_chat | CustomFilters.authorized_user) & CustomFilters.mirror_owner_filter | CustomFilters.sudo_user, run_async=True) 82 | cancel_all_handler = CommandHandler(BotCommands.CancelAllCommand, cancel_all, 83 | filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True) 84 | dispatcher.add_handler(cancel_all_handler) 85 | dispatcher.add_handler(cancel_mirror_handler) 86 | -------------------------------------------------------------------------------- /bot/modules/eval.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | # Common imports for eval 4 | import textwrap 5 | import traceback 6 | from contextlib import redirect_stdout 7 | from bot.helper.telegram_helper.filters import CustomFilters 8 | from bot import LOGGER, dispatcher 9 | from telegram import ParseMode 10 | from telegram.ext import CommandHandler 11 | 12 | namespaces = {} 13 | 14 | 15 | def namespace_of(chat, update, bot): 16 | if chat not in namespaces: 17 | namespaces[chat] = { 18 | '__builtins__': globals()['__builtins__'], 19 | 'bot': bot, 20 | 'effective_message': update.effective_message, 21 | 'effective_user': update.effective_user, 22 | 'effective_chat': update.effective_chat, 23 | 'update': update 24 | } 25 | 26 | return namespaces[chat] 27 | 28 | 29 | def log_input(update): 30 | user = update.effective_user.id 31 | chat = update.effective_chat.id 32 | LOGGER.info( 33 | f"IN: {update.effective_message.text} (user={user}, chat={chat})") 34 | 35 | 36 | def send(msg, bot, update): 37 | if len(str(msg)) > 2000: 38 | with io.BytesIO(str.encode(msg)) as out_file: 39 | out_file.name = "output.txt" 40 | bot.send_document( 41 | chat_id=update.effective_chat.id, document=out_file) 42 | else: 43 | LOGGER.info(f"OUT: '{msg}'") 44 | bot.send_message( 45 | chat_id=update.effective_chat.id, 46 | text=f"`{msg}`", 47 | parse_mode=ParseMode.MARKDOWN) 48 | 49 | 50 | def evaluate(update, context): 51 | bot = context.bot 52 | send(do(eval, bot, update), bot, update) 53 | 54 | 55 | def execute(update, context): 56 | bot = context.bot 57 | send(do(exec, bot, update), bot, update) 58 | 59 | 60 | def cleanup_code(code): 61 | if code.startswith('```') and code.endswith('```'): 62 | return '\n'.join(code.split('\n')[1:-1]) 63 | return code.strip('` \n') 64 | 65 | 66 | def do(func, bot, update): 67 | log_input(update) 68 | content = update.message.text.split(' ', 1)[-1] 69 | body = cleanup_code(content) 70 | env = namespace_of(update.message.chat_id, update, bot) 71 | 72 | os.chdir(os.getcwd()) 73 | with open( 74 | os.path.join(os.getcwd(), 75 | 'bot/modules/temp.txt'), 76 | 'w') as temp: 77 | temp.write(body) 78 | 79 | stdout = io.StringIO() 80 | 81 | to_compile = f'def func():\n{textwrap.indent(body, " ")}' 82 | 83 | try: 84 | exec(to_compile, env) 85 | except Exception as e: 86 | return f'{e.__class__.__name__}: {e}' 87 | 88 | func = env['func'] 89 | 90 | try: 91 | with redirect_stdout(stdout): 92 | func_return = func() 93 | except Exception as e: 94 | value = stdout.getvalue() 95 | return f'{value}{traceback.format_exc()}' 96 | else: 97 | value = stdout.getvalue() 98 | result = None 99 | if func_return is None: 100 | if value: 101 | result = f'{value}' 102 | else: 103 | try: 104 | result = f'{repr(eval(body, env))}' 105 | except: 106 | pass 107 | else: 108 | result = f'{value}{func_return}' 109 | if result: 110 | return result 111 | 112 | 113 | def clear(update, context): 114 | bot = context.bot 115 | log_input(update) 116 | global namespaces 117 | if update.message.chat_id in namespaces: 118 | del namespaces[update.message.chat_id] 119 | send("Cleared locals.", bot, update) 120 | 121 | 122 | EVAL_HANDLER = CommandHandler(('e', 'ev', 'eva', 'eval'), evaluate, filters=CustomFilters.owner_filter, run_async=True) 123 | EXEC_HANDLER = CommandHandler(('x', 'ex', 'exe', 'exec', 'py'), execute, filters=CustomFilters.owner_filter, run_async=True) 124 | CLEAR_HANDLER = CommandHandler('clearlocals', clear, filters=CustomFilters.owner_filter, run_async=True) 125 | 126 | dispatcher.add_handler(EVAL_HANDLER) 127 | dispatcher.add_handler(EXEC_HANDLER) 128 | dispatcher.add_handler(CLEAR_HANDLER) 129 | 130 | -------------------------------------------------------------------------------- /bot/helper/__init__.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import shlex 4 | import heroku3 5 | 6 | from functools import wraps 7 | from pyrogram.types import Message 8 | from typing import Tuple 9 | from html_telegraph_poster import TelegraphPoster 10 | from bot import HEROKU_API_KEY, HEROKU_APP_NAME 11 | 12 | # Implement by https://github.com/jusidama18 13 | # Setting Message 14 | 15 | def get_text(message: Message) -> [None, str]: 16 | """Extract Text From Commands""" 17 | text_to_return = message.text 18 | if message.text is None: 19 | return None 20 | if " " in text_to_return: 21 | try: 22 | return message.text.split(None, 1)[1] 23 | except IndexError: 24 | return None 25 | else: 26 | return None 27 | 28 | # Preparing For Setting Config 29 | # Implement by https://github.com/jusidama18 and Based on this https://github.com/DevsExpo/FridayUserbot/blob/master/plugins/heroku_helpers.py 30 | 31 | heroku_client = None 32 | if HEROKU_API_KEY: 33 | heroku_client = heroku3.from_key(HEROKU_API_KEY) 34 | 35 | def check_heroku(func): 36 | @wraps(func) 37 | async def heroku_cli(client, message): 38 | heroku_app = None 39 | if not heroku_client: 40 | await message.reply_text("`Please Add HEROKU_API_KEY Key For This To Function To Work!`", parse_mode="markdown") 41 | elif not HEROKU_APP_NAME: 42 | await message.reply_text("`Please Add HEROKU_APP_NAME For This To Function To Work!`", parse_mode="markdown") 43 | if HEROKU_APP_NAME and heroku_client: 44 | try: 45 | heroku_app = heroku_client.app(HEROKU_APP_NAME) 46 | except: 47 | await message.reply_text(message, "`Heroku Api Key And App Name Doesn't Match!`", parse_mode="markdown") 48 | if heroku_app: 49 | await func(client, message, heroku_app) 50 | 51 | return heroku_cli 52 | 53 | # Preparing For Update Bot 54 | # Implement by https://github.com/jusidama18 and Based on this https://github.com/DevsExpo/FridayUserbot/blob/master/plugins/updater.py 55 | 56 | def fetch_heroku_git_url(api_key, app_name): 57 | if not api_key: 58 | return None 59 | if not app_name: 60 | return None 61 | heroku = heroku3.from_key(api_key) 62 | try: 63 | heroku_applications = heroku.apps() 64 | except: 65 | return None 66 | heroku_app = None 67 | for app in heroku_applications: 68 | if app.name == app_name: 69 | heroku_app = app 70 | break 71 | if not heroku_app: 72 | return None 73 | return heroku_app.git_url.replace("https://", "https://api:" + api_key + "@") 74 | 75 | HEROKU_URL = fetch_heroku_git_url(HEROKU_API_KEY, HEROKU_APP_NAME) 76 | 77 | def post_to_telegraph(a_title: str, content: str) -> str: 78 | """ Create a Telegram Post using HTML Content """ 79 | post_client = TelegraphPoster(use_api=True) 80 | auth_name = "slam-mirrorbot" 81 | post_client.create_api_token(auth_name) 82 | post_page = post_client.post( 83 | title=a_title, 84 | author=auth_name, 85 | author_url="https://github.com/breakdowns/slam-mirrorbot", 86 | text=content, 87 | ) 88 | return post_page["url"] 89 | 90 | 91 | async def runcmd(cmd: str) -> Tuple[str, str, int, int]: 92 | """ run command in terminal """ 93 | args = shlex.split(cmd) 94 | process = await asyncio.create_subprocess_exec( 95 | *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE 96 | ) 97 | stdout, stderr = await process.communicate() 98 | return ( 99 | stdout.decode("utf-8", "replace").strip(), 100 | stderr.decode("utf-8", "replace").strip(), 101 | process.returncode, 102 | process.pid, 103 | ) 104 | 105 | 106 | # Solves ValueError: No closing quotation by removing ' or " in file name 107 | def safe_filename(path_): 108 | if path_ is None: 109 | return 110 | safename = path_.replace("'", "").replace('"', "") 111 | if safename != path_: 112 | os.rename(path_, safename) 113 | return safename 114 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/telegram_downloader.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import threading 3 | import time 4 | 5 | from bot import LOGGER, download_dict, download_dict_lock, app 6 | from .download_helper import DownloadHelper 7 | from ..status_utils.telegram_download_status import TelegramDownloadStatus 8 | 9 | global_lock = threading.Lock() 10 | GLOBAL_GID = set() 11 | 12 | logging.getLogger("pyrogram").setLevel(logging.WARNING) 13 | 14 | 15 | class TelegramDownloadHelper(DownloadHelper): 16 | def __init__(self, listener): 17 | super().__init__() 18 | self.__listener = listener 19 | self.__resource_lock = threading.RLock() 20 | self.__name = "" 21 | self.__gid = '' 22 | self.__start_time = time.time() 23 | self._bot = app 24 | self.__is_cancelled = False 25 | 26 | @property 27 | def gid(self): 28 | with self.__resource_lock: 29 | return self.__gid 30 | 31 | @property 32 | def download_speed(self): 33 | with self.__resource_lock: 34 | return self.downloaded_bytes / (time.time() - self.__start_time) 35 | 36 | def __onDownloadStart(self, name, size, file_id): 37 | with download_dict_lock: 38 | download_dict[self.__listener.uid] = TelegramDownloadStatus(self, self.__listener) 39 | with global_lock: 40 | GLOBAL_GID.add(file_id) 41 | with self.__resource_lock: 42 | self.name = name 43 | self.size = size 44 | self.__gid = file_id 45 | self.__listener.onDownloadStarted() 46 | 47 | def __onDownloadProgress(self, current, total): 48 | if self.__is_cancelled: 49 | self.__onDownloadError('Cancelled by user!') 50 | self._bot.stop_transmission() 51 | return 52 | with self.__resource_lock: 53 | self.downloaded_bytes = current 54 | try: 55 | self.progress = current / self.size * 100 56 | except ZeroDivisionError: 57 | self.progress = 0 58 | 59 | def __onDownloadError(self, error): 60 | with global_lock: 61 | try: 62 | GLOBAL_GID.remove(self.gid) 63 | except KeyError: 64 | pass 65 | self.__listener.onDownloadError(error) 66 | 67 | def __onDownloadComplete(self): 68 | with global_lock: 69 | GLOBAL_GID.remove(self.gid) 70 | self.__listener.onDownloadComplete() 71 | 72 | def __download(self, message, path): 73 | download = self._bot.download_media(message, 74 | progress=self.__onDownloadProgress, file_name=path) 75 | if download is not None: 76 | self.__onDownloadComplete() 77 | else: 78 | if not self.__is_cancelled: 79 | self.__onDownloadError('Internal error occurred') 80 | 81 | def add_download(self, message, path, filename): 82 | _message = self._bot.get_messages(message.chat.id, message.message_id) 83 | media = None 84 | media_array = [_message.document, _message.video, _message.audio] 85 | for i in media_array: 86 | if i is not None: 87 | media = i 88 | break 89 | if media is not None: 90 | with global_lock: 91 | # For avoiding locking the thread lock for long time unnecessarily 92 | download = media.file_id not in GLOBAL_GID 93 | if filename == "": 94 | name = media.file_name 95 | else: 96 | name = filename 97 | path = path + name 98 | if download: 99 | self.__onDownloadStart(name, media.file_size, media.file_id) 100 | LOGGER.info(f'Downloading telegram file with id: {media.file_id}') 101 | threading.Thread(target=self.__download, args=(_message, path)).start() 102 | else: 103 | self.__onDownloadError('File already being downloaded!') 104 | else: 105 | self.__onDownloadError('No document in the replied message') 106 | 107 | def cancel_download(self): 108 | LOGGER.info(f'Cancelling download on user request: {self.gid}') 109 | self.__is_cancelled = True 110 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /bot/helper/ext_utils/fs_utils.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from bot import aria2, LOGGER, DOWNLOAD_DIR 3 | import shutil 4 | import os 5 | import pathlib 6 | import magic 7 | import tarfile 8 | from .exceptions import NotSupportedExtractionArchive 9 | 10 | 11 | def clean_download(path: str): 12 | if os.path.exists(path): 13 | LOGGER.info(f"Cleaning download: {path}") 14 | shutil.rmtree(path) 15 | 16 | 17 | def start_cleanup(): 18 | try: 19 | shutil.rmtree(DOWNLOAD_DIR) 20 | except FileNotFoundError: 21 | pass 22 | 23 | 24 | def clean_all(): 25 | aria2.remove_all(True) 26 | try: 27 | shutil.rmtree(DOWNLOAD_DIR) 28 | except FileNotFoundError: 29 | pass 30 | 31 | 32 | def exit_clean_up(signal, frame): 33 | try: 34 | LOGGER.info("Please wait, while we clean up the downloads and stop running downloads") 35 | clean_all() 36 | sys.exit(0) 37 | except KeyboardInterrupt: 38 | LOGGER.warning("Force Exiting before the cleanup finishes!") 39 | sys.exit(1) 40 | 41 | 42 | def get_path_size(path): 43 | if os.path.isfile(path): 44 | return os.path.getsize(path) 45 | total_size = 0 46 | for root, dirs, files in os.walk(path): 47 | for f in files: 48 | abs_path = os.path.join(root, f) 49 | total_size += os.path.getsize(abs_path) 50 | return total_size 51 | 52 | 53 | def tar(org_path): 54 | tar_path = org_path + ".tar" 55 | path = pathlib.PurePath(org_path) 56 | LOGGER.info(f'Tar: orig_path: {org_path}, tar_path: {tar_path}') 57 | tar = tarfile.open(tar_path, "w") 58 | tar.add(org_path, arcname=os.path.basename(org_path)) 59 | tar.close() 60 | return tar_path 61 | 62 | 63 | def get_base_name(orig_path: str): 64 | if orig_path.endswith(".tar.bz2"): 65 | return orig_path.replace(".tar.bz2", "") 66 | elif orig_path.endswith(".tar.gz"): 67 | return orig_path.replace(".tar.gz", "") 68 | elif orig_path.endswith(".bz2"): 69 | return orig_path.replace(".bz2", "") 70 | elif orig_path.endswith(".gz"): 71 | return orig_path.replace(".gz", "") 72 | elif orig_path.endswith(".tar.xz"): 73 | return orig_path.replace(".tar.xz", "") 74 | elif orig_path.endswith(".tar"): 75 | return orig_path.replace(".tar", "") 76 | elif orig_path.endswith(".tbz2"): 77 | return orig_path.replace("tbz2", "") 78 | elif orig_path.endswith(".tgz"): 79 | return orig_path.replace(".tgz", "") 80 | elif orig_path.endswith(".zip"): 81 | return orig_path.replace(".zip", "") 82 | elif orig_path.endswith(".7z"): 83 | return orig_path.replace(".7z", "") 84 | elif orig_path.endswith(".Z"): 85 | return orig_path.replace(".Z", "") 86 | elif orig_path.endswith(".rar"): 87 | return orig_path.replace(".rar", "") 88 | elif orig_path.endswith(".iso"): 89 | return orig_path.replace(".iso", "") 90 | elif orig_path.endswith(".wim"): 91 | return orig_path.replace(".wim", "") 92 | elif orig_path.endswith(".cab"): 93 | return orig_path.replace(".cab", "") 94 | elif orig_path.endswith(".apm"): 95 | return orig_path.replace(".apm", "") 96 | elif orig_path.endswith(".arj"): 97 | return orig_path.replace(".arj", "") 98 | elif orig_path.endswith(".chm"): 99 | return orig_path.replace(".chm", "") 100 | elif orig_path.endswith(".cpio"): 101 | return orig_path.replace(".cpio", "") 102 | elif orig_path.endswith(".cramfs"): 103 | return orig_path.replace(".cramfs", "") 104 | elif orig_path.endswith(".deb"): 105 | return orig_path.replace(".deb", "") 106 | elif orig_path.endswith(".dmg"): 107 | return orig_path.replace(".dmg", "") 108 | elif orig_path.endswith(".fat"): 109 | return orig_path.replace(".fat", "") 110 | elif orig_path.endswith(".hfs"): 111 | return orig_path.replace(".hfs", "") 112 | elif orig_path.endswith(".lzh"): 113 | return orig_path.replace(".lzh", "") 114 | elif orig_path.endswith(".lzma"): 115 | return orig_path.replace(".lzma", "") 116 | elif orig_path.endswith(".lzma2"): 117 | return orig_path.replace(".lzma2", "") 118 | elif orig_path.endswith(".mbr"): 119 | return orig_path.replace(".mbr", "") 120 | elif orig_path.endswith(".msi"): 121 | return orig_path.replace(".msi", "") 122 | elif orig_path.endswith(".mslz"): 123 | return orig_path.replace(".mslz", "") 124 | elif orig_path.endswith(".nsis"): 125 | return orig_path.replace(".nsis", "") 126 | elif orig_path.endswith(".ntfs"): 127 | return orig_path.replace(".ntfs", "") 128 | elif orig_path.endswith(".rpm"): 129 | return orig_path.replace(".rpm", "") 130 | elif orig_path.endswith(".squashfs"): 131 | return orig_path.replace(".squashfs", "") 132 | elif orig_path.endswith(".udf"): 133 | return orig_path.replace(".udf", "") 134 | elif orig_path.endswith(".vhd"): 135 | return orig_path.replace(".vhd", "") 136 | elif orig_path.endswith(".xar"): 137 | return orig_path.replace(".xar", "") 138 | else: 139 | raise NotSupportedExtractionArchive('File format not supported for extraction') 140 | 141 | 142 | def get_mime_type(file_path): 143 | mime = magic.Magic(mime=True) 144 | mime_type = mime.from_file(file_path) 145 | mime_type = mime_type if mime_type else "text/plain" 146 | return mime_type 147 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/aria2_download.py: -------------------------------------------------------------------------------- 1 | from bot import aria2, download_dict_lock, STOP_DUPLICATE_MIRROR, TORRENT_DIRECT_LIMIT 2 | from bot.helper.mirror_utils.upload_utils.gdriveTools import GoogleDriveHelper 3 | from bot.helper.ext_utils.bot_utils import * 4 | from .download_helper import DownloadHelper 5 | from bot.helper.mirror_utils.status_utils.aria_download_status import AriaDownloadStatus 6 | from bot.helper.telegram_helper.message_utils import * 7 | import threading 8 | from aria2p import API 9 | from time import sleep 10 | 11 | 12 | class AriaDownloadHelper(DownloadHelper): 13 | 14 | def __init__(self): 15 | super().__init__() 16 | 17 | @new_thread 18 | def __onDownloadStarted(self, api, gid): 19 | if STOP_DUPLICATE_MIRROR or TORRENT_DIRECT_LIMIT is not None: 20 | sleep(0.5) 21 | dl = getDownloadByGid(gid) 22 | download = api.get_download(gid) 23 | 24 | if STOP_DUPLICATE_MIRROR: 25 | LOGGER.info(f"Checking File/Folder if already in Drive...") 26 | self.name = download.name 27 | sname = download.name 28 | if self.listener.isTar: 29 | sname = sname + ".tar" 30 | if self.listener.extract: 31 | smsg = None 32 | else: 33 | gdrive = GoogleDriveHelper(None) 34 | smsg, button = gdrive.drive_list(sname) 35 | if smsg: 36 | aria2.remove([download]) 37 | dl.getListener().onDownloadError(f'File/Folder is already available in Drive.\n\n') 38 | sendMarkup("Here are the search results:", dl.getListener().bot, dl.getListener().update, button) 39 | return 40 | 41 | if TORRENT_DIRECT_LIMIT is not None: 42 | LOGGER.info(f"Checking File/Folder Size...") 43 | sleep(1.5) 44 | size = aria2.get_download(gid).total_length 45 | limit = TORRENT_DIRECT_LIMIT 46 | limit = limit.split(' ', maxsplit=1) 47 | limitint = int(limit[0]) 48 | if 'GB' in limit or 'gb' in limit: 49 | if size > limitint * 1024**3: 50 | aria2.remove([download]) 51 | dl.getListener().onDownloadError(f'Torrent/Direct limit is {TORRENT_DIRECT_LIMIT}.\nYour File/Folder size is {get_readable_file_size(size)}') 52 | return 53 | elif 'TB' in limit or 'tb' in limit: 54 | if size > limitint * 1024**4: 55 | aria2.remove([download]) 56 | dl.getListener().onDownloadError(f'Torrent/Direct limit is {TORRENT_DIRECT_LIMIT}.\nYour File/Folder size is {get_readable_file_size(size)}') 57 | return 58 | update_all_messages() 59 | 60 | def __onDownloadComplete(self, api: API, gid): 61 | LOGGER.info(f"onDownloadComplete: {gid}") 62 | dl = getDownloadByGid(gid) 63 | download = api.get_download(gid) 64 | if download.followed_by_ids: 65 | new_gid = download.followed_by_ids[0] 66 | new_download = api.get_download(new_gid) 67 | with download_dict_lock: 68 | sleep(0.5) 69 | download_dict[dl.uid()] = AriaDownloadStatus(new_gid, dl.getListener()) 70 | if new_download.is_torrent: 71 | download_dict[dl.uid()].is_torrent = True 72 | update_all_messages() 73 | LOGGER.info(f'Changed gid from {gid} to {new_gid}') 74 | else: 75 | if dl: 76 | threading.Thread(target=dl.getListener().onDownloadComplete).start() 77 | 78 | @new_thread 79 | def __onDownloadStopped(self, api, gid): 80 | sleep(0.5) 81 | dl = getDownloadByGid(gid) 82 | if dl: 83 | dl.getListener().onDownloadError('Dead torrent!') 84 | 85 | @new_thread 86 | def __onDownloadError(self, api, gid): 87 | sleep(0.5) # sleep for split second to ensure proper dl gid update from onDownloadComplete 88 | dl = getDownloadByGid(gid) 89 | download = api.get_download(gid) 90 | error = download.error_message 91 | LOGGER.info(f"Download Error: {error}") 92 | if dl: 93 | dl.getListener().onDownloadError(error) 94 | 95 | def start_listener(self): 96 | aria2.listen_to_notifications(threaded=True, on_download_start=self.__onDownloadStarted, 97 | on_download_error=self.__onDownloadError, 98 | on_download_stop=self.__onDownloadStopped, 99 | on_download_complete=self.__onDownloadComplete) 100 | 101 | def add_download(self, link: str, path, listener, filename): 102 | if is_magnet(link): 103 | download = aria2.add_magnet(link, {'dir': path, 'out': filename}) 104 | else: 105 | download = aria2.add_uris([link], {'dir': path, 'out': filename}) 106 | if download.error_message: # no need to proceed further at this point 107 | listener.onDownloadError(download.error_message) 108 | return 109 | with download_dict_lock: 110 | download_dict[listener.uid] = AriaDownloadStatus(download.gid, listener) 111 | LOGGER.info(f"Started: {download.gid} DIR:{download.dir} ") 112 | self.listener = listener 113 | -------------------------------------------------------------------------------- /bot/modules/authorize.py: -------------------------------------------------------------------------------- 1 | from bot.helper.telegram_helper.message_utils import sendMessage 2 | from bot import AUTHORIZED_CHATS, SUDO_USERS, dispatcher 3 | from telegram.ext import CommandHandler 4 | from bot.helper.telegram_helper.filters import CustomFilters 5 | from telegram.ext import Filters 6 | from telegram import Update 7 | from bot.helper.telegram_helper.bot_commands import BotCommands 8 | from bot.helper.ext_utils.db_handler import DbManger 9 | 10 | 11 | def authorize(update, context): 12 | reply_message = None 13 | message_ = None 14 | reply_message = update.message.reply_to_message 15 | message_ = update.message.text.split(' ') 16 | if len(message_) == 2: 17 | chat_id = int(message_[1]) 18 | if chat_id not in AUTHORIZED_CHATS: 19 | msg = DbManger().db_auth(chat_id) 20 | else: 21 | msg = 'User already authorized' 22 | else: 23 | if reply_message is None: 24 | # Trying to authorize a chat 25 | chat_id = update.effective_chat.id 26 | if chat_id not in AUTHORIZED_CHATS: 27 | msg = DbManger().db_auth(chat_id) 28 | else: 29 | msg = 'Already authorized chat' 30 | 31 | else: 32 | # Trying to authorize someone in specific 33 | user_id = reply_message.from_user.id 34 | if user_id not in AUTHORIZED_CHATS: 35 | msg = DbManger().db_auth(user_id) 36 | else: 37 | msg = 'User already authorized' 38 | sendMessage(msg, context.bot, update) 39 | 40 | 41 | def unauthorize(update, context): 42 | reply_message = None 43 | message_ = None 44 | reply_message = update.message.reply_to_message 45 | message_ = update.message.text.split(' ') 46 | if len(message_) == 2: 47 | chat_id = int(message_[1]) 48 | if chat_id in AUTHORIZED_CHATS: 49 | msg = DbManger().db_unauth(chat_id) 50 | else: 51 | msg = 'User already unauthorized' 52 | else: 53 | if reply_message is None: 54 | # Trying to unauthorize a chat 55 | chat_id = update.effective_chat.id 56 | if chat_id in AUTHORIZED_CHATS: 57 | msg = DbManger().db_unauth(chat_id) 58 | else: 59 | msg = 'Already unauthorized chat' 60 | else: 61 | # Trying to authorize someone in specific 62 | user_id = reply_message.from_user.id 63 | if user_id in AUTHORIZED_CHATS: 64 | msg = DbManger().db_unauth(user_id) 65 | else: 66 | msg = 'User already unauthorized' 67 | sendMessage(msg, context.bot, update) 68 | 69 | 70 | def addSudo(update, context): 71 | reply_message = None 72 | message_ = None 73 | reply_message = update.message.reply_to_message 74 | message_ = update.message.text.split(' ') 75 | if len(message_) == 2: 76 | chat_id = int(message_[1]) 77 | if chat_id not in SUDO_USERS: 78 | msg = DbManger().db_addsudo(chat_id) 79 | else: 80 | msg = 'Already Sudo' 81 | else: 82 | if reply_message is None: 83 | msg = "Give ID or Reply To message of whom you want to Promote" 84 | else: 85 | # Trying to authorize someone in specific 86 | user_id = reply_message.from_user.id 87 | if user_id not in SUDO_USERS: 88 | msg = DbManger().db_addsudo(user_id) 89 | else: 90 | msg = 'Already Sudo' 91 | sendMessage(msg, context.bot, update) 92 | 93 | 94 | def removeSudo(update, context): 95 | reply_message = None 96 | message_ = None 97 | reply_message = update.message.reply_to_message 98 | message_ = update.message.text.split(' ') 99 | if len(message_) == 2: 100 | chat_id = int(message_[1]) 101 | if chat_id in SUDO_USERS: 102 | msg = DbManger().db_rmsudo(chat_id) 103 | else: 104 | msg = 'Not a Sudo' 105 | else: 106 | if reply_message is None: 107 | msg = "Give ID or Reply To message of whom you want to remove from Sudo" 108 | else: 109 | user_id = reply_message.from_user.id 110 | if user_id in SUDO_USERS: 111 | msg = DbManger().db_rmsudo(user_id) 112 | else: 113 | msg = 'Not a Sudo' 114 | sendMessage(msg, context.bot, update) 115 | 116 | 117 | def sendAuthChats(update, context): 118 | user = sudo = '' 119 | user += '\n'.join(str(id) for id in AUTHORIZED_CHATS) 120 | sudo += '\n'.join(str(id) for id in SUDO_USERS) 121 | sendMessage(f'Authorized Chats\n{user}\nSudo Users\n{sudo}', context.bot, update) 122 | 123 | 124 | send_auth_handler = CommandHandler(command=BotCommands.AuthorizedUsersCommand, callback=sendAuthChats, 125 | filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True) 126 | authorize_handler = CommandHandler(command=BotCommands.AuthorizeCommand, callback=authorize, 127 | filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True) 128 | unauthorize_handler = CommandHandler(command=BotCommands.UnAuthorizeCommand, callback=unauthorize, 129 | filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True) 130 | addsudo_handler = CommandHandler(command=BotCommands.AddSudoCommand, callback=addSudo, 131 | filters=CustomFilters.owner_filter, run_async=True) 132 | removesudo_handler = CommandHandler(command=BotCommands.RmSudoCommand, callback=removeSudo, 133 | filters=CustomFilters.owner_filter, run_async=True) 134 | 135 | dispatcher.add_handler(send_auth_handler) 136 | dispatcher.add_handler(authorize_handler) 137 | dispatcher.add_handler(unauthorize_handler) 138 | dispatcher.add_handler(addsudo_handler) 139 | dispatcher.add_handler(removesudo_handler) 140 | -------------------------------------------------------------------------------- /bot/helper/ext_utils/bot_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import re 3 | import threading 4 | import time 5 | 6 | from bot.helper.telegram_helper.bot_commands import BotCommands 7 | from bot import download_dict, download_dict_lock 8 | 9 | LOGGER = logging.getLogger(__name__) 10 | 11 | MAGNET_REGEX = r"magnet:\?xt=urn:btih:[a-zA-Z0-9]*" 12 | 13 | URL_REGEX = r"(?:(?:https?|ftp):\/\/)?[\w/\-?=%.]+\.[\w/\-?=%.]+" 14 | 15 | 16 | class MirrorStatus: 17 | STATUS_UPLOADING = "Uploading...📤" 18 | STATUS_DOWNLOADING = "Downloading...📥" 19 | STATUS_WAITING = "Queued...📝" 20 | STATUS_FAILED = "Failed 🚫. Cleaning Download..." 21 | STATUS_ARCHIVING = "Archiving...🔐" 22 | STATUS_EXTRACTING = "Extracting...📂" 23 | 24 | 25 | PROGRESS_MAX_SIZE = 100 // 8 26 | PROGRESS_INCOMPLETE = ['▏', '▎', '▍', '▌', '▋', '▊', '▉'] 27 | 28 | SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] 29 | 30 | 31 | class setInterval: 32 | def __init__(self, interval, action): 33 | self.interval = interval 34 | self.action = action 35 | self.stopEvent = threading.Event() 36 | thread = threading.Thread(target=self.__setInterval) 37 | thread.start() 38 | 39 | def __setInterval(self): 40 | nextTime = time.time() + self.interval 41 | while not self.stopEvent.wait(nextTime - time.time()): 42 | nextTime += self.interval 43 | self.action() 44 | 45 | def cancel(self): 46 | self.stopEvent.set() 47 | 48 | 49 | def get_readable_file_size(size_in_bytes) -> str: 50 | if size_in_bytes is None: 51 | return '0B' 52 | index = 0 53 | while size_in_bytes >= 1024: 54 | size_in_bytes /= 1024 55 | index += 1 56 | try: 57 | return f'{round(size_in_bytes, 2)}{SIZE_UNITS[index]}' 58 | except IndexError: 59 | return 'File too large' 60 | 61 | 62 | def getDownloadByGid(gid): 63 | with download_dict_lock: 64 | for dl in download_dict.values(): 65 | status = dl.status() 66 | if status != MirrorStatus.STATUS_UPLOADING and status != MirrorStatus.STATUS_ARCHIVING \ 67 | and status != MirrorStatus.STATUS_EXTRACTING: 68 | if dl.gid() == gid: 69 | return dl 70 | return None 71 | 72 | def getAllDownload(): 73 | with download_dict_lock: 74 | for dlDetails in list(download_dict.values()): 75 | if dlDetails.status() == MirrorStatus.STATUS_DOWNLOADING \ 76 | or dlDetails.status() == MirrorStatus.STATUS_WAITING: 77 | if dlDetails: 78 | return dlDetails 79 | 80 | def get_progress_bar_string(status): 81 | completed = status.processed_bytes() / 8 82 | total = status.size_raw() / 8 83 | if total == 0: 84 | p = 0 85 | else: 86 | p = round(completed * 100 / total) 87 | p = min(max(p, 0), 100) 88 | cFull = p // 8 89 | cPart = p % 8 - 1 90 | p_str = '█' * cFull 91 | if cPart >= 0: 92 | p_str += PROGRESS_INCOMPLETE[cPart] 93 | p_str += ' ' * (PROGRESS_MAX_SIZE - cFull) 94 | p_str = f"[{p_str}]" 95 | return p_str 96 | 97 | 98 | def get_readable_message(): 99 | with download_dict_lock: 100 | msg = "" 101 | for download in list(download_dict.values()): 102 | msg += f"Filename: {download.name()}" 103 | msg += f"\nStatus: {download.status()}" 104 | if download.status() != MirrorStatus.STATUS_ARCHIVING and download.status() != MirrorStatus.STATUS_EXTRACTING: 105 | msg += f"\n{get_progress_bar_string(download)} {download.progress()}" 106 | if download.status() == MirrorStatus.STATUS_DOWNLOADING: 107 | msg += f"\n😻Downloaded: {get_readable_file_size(download.processed_bytes())} of {download.size()}" 108 | else: 109 | msg += f"\nUploaded: {get_readable_file_size(download.processed_bytes())} of {download.size()}" 110 | msg += f"\n⚡️Speed: {download.speed()}\n⏱ETA: {download.eta()} " 111 | # if hasattr(download, 'is_torrent'): 112 | try: 113 | msg += f"\n🌾Seeders: {download.aria_download().num_seeders}" \ 114 | f" | 🥀Peers: {download.aria_download().connections}" 115 | except: 116 | pass 117 | if download.status() == MirrorStatus.STATUS_DOWNLOADING: 118 | msg += f"\nTo Stop🚫: /{BotCommands.CancelMirror} {download.gid()}" 119 | msg += "\n\n" 120 | return msg 121 | 122 | 123 | def get_readable_time(seconds: int) -> str: 124 | result = '' 125 | (days, remainder) = divmod(seconds, 86400) 126 | days = int(days) 127 | if days != 0: 128 | result += f'{days}d' 129 | (hours, remainder) = divmod(remainder, 3600) 130 | hours = int(hours) 131 | if hours != 0: 132 | result += f'{hours}h' 133 | (minutes, seconds) = divmod(remainder, 60) 134 | minutes = int(minutes) 135 | if minutes != 0: 136 | result += f'{minutes}m' 137 | seconds = int(seconds) 138 | result += f'{seconds}s' 139 | return result 140 | 141 | 142 | def is_url(url: str): 143 | url = re.findall(URL_REGEX, url) 144 | if url: 145 | return True 146 | return False 147 | 148 | def is_gdrive_link(url: str): 149 | return "drive.google.com" in url 150 | 151 | def is_mega_link(url: str): 152 | return "mega.nz" in url 153 | 154 | def get_mega_link_type(url: str): 155 | if "folder" in url: 156 | return "folder" 157 | elif "file" in url: 158 | return "file" 159 | elif "/#F!" in url: 160 | return "folder" 161 | return "file" 162 | 163 | def is_magnet(url: str): 164 | magnet = re.findall(MAGNET_REGEX, url) 165 | if magnet: 166 | return True 167 | return False 168 | 169 | def new_thread(fn): 170 | """To use as decorator to make a function call threaded. 171 | Needs import 172 | from threading import Thread""" 173 | 174 | def wrapper(*args, **kwargs): 175 | thread = threading.Thread(target=fn, args=args, kwargs=kwargs) 176 | thread.start() 177 | return thread 178 | 179 | return wrapper 180 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/youtube_dl_download_helper.py: -------------------------------------------------------------------------------- 1 | from .download_helper import DownloadHelper 2 | import time 3 | from youtube_dl import YoutubeDL, DownloadError 4 | from bot import download_dict_lock, download_dict 5 | from ..status_utils.youtube_dl_download_status import YoutubeDLDownloadStatus 6 | import logging 7 | import re 8 | import threading 9 | 10 | LOGGER = logging.getLogger(__name__) 11 | 12 | 13 | class MyLogger: 14 | def __init__(self, obj): 15 | self.obj = obj 16 | 17 | def debug(self, msg): 18 | LOGGER.debug(msg) 19 | # Hack to fix changing changing extension 20 | match = re.search(r'.ffmpeg..Merging formats into..(.*?).$', msg) 21 | if match and not self.obj.is_playlist: 22 | newname = match.group(1) 23 | newname = newname.split("/") 24 | newname = newname[-1] 25 | self.obj.name = newname 26 | 27 | @staticmethod 28 | def warning(msg): 29 | LOGGER.warning(msg) 30 | 31 | @staticmethod 32 | def error(msg): 33 | LOGGER.error(msg) 34 | 35 | 36 | class YoutubeDLHelper(DownloadHelper): 37 | def __init__(self, listener): 38 | super().__init__() 39 | self.name = "" 40 | self.__start_time = time.time() 41 | self.__listener = listener 42 | self.__gid = "" 43 | self.opts = { 44 | 'progress_hooks': [self.__onDownloadProgress], 45 | 'logger': MyLogger(self), 46 | 'usenetrc': True 47 | } 48 | self.__download_speed = 0 49 | self.download_speed_readable = '' 50 | self.downloaded_bytes = 0 51 | self.size = 0 52 | self.is_playlist = False 53 | self.last_downloaded = 0 54 | self.is_cancelled = False 55 | self.vid_id = '' 56 | self.__resource_lock = threading.RLock() 57 | 58 | @property 59 | def download_speed(self): 60 | with self.__resource_lock: 61 | return self.__download_speed 62 | 63 | @property 64 | def gid(self): 65 | with self.__resource_lock: 66 | return self.__gid 67 | 68 | def __onDownloadProgress(self, d): 69 | if self.is_cancelled: 70 | raise ValueError("Cancelling Download..") 71 | if d['status'] == "finished": 72 | if self.is_playlist: 73 | self.last_downloaded = 0 74 | elif d['status'] == "downloading": 75 | with self.__resource_lock: 76 | self.__download_speed = d['speed'] 77 | try: 78 | tbyte = d['total_bytes'] 79 | except KeyError: 80 | tbyte = d['total_bytes_estimate'] 81 | if self.is_playlist: 82 | progress = d['downloaded_bytes'] / tbyte 83 | chunk_size = d['downloaded_bytes'] - self.last_downloaded 84 | self.last_downloaded = tbyte * progress 85 | self.downloaded_bytes += chunk_size 86 | try: 87 | self.progress = (self.downloaded_bytes / self.size) * 100 88 | except ZeroDivisionError: 89 | pass 90 | else: 91 | self.download_speed_readable = d['_speed_str'] 92 | self.downloaded_bytes = d['downloaded_bytes'] 93 | 94 | def __onDownloadStart(self): 95 | with download_dict_lock: 96 | download_dict[self.__listener.uid] = YoutubeDLDownloadStatus(self, self.__listener) 97 | 98 | def __onDownloadComplete(self): 99 | self.__listener.onDownloadComplete() 100 | 101 | def onDownloadError(self, error): 102 | self.__listener.onDownloadError(error) 103 | 104 | def extractMetaData(self, link, qual, name): 105 | if "hotstar" in link or "sonyliv" in link: 106 | self.opts['geo_bypass_country'] = 'IN' 107 | 108 | with YoutubeDL(self.opts) as ydl: 109 | try: 110 | result = ydl.extract_info(link, download=False) 111 | if name == "": 112 | name = ydl.prepare_filename(result) 113 | else: 114 | name = name 115 | # noobway hack for changing extension after converting to mp3 116 | if qual == "audio": 117 | name = name.replace(".mp4", ".mp3").replace(".webm", ".mp3") 118 | except DownloadError as e: 119 | self.onDownloadError(str(e)) 120 | return 121 | if result.get('direct'): 122 | return None 123 | if 'entries' in result: 124 | video = result['entries'][0] 125 | for v in result['entries']: 126 | if v and v.get('filesize'): 127 | self.size += float(v['filesize']) 128 | # For playlists, ydl.prepare-filename returns the following format: -.NA 129 | self.name = name.split(f"-{result['id']}")[0] 130 | self.vid_id = video.get('id') 131 | self.is_playlist = True 132 | else: 133 | video = result 134 | if video.get('filesize'): 135 | self.size = float(video.get('filesize')) 136 | self.name = name 137 | self.vid_id = video.get('id') 138 | return video 139 | 140 | def __download(self, link): 141 | try: 142 | with YoutubeDL(self.opts) as ydl: 143 | try: 144 | ydl.download([link]) 145 | except DownloadError as e: 146 | self.onDownloadError(str(e)) 147 | return 148 | self.__onDownloadComplete() 149 | except ValueError: 150 | LOGGER.info("Download Cancelled by User!") 151 | self.onDownloadError("Download Cancelled by User!") 152 | 153 | def add_download(self, link, path, qual, name): 154 | pattern = '^.*(youtu\.be\/|youtube.com\/)(playlist?)' 155 | if re.match(pattern, link): 156 | self.opts['ignoreerrors'] = True 157 | self.__onDownloadStart() 158 | self.extractMetaData(link, qual, name) 159 | LOGGER.info(f"Downloading with YT-DL: {link}") 160 | self.__gid = f"{self.vid_id}{self.__listener.uid}" 161 | if qual == "audio": 162 | self.opts['format'] = 'bestaudio/best' 163 | self.opts['postprocessors'] = [{'key': 'FFmpegExtractAudio','preferredcodec': 'mp3','preferredquality': '192',}] 164 | else: 165 | self.opts['format'] = qual 166 | if not self.is_playlist: 167 | self.opts['outtmpl'] = f"{path}/{self.name}" 168 | else: 169 | self.opts['outtmpl'] = f"{path}/{self.name}/%(title)s.%(ext)s" 170 | self.__download(link) 171 | 172 | def cancel_download(self): 173 | self.is_cancelled = True 174 | -------------------------------------------------------------------------------- /bot/helper/telegram_helper/message_utils.py: -------------------------------------------------------------------------------- 1 | from telegram import InlineKeyboardMarkup 2 | from telegram.message import Message 3 | from telegram.update import Update 4 | import psutil, shutil 5 | import time 6 | from bot import AUTO_DELETE_MESSAGE_DURATION, LOGGER, bot, \ 7 | status_reply_dict, status_reply_dict_lock, download_dict, download_dict_lock, botStartTime 8 | from bot.helper.ext_utils.bot_utils import get_readable_message, get_readable_file_size, get_readable_time, MirrorStatus 9 | from telegram.error import TimedOut, BadRequest 10 | 11 | 12 | def sendMessage(text: str, bot, update: Update): 13 | try: 14 | return bot.send_message(update.message.chat_id, 15 | reply_to_message_id=update.message.message_id, 16 | text=text, parse_mode='HTMl') 17 | except Exception as e: 18 | LOGGER.error(str(e)) 19 | def sendMarkup(text: str, bot, update: Update, reply_markup: InlineKeyboardMarkup): 20 | return bot.send_message(update.message.chat_id, 21 | reply_to_message_id=update.message.message_id, 22 | text=text, reply_markup=reply_markup, parse_mode='HTMl') 23 | 24 | def editMessage(text: str, message: Message, reply_markup=None): 25 | try: 26 | bot.edit_message_text(text=text, message_id=message.message_id, 27 | chat_id=message.chat.id,reply_markup=reply_markup, 28 | parse_mode='HTMl') 29 | except Exception as e: 30 | LOGGER.error(str(e)) 31 | 32 | 33 | def deleteMessage(bot, message: Message): 34 | try: 35 | bot.delete_message(chat_id=message.chat.id, 36 | message_id=message.message_id) 37 | except Exception as e: 38 | LOGGER.error(str(e)) 39 | 40 | 41 | def sendLogFile(bot, update: Update): 42 | with open('log.txt', 'rb') as f: 43 | bot.send_document(document=f, filename=f.name, 44 | reply_to_message_id=update.message.message_id, 45 | chat_id=update.message.chat_id) 46 | 47 | 48 | def auto_delete_message(bot, cmd_message: Message, bot_message: Message): 49 | if AUTO_DELETE_MESSAGE_DURATION != -1: 50 | time.sleep(AUTO_DELETE_MESSAGE_DURATION) 51 | try: 52 | # Skip if None is passed meaning we don't want to delete bot xor cmd message 53 | deleteMessage(bot, cmd_message) 54 | deleteMessage(bot, bot_message) 55 | except AttributeError: 56 | pass 57 | 58 | 59 | def delete_all_messages(): 60 | with status_reply_dict_lock: 61 | for message in list(status_reply_dict.values()): 62 | try: 63 | deleteMessage(bot, message) 64 | del status_reply_dict[message.chat.id] 65 | except Exception as e: 66 | LOGGER.error(str(e)) 67 | 68 | 69 | def update_all_messages(): 70 | total, used, free = shutil.disk_usage('.') 71 | free = get_readable_file_size(free) 72 | currentTime = get_readable_time(time.time() - botStartTime) 73 | msg = get_readable_message() 74 | msg += f"CPU: {psutil.cpu_percent()}%" \ 75 | f" RAM: {psutil.virtual_memory().percent}%" \ 76 | f" DISK: {psutil.disk_usage('/').percent}%" 77 | with download_dict_lock: 78 | dlspeed_bytes = 0 79 | uldl_bytes = 0 80 | for download in list(download_dict.values()): 81 | speedy = download.speed() 82 | if download.status() == MirrorStatus.STATUS_DOWNLOADING: 83 | if 'K' in speedy: 84 | dlspeed_bytes += float(speedy.split('K')[0]) * 1024 85 | elif 'M' in speedy: 86 | dlspeed_bytes += float(speedy.split('M')[0]) * 1048576 87 | if download.status() == MirrorStatus.STATUS_UPLOADING: 88 | if 'KB/s' in speedy: 89 | uldl_bytes += float(speedy.split('K')[0]) * 1024 90 | elif 'MB/s' in speedy: 91 | uldl_bytes += float(speedy.split('M')[0]) * 1048576 92 | dlspeed = get_readable_file_size(dlspeed_bytes) 93 | ulspeed = get_readable_file_size(uldl_bytes) 94 | msg += f"\nFREE: {free} | UPTIME: {currentTime}\nDL: {dlspeed}ps 🔻 | UL: {ulspeed}ps 🔺\n" 95 | with status_reply_dict_lock: 96 | for chat_id in list(status_reply_dict.keys()): 97 | if status_reply_dict[chat_id] and msg != status_reply_dict[chat_id].text: 98 | if len(msg) == 0: 99 | msg = "Starting DL" 100 | try: 101 | editMessage(msg, status_reply_dict[chat_id]) 102 | except Exception as e: 103 | LOGGER.error(str(e)) 104 | status_reply_dict[chat_id].text = msg 105 | 106 | 107 | def sendStatusMessage(msg, bot): 108 | total, used, free = shutil.disk_usage('.') 109 | free = get_readable_file_size(free) 110 | currentTime = get_readable_time(time.time() - botStartTime) 111 | progress = get_readable_message() 112 | progress += f"CPU: {psutil.cpu_percent()}%" \ 113 | f" RAM: {psutil.virtual_memory().percent}%" \ 114 | f" DISK: {psutil.disk_usage('/').percent}%" 115 | with download_dict_lock: 116 | dlspeed_bytes = 0 117 | uldl_bytes = 0 118 | for download in list(download_dict.values()): 119 | speedy = download.speed() 120 | if download.status() == MirrorStatus.STATUS_DOWNLOADING: 121 | if 'K' in speedy: 122 | dlspeed_bytes += float(speedy.split('K')[0]) * 1024 123 | elif 'M' in speedy: 124 | dlspeed_bytes += float(speedy.split('M')[0]) * 1048576 125 | if download.status() == MirrorStatus.STATUS_UPLOADING: 126 | if 'KB/s' in speedy: 127 | uldl_bytes += float(speedy.split('K')[0]) * 1024 128 | elif 'MB/s' in speedy: 129 | uldl_bytes += float(speedy.split('M')[0]) * 1048576 130 | dlspeed = get_readable_file_size(dlspeed_bytes) 131 | ulspeed = get_readable_file_size(uldl_bytes) 132 | progress += f"\nFREE: {free} | UPTIME: {currentTime}\nDL: {dlspeed}ps 🔻 | UL: {ulspeed}ps 🔺\n" 133 | with status_reply_dict_lock: 134 | if msg.message.chat.id in list(status_reply_dict.keys()): 135 | try: 136 | message = status_reply_dict[msg.message.chat.id] 137 | deleteMessage(bot, message) 138 | del status_reply_dict[msg.message.chat.id] 139 | except Exception as e: 140 | LOGGER.error(str(e)) 141 | del status_reply_dict[msg.message.chat.id] 142 | pass 143 | if len(progress) == 0: 144 | progress = "Starting DL" 145 | message = sendMessage(progress, bot, msg) 146 | status_reply_dict[msg.message.chat.id] = message 147 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Deepak Clouds Mirror", 3 | "description": "A Telegram bot for all your mirror needs", 4 | "logo": "https://imagetot.com/images/2021/06/17/d7e86862ce2686f4c6c6596bfa72785d.jpg", 5 | "keywords": [ 6 | "bot", 7 | "telegram", 8 | "python" 9 | ], 10 | "repository": "https://github.com/deepakcloudsoffical/deepak-clouds-mirror", 11 | "website": "https://github.com/blackdeep04/deepak-clouds-mirror", 12 | "success_url": "https://t.me/cloudsmirror", 13 | "stack": "container", 14 | "env": { 15 | "ENV": { 16 | "description": "Setting this to ANYTHING will enable Webhooks when in env mode", 17 | "value": "ANYTHING" 18 | }, 19 | "BOT_TOKEN": { 20 | "description": "The telegram bot token that you get from @BotFather.", 21 | "required": true 22 | }, 23 | "GDRIVE_FOLDER_ID": { 24 | "description": "This is the folder ID of the Google Drive Folder to which you want to upload all the mirrors.", 25 | "required": true 26 | }, 27 | "DOWNLOAD_DIR": { 28 | "description": "The path to the local folder where the downloads should be downloaded to.", 29 | "value": "/usr/src/app/downloads", 30 | "required": false 31 | }, 32 | "DOWNLOAD_STATUS_UPDATE_INTERVAL": { 33 | "description": "A short interval of time in seconds after which the Mirror progress message is updated. (I recommend to keep it 5 seconds at least).", 34 | "value": "5", 35 | "required":false 36 | }, 37 | "OWNER_ID": { 38 | "description": "The Telegram User ID of the Owner of the Bot. Get it by using /info in @MissRose_bot.", 39 | "required": true 40 | }, 41 | "AUTO_DELETE_MESSAGE_DURATION": { 42 | "description": "Interval of time (in seconds), after which the bot deletes it's message (and command message) which is expected to be viewed instantly. Note: Set to -1 to never automatically delete messages.", 43 | "required": true 44 | }, 45 | "IS_TEAM_DRIVE": { 46 | "description": "Set to 'True' if GDRIVE_FOLDER_ID is from a Team Drive else False or Leave it empty.", 47 | "required": false 48 | }, 49 | "USE_SERVICE_ACCOUNTS": { 50 | "description": "Whether to use Service Accounts or not. For this to work see 'Using Service Accounts' in repo.", 51 | "required": false 52 | }, 53 | "AUTHORIZED_CHATS": { 54 | "description": "Fill User ID and Chat ID of you want to authorize.", 55 | "required": false 56 | }, 57 | "INDEX_URL": { 58 | "description": "Refer to https://github.com/ParveenBhadooOfficial/Google-Drive-Index The URL should not have any trailing '/'.", 59 | "required": false 60 | }, 61 | "TELEGRAM_API": { 62 | "description": "This is to authenticate to your Telegram account for downloading Telegram files. You can get this from https://my.telegram.org.", 63 | "required": true 64 | }, 65 | "TELEGRAM_HASH": { 66 | "description": "This is to authenticate to your Telegram account for downloading Telegram files. You can get this from https://my.telegram.org.", 67 | "required": true 68 | }, 69 | "HEROKU_API_KEY": { 70 | "description": "Your Heroku API key, get it from https://dashboard.heroku.com/account.", 71 | "required": true 72 | }, 73 | "HEROKU_APP_NAME": { 74 | "description": "Add the Heroku app name here.", 75 | "required": true 76 | }, 77 | "UPSTREAM_REPO": { 78 | "description": "Link for Bot Upstream Repo, If you want default update, Fill https://github.com/blackdeep04/deepak-clouds-mirror.", 79 | "value": "https://github.com/blackdeep04/deepak-clouds-mirror", 80 | "required": true 81 | }, 82 | "UPSTREAM_BRANCH": { 83 | "description": "Branch name for Upstream Repo (Recommended using master branch).", 84 | "value": "master", 85 | "required": true 86 | }, 87 | "UPTOBOX_TOKEN": { 88 | "description": "Uptobox premium token to mirror uptobox links. Get it from https://uptobox.com/my_account.", 89 | "required": false 90 | }, 91 | "MEGA_API_KEY": { 92 | "description": "Mega.nz api key to mirror mega.nz links. Get it from https://mega.nz/sdk.", 93 | "required": false 94 | }, 95 | "MEGA_EMAIL_ID": { 96 | "description": "Your email id you used to sign up on mega.nz.", 97 | "required": false 98 | }, 99 | "MEGA_PASSWORD": { 100 | "description": "Your password for your mega.nz account.", 101 | "required": false 102 | }, 103 | "BLOCK_MEGA_FOLDER": { 104 | "description": "If you want to remove mega.nz folder support, set it to True.", 105 | "required": false 106 | }, 107 | "BLOCK_MEGA_LINKS": { 108 | "description": "If you want to remove mega.nz mirror support, set it to True.", 109 | "required": false 110 | }, 111 | "TORRENT_DIRECT_LIMIT": { 112 | "description": "To limit the Torrent/Direct mirror size, Leave space between number and unit. Available units is (gb or GB, tb or TB).", 113 | "required": false 114 | }, 115 | "CLONE_LIMIT": { 116 | "description": "To limit cloning Google Drive (leave space between number and unit, Available units is (gb or GB, tb or TB).", 117 | "required": false 118 | }, 119 | "MEGA_LIMIT": { 120 | "description": "To limit downloading Mega (leave space between number and unit, Available units is (gb or GB, tb or TB).", 121 | "required": false 122 | }, 123 | "STOP_DUPLICATE_MIRROR": { 124 | "description": "If this field is set to True, bot will check file in Drive, if it is present in Drive, downloading will be stopped.", 125 | "required": false 126 | }, 127 | "STOP_DUPLICATE_MEGA": { 128 | "description": "If this field is set to True, bot will check file in Drive, if it is present in Drive, downloading Mega will be stopped.", 129 | "required": false 130 | }, 131 | "STOP_DUPLICATE_CLONE": { 132 | "description": "If this field is set to True, bot will check file in Drive, if it is present in Drive, cloning will be stopped.", 133 | "required": false 134 | }, 135 | "IGNORE_PENDING_REQUESTS": { 136 | "description": "If you want the bot to ignore pending requests after it restarts, set this to True.", 137 | "required": false 138 | }, 139 | "SHORTENER": { 140 | "description": "If you want to use shortener in Gdrive and index link.", 141 | "required": false 142 | }, 143 | "SHORTENER_API": { 144 | "description": "Fill your shortener api key if you are using shortener.", 145 | "required": false 146 | 147 | }, 148 | "TOKEN_PICKLE_URL": { 149 | "description": "(Optional) Only if you want to load your token.pickle externally from an index link. Fill this with the direct link of that file.", 150 | "required": false 151 | }, 152 | "ACCOUNTS_ZIP_URL": { 153 | "description": "(Optional) Only if you want to load your service accs externally from an index link. Archive your service accs json files to a zip file directly (don't archive the accounts folder. Select all the jsons inside and zip them only instead. Name the zip file with whatever you want, it doesn't matter). Fill this with the direct link of that file.", 154 | "required": false 155 | }, 156 | "VIEW_LINK": { 157 | "description": "View Link button to open file Index Link in browser instead of direct download link, you can figure out if it's compatible with your Index code or not, open any video from you Index and check if the END of link from browser link bar is ?a=view, if yes make it True it will work (Compatible with Bhadoo Index Code).", 158 | "value": "False", 159 | "required": false 160 | }, 161 | "BUTTON_FOUR_NAME": { 162 | "description": "Extra buttons (optional).", 163 | "required": false 164 | }, 165 | "BUTTON_FOUR_URL": { 166 | "description": "Fill your URL if you are using extra buttons.", 167 | "required": false 168 | }, 169 | "BUTTON_FIVE_NAME": { 170 | "description": "Extra buttons (optional).", 171 | "required": false 172 | }, 173 | "BUTTON_FIVE_URL": { 174 | "description": "Fill your URL if you are using extra buttons.", 175 | "required": false 176 | }, 177 | "BUTTON_SIX_NAME": { 178 | "description": "Extra buttons (optional).", 179 | "required": false 180 | }, 181 | "BUTTON_SIX_URL": { 182 | "description": "Fill your URL if you are using extra buttons.", 183 | "required": false 184 | } 185 | }, 186 | "addons": [ 187 | { 188 | "plan": "heroku-postgresql" 189 | } 190 | ], 191 | "formation": { 192 | "worker": { 193 | "quantity": 1, 194 | "size": "free" 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/mega_downloader.py: -------------------------------------------------------------------------------- 1 | from bot import LOGGER, MEGA_API_KEY, download_dict_lock, download_dict, MEGA_EMAIL_ID, MEGA_PASSWORD 2 | import threading 3 | from mega import (MegaApi, MegaListener, MegaRequest, MegaTransfer, MegaError) 4 | from bot.helper.telegram_helper.message_utils import * 5 | import os 6 | from bot.helper.ext_utils.bot_utils import new_thread, get_mega_link_type, get_readable_file_size 7 | from bot.helper.mirror_utils.status_utils.mega_download_status import MegaDownloadStatus 8 | from bot.helper.mirror_utils.upload_utils.gdriveTools import GoogleDriveHelper 9 | from bot import MEGA_LIMIT, STOP_DUPLICATE_MEGA 10 | import random 11 | import string 12 | 13 | class MegaDownloaderException(Exception): 14 | pass 15 | 16 | 17 | class MegaAppListener(MegaListener): 18 | _NO_EVENT_ON = (MegaRequest.TYPE_LOGIN,MegaRequest.TYPE_FETCH_NODES) 19 | NO_ERROR = "no error" 20 | 21 | def __init__(self, continue_event: threading.Event, listener): 22 | self.continue_event = continue_event 23 | self.node = None 24 | self.public_node = None 25 | self.listener = listener 26 | self.uid = listener.uid 27 | self.__bytes_transferred = 0 28 | self.is_cancelled = False 29 | self.__speed = 0 30 | self.__name = '' 31 | self.__size = 0 32 | self.error = None 33 | self.gid = "" 34 | super(MegaAppListener, self).__init__() 35 | 36 | @property 37 | def speed(self): 38 | """Returns speed of the download in bytes/second""" 39 | return self.__speed 40 | 41 | @property 42 | def name(self): 43 | """Returns name of the download""" 44 | return self.__name 45 | 46 | def setValues(self, name, size, gid): 47 | self.__name = name 48 | self.__size = size 49 | self.gid = gid 50 | 51 | @property 52 | def size(self): 53 | """Size of download in bytes""" 54 | return self.__size 55 | 56 | @property 57 | def downloaded_bytes(self): 58 | return self.__bytes_transferred 59 | 60 | def onRequestStart(self, api, request): 61 | pass 62 | 63 | def onRequestFinish(self, api, request, error): 64 | if str(error).lower() != "no error": 65 | self.error = error.copy() 66 | return 67 | request_type = request.getType() 68 | if request_type == MegaRequest.TYPE_LOGIN: 69 | api.fetchNodes() 70 | elif request_type == MegaRequest.TYPE_GET_PUBLIC_NODE: 71 | self.public_node = request.getPublicMegaNode() 72 | elif request_type == MegaRequest.TYPE_FETCH_NODES: 73 | LOGGER.info("Fetching Root Node.") 74 | self.node = api.getRootNode() 75 | LOGGER.info(f"Node Name: {self.node.getName()}") 76 | if request_type not in self._NO_EVENT_ON or self.node and "cloud drive" not in self.node.getName().lower(): 77 | self.continue_event.set() 78 | 79 | def onRequestTemporaryError(self, api, request, error: MegaError): 80 | LOGGER.info(f'Mega Request error in {error}') 81 | if not self.is_cancelled: 82 | self.listener.onDownloadError("RequestTempError: " + error.toString()) 83 | self.is_cancelled = True 84 | self.error = error.toString() 85 | self.continue_event.set() 86 | 87 | def onTransferStart(self, api: MegaApi, transfer: MegaTransfer): 88 | pass 89 | 90 | def onTransferUpdate(self, api: MegaApi, transfer: MegaTransfer): 91 | if self.is_cancelled: 92 | api.cancelTransfer(transfer, None) 93 | self.__speed = transfer.getSpeed() 94 | self.__bytes_transferred = transfer.getTransferredBytes() 95 | 96 | def onTransferFinish(self, api: MegaApi, transfer: MegaTransfer, error): 97 | try: 98 | if transfer.isFolderTransfer() and transfer.isFinished() or transfer.getFileName() == self.name and not self.is_cancelled: 99 | self.listener.onDownloadComplete() 100 | self.continue_event.set() 101 | except Exception as e: 102 | LOGGER.error(e) 103 | 104 | def onTransferTemporaryError(self, api, transfer, error): 105 | filen = transfer.getFileName() 106 | state = transfer.getState() 107 | errStr = error.toString() 108 | LOGGER.info(f'Mega download error in file {transfer} {filen}: {error}') 109 | 110 | if state == 1 or state == 4: 111 | # Sometimes MEGA (offical client) can't stream a node either and raises a temp failed error. 112 | # Don't break the transfer queue if transfer's in queued (1) or retrying (4) state [causes seg fault] 113 | return 114 | 115 | self.error = errStr 116 | if not self.is_cancelled: 117 | self.is_cancelled = True 118 | self.listener.onDownloadError(f"TransferTempError: {errStr} ({filen})") 119 | 120 | def cancel_download(self): 121 | self.is_cancelled = True 122 | self.listener.onDownloadError("Download Canceled by user") 123 | 124 | 125 | class AsyncExecutor: 126 | 127 | def __init__(self): 128 | self.continue_event = threading.Event() 129 | 130 | def do(self, function, args): 131 | self.continue_event.clear() 132 | function(*args) 133 | self.continue_event.wait() 134 | 135 | listeners = [] 136 | 137 | class MegaDownloadHelper: 138 | def __init__(self): 139 | pass 140 | 141 | @staticmethod 142 | @new_thread 143 | def add_download(mega_link: str, path: str, listener): 144 | if MEGA_API_KEY is None: 145 | raise MegaDownloaderException('Mega API KEY not provided! Cannot mirror Mega links') 146 | executor = AsyncExecutor() 147 | api = MegaApi(MEGA_API_KEY, None, None, 'telegram-mirror-bot') 148 | global listeners 149 | mega_listener = MegaAppListener(executor.continue_event, listener) 150 | listeners.append(mega_listener) 151 | api.addListener(mega_listener) 152 | if MEGA_EMAIL_ID is not None and MEGA_PASSWORD is not None: 153 | executor.do(api.login, (MEGA_EMAIL_ID, MEGA_PASSWORD)) 154 | link_type = get_mega_link_type(mega_link) 155 | if link_type == "file": 156 | LOGGER.info("File. If your download didn't start, then check your link if it's available to download") 157 | executor.do(api.getPublicNode, (mega_link,)) 158 | node = mega_listener.public_node 159 | else: 160 | LOGGER.info("Folder. If your download didn't start, then check your link if it's available to download") 161 | folder_api = MegaApi(MEGA_API_KEY,None,None,'TgBot') 162 | folder_api.addListener(mega_listener) 163 | executor.do(folder_api.loginToFolder, (mega_link,)) 164 | node = folder_api.authorizeNode(mega_listener.node) 165 | if mega_listener.error is not None: 166 | return listener.onDownloadError(str(mega_listener.error)) 167 | if STOP_DUPLICATE_MEGA or MEGA_LIMIT is not None: 168 | msg = sendMessage('Checking Your Link...', listener.bot, listener.update) 169 | if STOP_DUPLICATE_MEGA: 170 | LOGGER.info(f'Checking File/Folder if already in Drive') 171 | mname = node.getName() 172 | if listener.isTar: 173 | mname = mname + ".tar" 174 | if listener.extract: 175 | smsg = None 176 | else: 177 | gd = GoogleDriveHelper() 178 | smsg, button = gd.drive_list(mname) 179 | if smsg: 180 | deleteMessage(listener.bot, msg) 181 | msg1 = "File/Folder is already available in Drive.\nHere are the search results:" 182 | sendMarkup(msg1, listener.bot, listener.update, button) 183 | return 184 | else: 185 | if MEGA_LIMIT is None: 186 | deleteMessage(listener.bot, msg) 187 | 188 | if MEGA_LIMIT is not None: 189 | LOGGER.info(f'Checking File/Folder Size') 190 | limit = MEGA_LIMIT 191 | limit = limit.split(' ', maxsplit=1) 192 | limitint = int(limit[0]) 193 | msg3 = f'Failed, Mega limit is {MEGA_LIMIT}.\nYour File/Folder size is {get_readable_file_size(api.getSize(node))}.' 194 | if 'GB' in limit or 'gb' in limit: 195 | if api.getSize(node) > limitint * 1024**3: 196 | deleteMessage(listener.bot, msg) 197 | sendMessage(msg3, listener.bot, listener.update) 198 | return 199 | else: 200 | deleteMessage(listener.bot, msg) 201 | elif 'TB' in limit or 'tb' in limit: 202 | if api.getSize(node) > limitint * 1024**4: 203 | deleteMessage(listener.bot, msg) 204 | sendMessage(msg3, listener.bot, listener.update) 205 | return 206 | else: 207 | deleteMessage(listener.bot, msg) 208 | with download_dict_lock: 209 | download_dict[listener.uid] = MegaDownloadStatus(mega_listener, listener) 210 | os.makedirs(path) 211 | gid = ''.join(random.SystemRandom().choices(string.ascii_letters + string.digits, k=8)) 212 | mega_listener.setValues(node.getName(), api.getSize(node), gid) 213 | sendStatusMessage(listener.update, listener.bot) 214 | executor.do(api.startDownload,(node,path)) 215 | -------------------------------------------------------------------------------- /bot/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import threading 4 | import time 5 | import random 6 | import string 7 | 8 | import aria2p 9 | import telegram.ext as tg 10 | from dotenv import load_dotenv 11 | from pyrogram import Client 12 | from telegraph import Telegraph 13 | 14 | import psycopg2 15 | from psycopg2 import Error 16 | 17 | import socket 18 | import faulthandler 19 | faulthandler.enable() 20 | 21 | socket.setdefaulttimeout(600) 22 | 23 | botStartTime = time.time() 24 | if os.path.exists('log.txt'): 25 | with open('log.txt', 'r+') as f: 26 | f.truncate(0) 27 | 28 | logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', 29 | handlers=[logging.FileHandler('log.txt'), logging.StreamHandler()], 30 | level=logging.INFO) 31 | 32 | LOGGER = logging.getLogger(__name__) 33 | 34 | load_dotenv('config.env') 35 | 36 | Interval = [] 37 | 38 | 39 | def getConfig(name: str): 40 | return os.environ[name] 41 | 42 | def mktable(): 43 | try: 44 | conn = psycopg2.connect(DB_URI) 45 | cur = conn.cursor() 46 | sql = "CREATE TABLE users (uid bigint, sudo boolean DEFAULT FALSE);" 47 | cur.execute(sql) 48 | conn.commit() 49 | LOGGER.info("Table Created!") 50 | except Error as e: 51 | LOGGER.error(e) 52 | exit(1) 53 | 54 | try: 55 | if bool(getConfig('_____REMOVE_THIS_LINE_____')): 56 | logging.error('The README.md file there to be read! Exiting now!') 57 | exit() 58 | except KeyError: 59 | pass 60 | 61 | aria2 = aria2p.API( 62 | aria2p.Client( 63 | host="http://localhost", 64 | port=6800, 65 | secret="", 66 | ) 67 | ) 68 | 69 | DOWNLOAD_DIR = None 70 | BOT_TOKEN = None 71 | 72 | download_dict_lock = threading.Lock() 73 | status_reply_dict_lock = threading.Lock() 74 | # Key: update.effective_chat.id 75 | # Value: telegram.Message 76 | status_reply_dict = {} 77 | # Key: update.message.message_id 78 | # Value: An object of Status 79 | download_dict = {} 80 | # Stores list of users and chats the bot is authorized to use in 81 | AUTHORIZED_CHATS = set() 82 | SUDO_USERS = set() 83 | try: 84 | achats = getConfig('AUTHORIZED_CHATS') 85 | achats = achats.split(" ") 86 | for chats in achats: 87 | AUTHORIZED_CHATS.add(int(chats)) 88 | except: 89 | pass 90 | 91 | try: 92 | BOT_TOKEN = getConfig('BOT_TOKEN') 93 | DB_URI = getConfig('DATABASE_URL') 94 | parent_id = getConfig('GDRIVE_FOLDER_ID') 95 | DOWNLOAD_DIR = getConfig('DOWNLOAD_DIR') 96 | if not DOWNLOAD_DIR.endswith("/"): 97 | DOWNLOAD_DIR = DOWNLOAD_DIR + '/' 98 | DOWNLOAD_STATUS_UPDATE_INTERVAL = int(getConfig('DOWNLOAD_STATUS_UPDATE_INTERVAL')) 99 | OWNER_ID = int(getConfig('OWNER_ID')) 100 | AUTO_DELETE_MESSAGE_DURATION = int(getConfig('AUTO_DELETE_MESSAGE_DURATION')) 101 | TELEGRAM_API = getConfig('TELEGRAM_API') 102 | TELEGRAM_HASH = getConfig('TELEGRAM_HASH') 103 | UPSTREAM_REPO = getConfig('UPSTREAM_REPO') 104 | UPSTREAM_BRANCH = getConfig('UPSTREAM_BRANCH') 105 | except KeyError as e: 106 | LOGGER.error("One or more env variables missing! Exiting now") 107 | exit(1) 108 | 109 | try: 110 | conn = psycopg2.connect(DB_URI) 111 | cur = conn.cursor() 112 | sql = "SELECT * from users;" 113 | cur.execute(sql) 114 | rows = cur.fetchall() #returns a list ==> (uid, sudo) 115 | for row in rows: 116 | AUTHORIZED_CHATS.add(row[0]) 117 | if row[1]: 118 | SUDO_USERS.add(row[0]) 119 | except Error as e: 120 | if 'relation "users" does not exist' in str(e): 121 | mktable() 122 | else: 123 | LOGGER.error(e) 124 | exit(1) 125 | finally: 126 | cur.close() 127 | conn.close() 128 | 129 | LOGGER.info("Generating USER_SESSION_STRING") 130 | app = Client(':memory:', api_id=int(TELEGRAM_API), api_hash=TELEGRAM_HASH, bot_token=BOT_TOKEN) 131 | 132 | #Generate Telegraph Token 133 | sname = ''.join(random.SystemRandom().choices(string.ascii_letters, k=8)) 134 | LOGGER.info("Generating TELEGRAPH_TOKEN using '" + sname + "' name") 135 | telegraph = Telegraph() 136 | telegraph.create_account(short_name=sname) 137 | telegraph_token = telegraph.get_access_token() 138 | 139 | try: 140 | MEGA_API_KEY = getConfig('MEGA_API_KEY') 141 | except KeyError: 142 | logging.warning('MEGA API KEY not provided!') 143 | MEGA_API_KEY = None 144 | try: 145 | MEGA_EMAIL_ID = getConfig('MEGA_EMAIL_ID') 146 | MEGA_PASSWORD = getConfig('MEGA_PASSWORD') 147 | if len(MEGA_EMAIL_ID) == 0 or len(MEGA_PASSWORD) == 0: 148 | raise KeyError 149 | except KeyError: 150 | logging.warning('MEGA Credentials not provided!') 151 | MEGA_EMAIL_ID = None 152 | MEGA_PASSWORD = None 153 | try: 154 | HEROKU_API_KEY = getConfig('HEROKU_API_KEY') 155 | except KeyError: 156 | logging.warning('HEROKU API KEY not provided!') 157 | HEROKU_API_KEY = None 158 | try: 159 | HEROKU_APP_NAME = getConfig('HEROKU_APP_NAME') 160 | except KeyError: 161 | logging.warning('HEROKU APP NAME not provided!') 162 | HEROKU_APP_NAME = None 163 | try: 164 | UPTOBOX_TOKEN = getConfig('UPTOBOX_TOKEN') 165 | except KeyError: 166 | logging.info('UPTOBOX_TOKEN not provided!') 167 | UPTOBOX_TOKEN = None 168 | try: 169 | INDEX_URL = getConfig('INDEX_URL') 170 | if len(INDEX_URL) == 0: 171 | INDEX_URL = None 172 | except KeyError: 173 | INDEX_URL = None 174 | try: 175 | TORRENT_DIRECT_LIMIT = getConfig('TORRENT_DIRECT_LIMIT') 176 | if len(TORRENT_DIRECT_LIMIT) == 0: 177 | TORRENT_DIRECT_LIMIT = None 178 | except KeyError: 179 | TORRENT_DIRECT_LIMIT = None 180 | try: 181 | CLONE_LIMIT = getConfig('CLONE_LIMIT') 182 | if len(CLONE_LIMIT) == 0: 183 | CLONE_LIMIT = None 184 | except KeyError: 185 | CLONE_LIMIT = None 186 | try: 187 | MEGA_LIMIT = getConfig('MEGA_LIMIT') 188 | if len(MEGA_LIMIT) == 0: 189 | MEGA_LIMIT = None 190 | except KeyError: 191 | MEGA_LIMIT = None 192 | try: 193 | BUTTON_FOUR_NAME = getConfig('BUTTON_FOUR_NAME') 194 | BUTTON_FOUR_URL = getConfig('BUTTON_FOUR_URL') 195 | if len(BUTTON_FOUR_NAME) == 0 or len(BUTTON_FOUR_URL) == 0: 196 | raise KeyError 197 | except KeyError: 198 | BUTTON_FOUR_NAME = None 199 | BUTTON_FOUR_URL = None 200 | try: 201 | BUTTON_FIVE_NAME = getConfig('BUTTON_FIVE_NAME') 202 | BUTTON_FIVE_URL = getConfig('BUTTON_FIVE_URL') 203 | if len(BUTTON_FIVE_NAME) == 0 or len(BUTTON_FIVE_URL) == 0: 204 | raise KeyError 205 | except KeyError: 206 | BUTTON_FIVE_NAME = None 207 | BUTTON_FIVE_URL = None 208 | try: 209 | BUTTON_SIX_NAME = getConfig('BUTTON_SIX_NAME') 210 | BUTTON_SIX_URL = getConfig('BUTTON_SIX_URL') 211 | if len(BUTTON_SIX_NAME) == 0 or len(BUTTON_SIX_URL) == 0: 212 | raise KeyError 213 | except KeyError: 214 | BUTTON_SIX_NAME = None 215 | BUTTON_SIX_URL = None 216 | try: 217 | STOP_DUPLICATE_MIRROR = getConfig('STOP_DUPLICATE_MIRROR') 218 | if STOP_DUPLICATE_MIRROR.lower() == 'true': 219 | STOP_DUPLICATE_MIRROR = True 220 | else: 221 | STOP_DUPLICATE_MIRROR = False 222 | except KeyError: 223 | STOP_DUPLICATE_MIRROR = False 224 | try: 225 | STOP_DUPLICATE_MEGA = getConfig('STOP_DUPLICATE_MEGA') 226 | if STOP_DUPLICATE_MEGA.lower() == 'true': 227 | STOP_DUPLICATE_MEGA = True 228 | else: 229 | STOP_DUPLICATE_MEGA = False 230 | except KeyError: 231 | STOP_DUPLICATE_MEGA = False 232 | try: 233 | VIEW_LINK = getConfig('VIEW_LINK') 234 | if VIEW_LINK.lower() == 'true': 235 | VIEW_LINK = True 236 | else: 237 | VIEW_LINK = False 238 | except KeyError: 239 | VIEW_LINK = False 240 | try: 241 | STOP_DUPLICATE_CLONE = getConfig('STOP_DUPLICATE_CLONE') 242 | if STOP_DUPLICATE_CLONE.lower() == 'true': 243 | STOP_DUPLICATE_CLONE = True 244 | else: 245 | STOP_DUPLICATE_CLONE = False 246 | except KeyError: 247 | STOP_DUPLICATE_CLONE = False 248 | try: 249 | IS_TEAM_DRIVE = getConfig('IS_TEAM_DRIVE') 250 | if IS_TEAM_DRIVE.lower() == 'true': 251 | IS_TEAM_DRIVE = True 252 | else: 253 | IS_TEAM_DRIVE = False 254 | except KeyError: 255 | IS_TEAM_DRIVE = False 256 | try: 257 | USE_SERVICE_ACCOUNTS = getConfig('USE_SERVICE_ACCOUNTS') 258 | if USE_SERVICE_ACCOUNTS.lower() == 'true': 259 | USE_SERVICE_ACCOUNTS = True 260 | else: 261 | USE_SERVICE_ACCOUNTS = False 262 | except KeyError: 263 | USE_SERVICE_ACCOUNTS = False 264 | try: 265 | BLOCK_MEGA_FOLDER = getConfig('BLOCK_MEGA_FOLDER') 266 | if BLOCK_MEGA_FOLDER.lower() == 'true': 267 | BLOCK_MEGA_FOLDER = True 268 | else: 269 | BLOCK_MEGA_FOLDER = False 270 | except KeyError: 271 | BLOCK_MEGA_FOLDER = False 272 | try: 273 | BLOCK_MEGA_LINKS = getConfig('BLOCK_MEGA_LINKS') 274 | if BLOCK_MEGA_LINKS.lower() == 'true': 275 | BLOCK_MEGA_LINKS = True 276 | else: 277 | BLOCK_MEGA_LINKS = False 278 | except KeyError: 279 | BLOCK_MEGA_LINKS = False 280 | try: 281 | SHORTENER = getConfig('SHORTENER') 282 | SHORTENER_API = getConfig('SHORTENER_API') 283 | if len(SHORTENER) == 0 or len(SHORTENER_API) == 0: 284 | raise KeyError 285 | except KeyError: 286 | SHORTENER = None 287 | SHORTENER_API = None 288 | try: 289 | IMAGE_URL = getConfig('IMAGE_URL') 290 | if len(IMAGE_URL) == 0: 291 | IMAGE_URL = 'https://imagetot.com/images/2021/06/28/e8155fe3d87a1084d7cda5f4ecc5211c.jpg' 292 | except KeyError: 293 | IMAGE_URL = 'https://imagetot.com/images/2021/06/28/e8155fe3d87a1084d7cda5f4ecc5211c.jpg' 294 | 295 | IGNORE_PENDING_REQUESTS = False 296 | try: 297 | if getConfig("IGNORE_PENDING_REQUESTS").lower() == "true": 298 | IGNORE_PENDING_REQUESTS = True 299 | except KeyError: 300 | pass 301 | 302 | updater = tg.Updater(token=BOT_TOKEN) 303 | bot = updater.bot 304 | dispatcher = updater.dispatcher 305 | -------------------------------------------------------------------------------- /bot/modules/config.py: -------------------------------------------------------------------------------- 1 | # Implement By https://github.com/jusidama18 2 | # Based on this https://github.com/DevsExpo/FridayUserbot/blob/master/plugins/heroku_helpers.py 3 | 4 | from pyrogram import filters, types, emoji 5 | from bot.helper.telegram_helper.bot_commands import BotCommands 6 | from bot import app, OWNER_ID 7 | from bot.helper import get_text, check_heroku 8 | from bot import * 9 | 10 | # Add Variable 11 | 12 | @app.on_message(filters.command('setvar') & filters.user(OWNER_ID)) 13 | @check_heroku 14 | async def set_varr(client, message, app_): 15 | msg_ = await message.reply_text("`Please Wait!`") 16 | heroku_var = app_.config() 17 | _var = get_text(message) 18 | if not _var: 19 | await msg_.edit("`Here is Usage Syntax: /setvar KEY VALUE`", parse_mode="markdown") 20 | return 21 | if not " " in _var: 22 | await msg_.edit("`Variable VALUE needed !`", parse_mode="markdown") 23 | return 24 | var_ = _var.split(" ", 1) 25 | if len(var_) > 2: 26 | await msg_.edit("`Here is Usage Syntax: /setvar KEY VALUE`", parse_mode="markdown") 27 | return 28 | _varname, _varvalue = var_ 29 | await msg_.edit(f"`Variable {_varname} Added With Value {_varvalue}!`") 30 | heroku_var[_varname] = _varvalue 31 | 32 | # Delete Variable 33 | 34 | @app.on_message(filters.command('delvar') & filters.user(OWNER_ID)) 35 | @check_heroku 36 | async def del_varr(client, message, app_): 37 | msg_ = await message.reply_text("`Please Wait!`", parse_mode="markdown") 38 | heroku_var = app_.config() 39 | _var = get_text(message) 40 | if not _var: 41 | await msg_.edit("`Give Var Name As Input!`", parse_mode="markdown") 42 | return 43 | if not _var in heroku_var: 44 | await msg_.edit("`This Var Doesn't Exists!`", parse_mode="markdown") 45 | return 46 | await msg_.edit(f"`Sucessfully Deleted {_var} Var!`", parse_mode="markdown") 47 | del heroku_var[_var] 48 | 49 | # CONFIG LIST # 50 | 51 | __header__='📕 **Page** **{}**\n\n' 52 | 53 | @app.on_message(filters.command(BotCommands.ConfigMenuCommand) & filters.user(OWNER_ID)) 54 | async def config_menu(_, message): 55 | await message.reply( 56 | f"**Hello {message.from_user.mention}**,\n\n**If you want to add or set Variable in Heroku use** `/setvar`\n\n**If you want to delete Variable in Heroku use `/delvar`**\n\n**WARNING! Very Recommended to do this command in private since it's contain bot info.**\n\n**Here's This is Slam-MirrorBot Current Configs**", 57 | reply_markup=types.InlineKeyboardMarkup( 58 | [[types.InlineKeyboardButton(f"{emoji.CROSS_MARK}", callback_data='docs_end'), types.InlineKeyboardButton(f"BOT CONFIG", callback_data='docs_1')]] 59 | ) 60 | ) 61 | 62 | @app.on_callback_query(filters.regex('^docs_') & filters.user(OWNER_ID)) 63 | async def config_button(_, query): 64 | data = query.data.split('_')[1] 65 | if data == '1': 66 | return await query.message.edit( 67 | __header__.format(data) 68 | + f"**[ Telegram Config ]**\n\n**Bot Token:** `{BOT_TOKEN}`\n\n**Telegram API:** `{TELEGRAM_API}`\n\n**Telegram HASH:** `{TELEGRAM_HASH}`\n\n**Telegraph Token:** `{telegraph_token}`", 69 | reply_markup=types.InlineKeyboardMarkup( 70 | [ 71 | [ 72 | types.InlineKeyboardButton(f"{emoji.LEFT_ARROW}", callback_data='docs_9'), 73 | types.InlineKeyboardButton(f"{emoji.CROSS_MARK}", callback_data='docs_end'), 74 | types.InlineKeyboardButton(f"{emoji.RIGHT_ARROW}", callback_data='docs_2') 75 | ] 76 | ] 77 | ) 78 | ) 79 | elif data == '2': 80 | return await query.message.edit( 81 | __header__.format(data) 82 | + f"**[ Drive and Index Config ]**\n**Drive Folder:** `{parent_id}`\n\n**Using Team Drive:** `{IS_TEAM_DRIVE}`\n\n**Using Service Account:** `{USE_SERVICE_ACCOUNTS}`\n\n**Index Url:** `{INDEX_URL}`", 83 | reply_markup=types.InlineKeyboardMarkup( 84 | [ 85 | [ 86 | types.InlineKeyboardButton(f"{emoji.LEFT_ARROW}", callback_data='docs_1'), 87 | types.InlineKeyboardButton(f"{emoji.CROSS_MARK}", callback_data='docs_end'), 88 | types.InlineKeyboardButton(f"{emoji.RIGHT_ARROW}", callback_data='docs_3') 89 | ] 90 | ] 91 | ) 92 | ) 93 | elif data == '3': 94 | return await query.message.edit( 95 | __header__.format(data) 96 | + f"**[ Mega and Uptobox Config ]**\n\n**Mega API:** `{MEGA_API_KEY}`\n\n**Mega Email:** `{MEGA_EMAIL_ID}`\n\n**Mega Password:** `{MEGA_PASSWORD}`\n\n**Uptobox Token:** `{UPTOBOX_TOKEN}`", 97 | reply_markup=types.InlineKeyboardMarkup( 98 | [ 99 | [ 100 | types.InlineKeyboardButton(f"{emoji.LEFT_ARROW}", callback_data='docs_2'), 101 | types.InlineKeyboardButton(f"{emoji.CROSS_MARK}", callback_data='docs_end'), 102 | types.InlineKeyboardButton(f"{emoji.RIGHT_ARROW}", callback_data='docs_4') 103 | ] 104 | ] 105 | ) 106 | ) 107 | elif data == '4': 108 | return await query.message.edit( 109 | __header__.format(data) 110 | + f"**[ Stop Duplicate Config ]**\n\n**Mirror:** `{STOP_DUPLICATE_MIRROR}`\n\n**Clone:** `{STOP_DUPLICATE_CLONE}`\n\n**Mega:** `{STOP_DUPLICATE_MEGA}`\n\n**[ Block Mega Config ]**\n\n**Folder:** `{BLOCK_MEGA_FOLDER}`\n\n**Link:** `{BLOCK_MEGA_LINKS}`\n\n", 111 | reply_markup=types.InlineKeyboardMarkup( 112 | [ 113 | [ 114 | types.InlineKeyboardButton(f"{emoji.LEFT_ARROW}", callback_data='docs_3'), 115 | types.InlineKeyboardButton(f"{emoji.CROSS_MARK}", callback_data='docs_end'), 116 | types.InlineKeyboardButton(f"{emoji.RIGHT_ARROW}", callback_data='docs_5') 117 | ] 118 | ] 119 | ) 120 | ) 121 | elif data == '5': 122 | return await query.message.edit( 123 | __header__.format(data) 124 | + f"**[ Limit Size Config ]**\n\n**Torrent and Direct:** `{TORRENT_DIRECT_LIMIT}`\n\n**Clone:** `{CLONE_LIMIT}`\n\n**Mega:** `{MEGA_LIMIT}`", 125 | reply_markup=types.InlineKeyboardMarkup( 126 | [ 127 | [ 128 | types.InlineKeyboardButton(f"{emoji.LEFT_ARROW}", callback_data='docs_4'), 129 | types.InlineKeyboardButton(f"{emoji.CROSS_MARK}", callback_data='docs_end'), 130 | types.InlineKeyboardButton(f"{emoji.RIGHT_ARROW}", callback_data='docs_6') 131 | ] 132 | ] 133 | ) 134 | ) 135 | elif data == '6': 136 | user = sudo = '' 137 | user += '\n'.join(str(id) for id in AUTHORIZED_CHATS) 138 | sudo += '\n'.join(str(id) for id in SUDO_USERS) 139 | return await query.message.edit( 140 | __header__.format(data) 141 | + f"**[ User ID Config ]**\n\n**Owner ID:** `{OWNER_ID}`\n\n**Authorized Chat:**\n`{user}`\n\n**Sudo Users:**\n`{sudo}`", 142 | reply_markup=types.InlineKeyboardMarkup( 143 | [ 144 | [ 145 | types.InlineKeyboardButton(f"{emoji.LEFT_ARROW}", callback_data='docs_5'), 146 | types.InlineKeyboardButton(f"{emoji.CROSS_MARK}", callback_data='docs_end'), 147 | types.InlineKeyboardButton(f"{emoji.RIGHT_ARROW}", callback_data='docs_7') 148 | ] 149 | ] 150 | ) 151 | ) 152 | elif data == '7': 153 | return await query.message.edit( 154 | __header__.format(data) 155 | + f"**[ Button Config ]**\n\n**Button Four Name:** `{BUTTON_FOUR_NAME}`\n\n**Button Four Url:** `{BUTTON_FOUR_URL}`\n\n**Button Five Name:** `{BUTTON_FIVE_NAME}`\n\n**Button Five Url:** `{BUTTON_FIVE_URL}`\n\n**Button Six Name:** `{BUTTON_SIX_NAME}`\n\n**Button Six Url:** `{BUTTON_SIX_URL}`", 156 | reply_markup=types.InlineKeyboardMarkup( 157 | [ 158 | [ 159 | types.InlineKeyboardButton(f"{emoji.LEFT_ARROW}", callback_data='docs_6'), 160 | types.InlineKeyboardButton(f"{emoji.CROSS_MARK}", callback_data='docs_end'), 161 | types.InlineKeyboardButton(f"{emoji.RIGHT_ARROW}", callback_data='docs_8') 162 | ] 163 | ] 164 | ) 165 | ) 166 | elif data == '8': 167 | return await query.message.edit( 168 | __header__.format(data) 169 | + f"**[ Heroku Config ]**\n\n**Heroku Name:** `{HEROKU_APP_NAME}`\n\n**Heroku API:** `{HEROKU_API_KEY}`\n\n**[ Shortener Config ]**\n\n**Shortener Name:** `{SHORTENER}`\n\n**Shortener API:** `{SHORTENER_API}`", 170 | reply_markup=types.InlineKeyboardMarkup( 171 | [ 172 | [ 173 | types.InlineKeyboardButton(f"{emoji.LEFT_ARROW}", callback_data='docs_7'), 174 | types.InlineKeyboardButton(f"{emoji.CROSS_MARK}", callback_data='docs_end'), 175 | types.InlineKeyboardButton(f"{emoji.RIGHT_ARROW}", callback_data='docs_9') 176 | ] 177 | ] 178 | ) 179 | ) 180 | elif data == '9': 181 | return await query.message.edit( 182 | __header__.format(data) 183 | + f" **[ Others Config ]**\n\n**Ignore Pending Request:** `{IGNORE_PENDING_REQUESTS}`\n\n**Image Url:** `{IMAGE_URL}`\n\n**Directory:** `{DOWNLOAD_DIR}`\n\n**Status Interval:** `{DOWNLOAD_STATUS_UPDATE_INTERVAL}`\n\n**View Link:** `{VIEW_LINK}`\n\n**Database Url:** `{DB_URI}`\n\n**Delete Message Duration:** `{AUTO_DELETE_MESSAGE_DURATION}`", 184 | reply_markup=types.InlineKeyboardMarkup( 185 | [ 186 | [ 187 | types.InlineKeyboardButton(f"{emoji.LEFT_ARROW}", callback_data='docs_8'), 188 | types.InlineKeyboardButton(f"{emoji.CROSS_MARK}", callback_data='docs_end'), 189 | types.InlineKeyboardButton(f"{emoji.RIGHT_ARROW}", callback_data='docs_1') 190 | ] 191 | ] 192 | ) 193 | ) 194 | elif data == 'end': 195 | return await query.message.delete() 196 | -------------------------------------------------------------------------------- /bot/__main__.py: -------------------------------------------------------------------------------- 1 | import shutil, psutil 2 | import signal 3 | import os 4 | 5 | from pyrogram import idle 6 | from bot import app 7 | from sys import executable 8 | from datetime import datetime 9 | import pytz 10 | import time 11 | 12 | from telegram import ParseMode, BotCommand 13 | from telegram.ext import CommandHandler 14 | from bot import bot, dispatcher, updater, botStartTime, IMAGE_URL, IGNORE_PENDING_REQUESTS 15 | from bot.helper.ext_utils import fs_utils 16 | from bot.helper.telegram_helper.bot_commands import BotCommands 17 | from bot.helper.telegram_helper.message_utils import * 18 | from .helper.ext_utils.bot_utils import get_readable_file_size, get_readable_time 19 | from .helper.telegram_helper.filters import CustomFilters 20 | from bot.helper.telegram_helper import button_build 21 | from .modules import authorize, list, cancel_mirror, mirror_status, mirror, clone, watch, shell, eval, search, delete, speedtest, usage, mediainfo, count, config, updates 22 | 23 | now=datetime.now(pytz.timezone('Asia/Jakarta')) 24 | 25 | 26 | def stats(update, context): 27 | currentTime = get_readable_time(time.time() - botStartTime) 28 | current = now.strftime('%Y/%m/%d %I:%M:%S %p') 29 | total, used, free = shutil.disk_usage('.') 30 | total = get_readable_file_size(total) 31 | used = get_readable_file_size(used) 32 | free = get_readable_file_size(free) 33 | sent = get_readable_file_size(psutil.net_io_counters().bytes_sent) 34 | recv = get_readable_file_size(psutil.net_io_counters().bytes_recv) 35 | cpuUsage = psutil.cpu_percent(interval=0.5) 36 | memory = psutil.virtual_memory().percent 37 | disk = psutil.disk_usage('/').percent 38 | stats = f'Bot Uptime: {currentTime}\n' \ 39 | f'Start Time: {current}\n' \ 40 | f'Total Disk Space: {total}\n' \ 41 | f'Used: {used} ' \ 42 | f'Free: {free}\n\n' \ 43 | f'📊Data Usage📊\nUpload: {sent}\n' \ 44 | f'Download: {recv}\n\n' \ 45 | f'CPU: {cpuUsage}%\n' \ 46 | f'RAM: {memory}%\n' \ 47 | f'DISK: {disk}%' 48 | update.effective_message.reply_photo(IMAGE_URL, stats, parse_mode=ParseMode.HTML) 49 | 50 | 51 | def start(update, context): 52 | start_string = f''' 53 | This bot can mirror all your links to Google Drive! 54 | Type /{BotCommands.HelpCommand} to get a list of available commands 55 | ''' 56 | buttons = button_build.ButtonMaker() 57 | buttons.buildbutton("Support Group", "https://telegram.dog/cloudsmirror") 58 | reply_markup = InlineKeyboardMarkup(buttons.build_menu(2)) 59 | LOGGER.info('UID: {} - UN: {} - MSG: {}'.format(update.message.chat.id, update.message.chat.username, update.message.text)) 60 | uptime = get_readable_time((time.time() - botStartTime)) 61 | if CustomFilters.authorized_user(update) or CustomFilters.authorized_chat(update): 62 | if update.message.chat.type == "private" : 63 | sendMessage(f"Hey I'm Alive 🙂\nSince: {uptime}", context.bot, update) 64 | else : 65 | update.effective_message.reply_photo(IMAGE_URL, start_string, parse_mode=ParseMode.MARKDOWN, reply_markup=reply_markup) 66 | else : 67 | sendMessage(f"Oops! not a Authorized user.", context.bot, update) 68 | 69 | 70 | def restart(update, context): 71 | restart_message = sendMessage("Restarting, Please wait!", context.bot, update) 72 | # Save restart message object in order to reply to it after restarting 73 | with open(".restartmsg", "w") as f: 74 | f.truncate(0) 75 | f.write(f"{restart_message.chat.id}\n{restart_message.message_id}\n") 76 | fs_utils.clean_all() 77 | os.execl(executable, executable, "-m", "bot") 78 | 79 | 80 | def ping(update, context): 81 | start_time = int(round(time.time() * 1000)) 82 | reply = sendMessage("Starting Ping", context.bot, update) 83 | end_time = int(round(time.time() * 1000)) 84 | editMessage(f'{end_time - start_time} ms', reply) 85 | 86 | 87 | def log(update, context): 88 | sendLogFile(context.bot, update) 89 | 90 | 91 | def bot_help(update, context): 92 | help_string_adm = f''' 93 | /{BotCommands.HelpCommand}: To get this message 94 | 95 | /{BotCommands.MirrorCommand} [download_url][magnet_link]: Start mirroring the link to Google Drive. 96 | 97 | /{BotCommands.UnzipMirrorCommand} [download_url][magnet_link]: Starts mirroring and if downloaded file is any archive, extracts it to Google Drive 98 | 99 | /{BotCommands.TarMirrorCommand} [download_url][magnet_link]: Start mirroring and upload the archived (.tar) version of the download 100 | 101 | /{BotCommands.CloneCommand}: Copy file/folder to Google Drive 102 | 103 | /{BotCommands.CountCommand}: Count file/folder of Google Drive Links 104 | 105 | /{BotCommands.DeleteCommand} [link]: Delete file from Google Drive (Only Owner & Sudo) 106 | 107 | /{BotCommands.WatchCommand} [youtube-dl supported link]: Mirror through youtube-dl. Click /{BotCommands.WatchCommand} for more help. 108 | 109 | /{BotCommands.TarWatchCommand} [youtube-dl supported link]: Mirror through youtube-dl and tar before uploading 110 | 111 | /{BotCommands.CancelMirror}: Reply to the message by which the download was initiated and that download will be cancelled 112 | 113 | /{BotCommands.StatusCommand}: Shows a status of all the downloads 114 | 115 | /{BotCommands.ListCommand} [search term]: Searches the search term in the Google Drive, if found replies with the link 116 | 117 | /{BotCommands.StatsCommand}: Show Stats of the machine the bot is hosted on 118 | 119 | /{BotCommands.AuthorizeCommand}: Authorize a chat or a user to use the bot (Can only be invoked by Owner & Sudo of the bot) 120 | 121 | /{BotCommands.UnAuthorizeCommand}: Unauthorize a chat or a user to use the bot (Can only be invoked by Owner & Sudo of the bot) 122 | 123 | /{BotCommands.AuthorizedUsersCommand}: Show authorized users (Only Owner & Sudo) 124 | 125 | /{BotCommands.AddSudoCommand}: Add sudo user (Only Owner) 126 | 127 | /{BotCommands.RmSudoCommand}: Remove sudo users (Only Owner) 128 | 129 | /{BotCommands.LogCommand}: Get a log file of the bot. Handy for getting crash reports 130 | 131 | /{BotCommands.ConfigMenuCommand}: Get Info Menu about bot config (Owner Only). 132 | 133 | /{BotCommands.UpdateCommand}: Update Bot from Upstream Repo. (Owner Only). 134 | 135 | /{BotCommands.UsageCommand}: To see Heroku Dyno Stats (Owner & Sudo only). 136 | 137 | /{BotCommands.SpeedCommand}: Check Internet Speed of the Host 138 | 139 | /{BotCommands.MediaInfoCommand}: Get detailed info about replied media (Only for Telegram file). 140 | 141 | /{BotCommands.ShellCommand}: Run commands in Shell (Terminal). 142 | 143 | /tshelp: Get help for Torrent search module. 144 | ''' 145 | 146 | help_string = f''' 147 | /{BotCommands.HelpCommand}: To get this message 148 | 149 | /{BotCommands.MirrorCommand} [download_url][magnet_link]: Start mirroring the link to Google Drive. 150 | 151 | /{BotCommands.UnzipMirrorCommand} [download_url][magnet_link]: Starts mirroring and if downloaded file is any archive, extracts it to Google Drive 152 | 153 | /{BotCommands.TarMirrorCommand} [download_url][magnet_link]: Start mirroring and upload the archived (.tar) version of the download 154 | 155 | /{BotCommands.CloneCommand}: Copy file/folder to Google Drive 156 | 157 | /{BotCommands.CountCommand}: Count file/folder of Google Drive Links 158 | 159 | /{BotCommands.WatchCommand} [youtube-dl supported link]: Mirror through youtube-dl. Click /{BotCommands.WatchCommand} for more help. 160 | 161 | /{BotCommands.TarWatchCommand} [youtube-dl supported link]: Mirror through youtube-dl and tar before uploading 162 | 163 | /{BotCommands.CancelMirror}: Reply to the message by which the download was initiated and that download will be cancelled 164 | 165 | /{BotCommands.StatusCommand}: Shows a status of all the downloads 166 | 167 | /{BotCommands.ListCommand} [search term]: Searches the search term in the Google Drive, if found replies with the link 168 | 169 | /{BotCommands.StatsCommand}: Show Stats of the machine the bot is hosted on 170 | 171 | /{BotCommands.SpeedCommand}: Check Internet Speed of the Host 172 | 173 | /{BotCommands.MediaInfoCommand}: Get detailed info about replied media (Only for Telegram file). 174 | 175 | /tshelp: Get help for Torrent search module. 176 | ''' 177 | 178 | if CustomFilters.sudo_user(update) or CustomFilters.owner_filter(update): 179 | sendMessage(help_string_adm, context.bot, update) 180 | else: 181 | sendMessage(help_string, context.bot, update) 182 | 183 | 184 | botcmds = [ 185 | BotCommand(f'{BotCommands.MirrorCommand}', 'Start Mirroring'), 186 | BotCommand(f'{BotCommands.TarMirrorCommand}','Upload tar (zipped) file'), 187 | BotCommand(f'{BotCommands.UnzipMirrorCommand}','Extract files'), 188 | BotCommand(f'{BotCommands.CloneCommand}','Copy file/folder to Drive'), 189 | BotCommand(f'{BotCommands.CountCommand}','Count file/folder of Drive link'), 190 | BotCommand(f'{BotCommands.WatchCommand}','Mirror YT-DL support link'), 191 | BotCommand(f'{BotCommands.TarWatchCommand}','Mirror Youtube playlist link as tar'), 192 | BotCommand(f'{BotCommands.CancelMirror}','Cancel a task'), 193 | BotCommand(f'{BotCommands.CancelAllCommand}','Cancel all tasks'), 194 | BotCommand(f'{BotCommands.DeleteCommand}','Delete file from Drive'), 195 | BotCommand(f'{BotCommands.ListCommand}',' [query] Searches files in Drive'), 196 | BotCommand(f'{BotCommands.StatusCommand}','Get Mirror Status message'), 197 | BotCommand(f'{BotCommands.StatsCommand}','Bot Usage Stats'), 198 | BotCommand(f'{BotCommands.HelpCommand}','Get Detailed Help'), 199 | BotCommand(f'{BotCommands.MediaInfoCommand}','Get detailed info about replied media'), 200 | BotCommand(f'{BotCommands.SpeedCommand}','Check Speed of the host'), 201 | BotCommand(f'{BotCommands.LogCommand}','Bot Log [owner/sudo only]'), 202 | BotCommand(f'{BotCommands.RestartCommand}','Restart bot [owner/sudo only]')] 203 | 204 | 205 | def main(): 206 | fs_utils.start_cleanup() 207 | # Check if the bot is restarting 208 | if os.path.isfile(".restartmsg"): 209 | with open(".restartmsg") as f: 210 | chat_id, msg_id = map(int, f) 211 | bot.edit_message_text("Restarted successfully!", chat_id, msg_id) 212 | os.remove(".restartmsg") 213 | bot.set_my_commands(botcmds) 214 | 215 | start_handler = CommandHandler(BotCommands.StartCommand, start, run_async=True) 216 | ping_handler = CommandHandler(BotCommands.PingCommand, ping, 217 | filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 218 | restart_handler = CommandHandler(BotCommands.RestartCommand, restart, 219 | filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True) 220 | help_handler = CommandHandler(BotCommands.HelpCommand, 221 | bot_help, filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 222 | stats_handler = CommandHandler(BotCommands.StatsCommand, 223 | stats, filters=CustomFilters.authorized_chat | CustomFilters.authorized_user, run_async=True) 224 | log_handler = CommandHandler(BotCommands.LogCommand, log, filters=CustomFilters.owner_filter | CustomFilters.sudo_user, run_async=True) 225 | dispatcher.add_handler(start_handler) 226 | dispatcher.add_handler(ping_handler) 227 | dispatcher.add_handler(restart_handler) 228 | dispatcher.add_handler(help_handler) 229 | dispatcher.add_handler(stats_handler) 230 | dispatcher.add_handler(log_handler) 231 | updater.start_polling(drop_pending_updates=IGNORE_PENDING_REQUESTS) 232 | LOGGER.info("Bot Started!") 233 | signal.signal(signal.SIGINT, fs_utils.exit_clean_up) 234 | 235 | app.start() 236 | main() 237 | idle() 238 | -------------------------------------------------------------------------------- /bot/helper/mirror_utils/download_utils/direct_link_generator.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2019 The Raphielscape Company LLC. 2 | # 3 | # Licensed under the Raphielscape Public License, Version 1.c (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # 6 | """ Helper Module containing various sites direct links generators. This module is copied and modified as per need 7 | from https://github.com/AvinashReddy3108/PaperplaneExtended . I hereby take no credit of the following code other 8 | than the modifications. See https://github.com/AvinashReddy3108/PaperplaneExtended/commits/master/userbot/modules/direct_links.py 9 | for original authorship. """ 10 | 11 | from bot import UPTOBOX_TOKEN 12 | import logging 13 | import json 14 | import math 15 | import re 16 | import urllib.parse 17 | from os import popen 18 | from random import choice 19 | from urllib.parse import urlparse 20 | 21 | import lk21 22 | import requests 23 | from bs4 import BeautifulSoup 24 | from js2py import EvalJs 25 | from lk21.extractors.bypasser import Bypass 26 | from base64 import standard_b64encode 27 | from bot.helper.telegram_helper.bot_commands import BotCommands 28 | from bot.helper.ext_utils.exceptions import DirectDownloadLinkException 29 | 30 | 31 | def direct_link_generator(link: str): 32 | """ direct links generator """ 33 | if not link: 34 | raise DirectDownloadLinkException("`No links found!`") 35 | elif 'youtube.com' in link or 'youtu.be' in link: 36 | raise DirectDownloadLinkException(f"Youtube Link use /{BotCommands.WatchCommand} or /{BotCommands.TarWatchCommand}") 37 | elif 'zippyshare.com' in link: 38 | return zippy_share(link) 39 | elif 'yadi.sk' in link: 40 | return yandex_disk(link) 41 | elif 'cloud.mail.ru' in link: 42 | return cm_ru(link) 43 | elif 'mediafire.com' in link: 44 | return mediafire(link) 45 | elif 'uptobox.com' in link: 46 | return uptobox(link) 47 | elif 'osdn.net' in link: 48 | return osdn(link) 49 | elif 'github.com' in link: 50 | return github(link) 51 | elif 'hxfile.co' in link: 52 | return hxfile(link) 53 | elif 'anonfiles.com' in link: 54 | return anon(link) 55 | elif 'letsupload.io' in link: 56 | return letsupload(link) 57 | elif 'fembed.com' in link: 58 | return fembed(link) 59 | elif 'femax20.com' in link: 60 | return fembed(link) 61 | elif 'feurl.com' in link: 62 | return fembed(link) 63 | elif 'naniplay.nanime.in' in link: 64 | return fembed(link) 65 | elif 'naniplay.nanime.biz' in link: 66 | return fembed(link) 67 | elif 'naniplay.com' in link: 68 | return fembed(link) 69 | elif 'layarkacaxxi.icu' in link: 70 | return fembed(link) 71 | elif 'sbembed.com' in link: 72 | return sbembed(link) 73 | elif 'streamsb.net' in link: 74 | return sbembed(link) 75 | elif '1drv.ms' in link: 76 | return onedrive(link) 77 | elif 'pixeldrain.com' in link: 78 | return pixeldrain(link) 79 | else: 80 | raise DirectDownloadLinkException(f'No Direct link function found for {link}') 81 | 82 | 83 | def zippy_share(url: str) -> str: 84 | """ ZippyShare direct links generator 85 | Based on https://github.com/KenHV/Mirror-Bot """ 86 | link = re.findall("https:/.(.*?).zippyshare", url)[0] 87 | response_content = (requests.get(url)).content 88 | bs_obj = BeautifulSoup(response_content, "lxml") 89 | 90 | try: 91 | js_script = bs_obj.find("div", {"class": "center",}).find_all( 92 | "script" 93 | )[1] 94 | except: 95 | js_script = bs_obj.find("div", {"class": "right",}).find_all( 96 | "script" 97 | )[0] 98 | 99 | js_content = re.findall(r'\.href.=."/(.*?)";', str(js_script)) 100 | js_content = 'var x = "/' + js_content[0] + '"' 101 | 102 | evaljs = EvalJs() 103 | setattr(evaljs, "x", None) 104 | evaljs.execute(js_content) 105 | js_content = getattr(evaljs, "x") 106 | 107 | return f"https://{link}.zippyshare.com{js_content}" 108 | 109 | 110 | def yandex_disk(url: str) -> str: 111 | """ Yandex.Disk direct links generator 112 | Based on https://github.com/wldhx/yadisk-direct """ 113 | try: 114 | link = re.findall(r'\bhttps?://.*yadi\.sk\S+', url)[0] 115 | except IndexError: 116 | reply = "`No Yandex.Disk links found`\n" 117 | return reply 118 | api = 'https://cloud-api.yandex.net/v1/disk/public/resources/download?public_key={}' 119 | try: 120 | dl_url = requests.get(api.format(link)).json()['href'] 121 | return dl_url 122 | except KeyError: 123 | raise DirectDownloadLinkException("`Error: File not found/Download limit reached`\n") 124 | 125 | 126 | def cm_ru(url: str) -> str: 127 | """ cloud.mail.ru direct links generator 128 | Using https://github.com/JrMasterModelBuilder/cmrudl.py """ 129 | reply = '' 130 | try: 131 | link = re.findall(r'\bhttps?://.*cloud\.mail\.ru\S+', url)[0] 132 | except IndexError: 133 | raise DirectDownloadLinkException("`No cloud.mail.ru links found`\n") 134 | command = f'vendor/cmrudl.py/cmrudl -s {link}' 135 | result = popen(command).read() 136 | result = result.splitlines()[-1] 137 | try: 138 | data = json.loads(result) 139 | except json.decoder.JSONDecodeError: 140 | raise DirectDownloadLinkException("`Error: Can't extract the link`\n") 141 | dl_url = data['download'] 142 | return dl_url 143 | 144 | 145 | def uptobox(url: str) -> str: 146 | """ Uptobox direct links generator 147 | based on https://github.com/jovanzers/WinTenCermin """ 148 | try: 149 | link = re.findall(r'\bhttps?://.*uptobox\.com\S+', url)[0] 150 | except IndexError: 151 | raise DirectDownloadLinkException("`No Uptobox links found`\n") 152 | if UPTOBOX_TOKEN is None: 153 | logging.error('UPTOBOX_TOKEN not provided!') 154 | dl_url = url 155 | else: 156 | try: 157 | link = re.findall(r'\bhttp?://.*uptobox\.com/dl\S+', url)[0] 158 | logging.info('Uptobox direct link') 159 | dl_url = url 160 | except: 161 | file_id = re.findall(r'\bhttps?://.*uptobox\.com/(\w+)', url)[0] 162 | file_link = 'https://uptobox.com/api/link?token=%s&file_code=%s' % (UPTOBOX_TOKEN, file_id) 163 | req = requests.get(file_link) 164 | result = req.json() 165 | dl_url = result['data']['dlLink'] 166 | return dl_url 167 | 168 | 169 | def mediafire(url: str) -> str: 170 | """ MediaFire direct links generator """ 171 | try: 172 | link = re.findall(r'\bhttps?://.*mediafire\.com\S+', url)[0] 173 | except IndexError: 174 | raise DirectDownloadLinkException("`No MediaFire links found`\n") 175 | page = BeautifulSoup(requests.get(link).content, 'lxml') 176 | info = page.find('a', {'aria-label': 'Download file'}) 177 | dl_url = info.get('href') 178 | return dl_url 179 | 180 | 181 | def osdn(url: str) -> str: 182 | """ OSDN direct links generator """ 183 | osdn_link = 'https://osdn.net' 184 | try: 185 | link = re.findall(r'\bhttps?://.*osdn\.net\S+', url)[0] 186 | except IndexError: 187 | raise DirectDownloadLinkException("`No OSDN links found`\n") 188 | page = BeautifulSoup( 189 | requests.get(link, allow_redirects=True).content, 'lxml') 190 | info = page.find('a', {'class': 'mirror_link'}) 191 | link = urllib.parse.unquote(osdn_link + info['href']) 192 | mirrors = page.find('form', {'id': 'mirror-select-form'}).findAll('tr') 193 | urls = [] 194 | for data in mirrors[1:]: 195 | mirror = data.find('input')['value'] 196 | urls.append(re.sub(r'm=(.*)&f', f'm={mirror}&f', link)) 197 | return urls[0] 198 | 199 | 200 | def github(url: str) -> str: 201 | """ GitHub direct links generator """ 202 | try: 203 | re.findall(r'\bhttps?://.*github\.com.*releases\S+', url)[0] 204 | except IndexError: 205 | raise DirectDownloadLinkException("`No GitHub Releases links found`\n") 206 | download = requests.get(url, stream=True, allow_redirects=False) 207 | try: 208 | dl_url = download.headers["location"] 209 | return dl_url 210 | except KeyError: 211 | raise DirectDownloadLinkException("`Error: Can't extract the link`\n") 212 | 213 | 214 | def hxfile(url: str) -> str: 215 | """ Hxfile direct links generator 216 | based on https://github.com/breakdowns/slam-mirrorbot """ 217 | dl_url = '' 218 | try: 219 | link = re.findall(r'\bhttps?://.*hxfile\.co\S+', url)[0] 220 | except IndexError: 221 | raise DirectDownloadLinkException("`No Hxfile links found`\n") 222 | bypasser = lk21.Bypass() 223 | dl_url=bypasser.bypass_url(link) 224 | return dl_url 225 | 226 | 227 | def anon(url: str) -> str: 228 | """ Anonfiles direct links generator 229 | based on https://github.com/breakdowns/slam-mirrorbot """ 230 | dl_url = '' 231 | try: 232 | link = re.findall(r'\bhttps?://.*anonfiles\.com\S+', url)[0] 233 | except IndexError: 234 | raise DirectDownloadLinkException("`No Anonfiles links found`\n") 235 | bypasser = lk21.Bypass() 236 | dl_url=bypasser.bypass_url(link) 237 | return dl_url 238 | 239 | 240 | def letsupload(url: str) -> str: 241 | """ Letsupload direct link generator 242 | Based on https://github.com/breakdowns/slam-mirrorbot """ 243 | dl_url = '' 244 | try: 245 | link = re.findall(r'\bhttps?://.*letsupload\.io\S+', url)[0] 246 | except IndexError: 247 | raise DirectDownloadLinkException("`No Letsupload links found`\n") 248 | bypasser = lk21.Bypass() 249 | dl_url=bypasser.bypass_url(link) 250 | return dl_url 251 | 252 | 253 | def fembed(link: str) -> str: 254 | """ Fembed direct link generator 255 | Based on https://github.com/breakdowns/slam-mirrorbot """ 256 | bypasser = lk21.Bypass() 257 | dl_url=bypasser.bypass_fembed(link) 258 | lst_link = [] 259 | count = len(dl_url) 260 | for i in dl_url: 261 | lst_link.append(dl_url[i]) 262 | return lst_link[count-1] 263 | 264 | 265 | def sbembed(link: str) -> str: 266 | """ Sbembed direct link generator 267 | Based on https://github.com/breakdowns/slam-mirrorbot """ 268 | bypasser = lk21.Bypass() 269 | dl_url=bypasser.bypass_sbembed(link) 270 | lst_link = [] 271 | count = len(dl_url) 272 | for i in dl_url: 273 | lst_link.append(dl_url[i]) 274 | return lst_link[count-1] 275 | 276 | 277 | def onedrive(link: str) -> str: 278 | """ Onedrive direct link generator 279 | Based on https://github.com/UsergeTeam/Userge """ 280 | link_without_query = urlparse(link)._replace(query=None).geturl() 281 | direct_link_encoded = str(standard_b64encode(bytes(link_without_query, "utf-8")), "utf-8") 282 | direct_link1 = f"https://api.onedrive.com/v1.0/shares/u!{direct_link_encoded}/root/content" 283 | resp = requests.head(direct_link1) 284 | if resp.status_code != 302: 285 | return "`Error: Unauthorized link, the link may be private`" 286 | dl_link = resp.next.url 287 | file_name = dl_link.rsplit("/", 1)[1] 288 | resp2 = requests.head(dl_link) 289 | return dl_link 290 | 291 | 292 | def pixeldrain(url: str) -> str: 293 | """ Based on https://github.com/yash-dk/TorToolkit-Telegram """ 294 | url = url.strip("/ ") 295 | file_id = url.split("/")[-1] 296 | info_link = f"https://pixeldrain.com/api/file/{file_id}/info" 297 | dl_link = f"https://pixeldrain.com/api/file/{file_id}" 298 | resp = requests.get(info_link).json() 299 | if resp["success"]: 300 | return dl_link 301 | else: 302 | raise DirectDownloadLinkException("ERROR: Cant't download due {}.".format(resp.text["value"])) 303 | 304 | 305 | def useragent(): 306 | """ 307 | useragent random setter 308 | """ 309 | useragents = BeautifulSoup( 310 | requests.get( 311 | 'https://developers.whatismybrowser.com/' 312 | 'useragents/explore/operating_system_name/android/').content, 313 | 'lxml').findAll('td', {'class': 'useragent'}) 314 | user_agent = choice(useragents) 315 | return user_agent.text 316 | -------------------------------------------------------------------------------- /bot/modules/search.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import html 4 | import asyncio 5 | import aiohttp 6 | import json 7 | import feedparser 8 | import requests 9 | import itertools 10 | 11 | from telegram.ext import CommandHandler 12 | from telegram import ParseMode 13 | 14 | from urllib.parse import quote as urlencode, urlsplit 15 | 16 | from pyrogram import Client, filters, emoji 17 | from pyrogram.parser import html as pyrogram_html 18 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton 19 | from pyrogram.handlers import MessageHandler, CallbackQueryHandler 20 | 21 | from bot import app, dispatcher, IMAGE_URL 22 | from bot.helper import custom_filters 23 | from bot.helper.telegram_helper.filters import CustomFilters 24 | 25 | search_lock = asyncio.Lock() 26 | search_info = {False: dict(), True: dict()} 27 | 28 | async def return_search(query, page=1, sukebei=False): 29 | page -= 1 30 | query = query.lower().strip() 31 | used_search_info = search_info[sukebei] 32 | async with search_lock: 33 | results, get_time = used_search_info.get(query, (None, 0)) 34 | if (time.time() - get_time) > 3600: 35 | results = [] 36 | async with aiohttp.ClientSession() as session: 37 | async with session.get(f'https://{"sukebei." if sukebei else ""}nyaa.si/?page=rss&q={urlencode(query)}') as resp: 38 | d = feedparser.parse(await resp.text()) 39 | text = '' 40 | a = 0 41 | parser = pyrogram_html.HTML(None) 42 | for i in sorted(d['entries'], key=lambda i: int(i['nyaa_seeders']), reverse=True): 43 | if i['nyaa_size'].startswith('0'): 44 | continue 45 | if not int(i['nyaa_seeders']): 46 | break 47 | link = i['link'] 48 | splitted = urlsplit(link) 49 | if splitted.scheme == 'magnet' and splitted.query: 50 | link = f'{link}' 51 | newtext = f'''{a + 1}. {html.escape(i["title"])} 52 | Link: {link} 53 | Size: {i["nyaa_size"]} 54 | Seeders: {i["nyaa_seeders"]} 55 | Leechers: {i["nyaa_leechers"]} 56 | Category: {i["nyaa_category"]}\n\n''' 57 | futtext = text + newtext 58 | if (a and not a % 10) or len((await parser.parse(futtext))['message']) > 4096: 59 | results.append(text) 60 | futtext = newtext 61 | text = futtext 62 | a += 1 63 | results.append(text) 64 | ttl = time.time() 65 | used_search_info[query] = results, ttl 66 | try: 67 | return results[page], len(results), ttl 68 | except IndexError: 69 | return '', len(results), ttl 70 | 71 | message_info = dict() 72 | ignore = set() 73 | 74 | @app.on_message(filters.command(['nyaa'])) 75 | async def nyaa_search(client, message): 76 | text = message.text.split(' ') 77 | text.pop(0) 78 | query = ' '.join(text) 79 | await init_search(client, message, query, False) 80 | 81 | @app.on_message(filters.command(['sukebei'])) 82 | async def nyaa_search_sukebei(client, message): 83 | text = message.text.split(' ') 84 | text.pop(0) 85 | query = ' '.join(text) 86 | await init_search(client, message, query, True) 87 | 88 | async def init_search(client, message, query, sukebei): 89 | result, pages, ttl = await return_search(query, sukebei=sukebei) 90 | if not result: 91 | await message.reply_text('No results found') 92 | else: 93 | buttons = [InlineKeyboardButton(f'1/{pages}', 'nyaa_nop'), InlineKeyboardButton(f'Next', 'nyaa_next')] 94 | if pages == 1: 95 | buttons.pop() 96 | reply = await message.reply_text(result, reply_markup=InlineKeyboardMarkup([ 97 | buttons 98 | ])) 99 | message_info[(reply.chat.id, reply.message_id)] = message.from_user.id, ttl, query, 1, pages, sukebei 100 | 101 | @app.on_callback_query(custom_filters.callback_data('nyaa_nop')) 102 | async def nyaa_nop(client, callback_query): 103 | await callback_query.answer(cache_time=3600) 104 | 105 | callback_lock = asyncio.Lock() 106 | @app.on_callback_query(custom_filters.callback_data(['nyaa_back', 'nyaa_next'])) 107 | async def nyaa_callback(client, callback_query): 108 | message = callback_query.message 109 | message_identifier = (message.chat.id, message.message_id) 110 | data = callback_query.data 111 | async with callback_lock: 112 | if message_identifier in ignore: 113 | await callback_query.answer() 114 | return 115 | user_id, ttl, query, current_page, pages, sukebei = message_info.get(message_identifier, (None, 0, None, 0, 0, None)) 116 | og_current_page = current_page 117 | if data == 'nyaa_back': 118 | current_page -= 1 119 | elif data == 'nyaa_next': 120 | current_page += 1 121 | if current_page < 1: 122 | current_page = 1 123 | elif current_page > pages: 124 | current_page = pages 125 | ttl_ended = (time.time() - ttl) > 3600 126 | if ttl_ended: 127 | text = getattr(message.text, 'html', 'Search expired') 128 | else: 129 | if callback_query.from_user.id != user_id: 130 | await callback_query.answer('...no', cache_time=3600) 131 | return 132 | text, pages, ttl = await return_search(query, current_page, sukebei) 133 | buttons = [InlineKeyboardButton(f'Prev', 'nyaa_back'), InlineKeyboardButton(f'{current_page}/{pages}', 'nyaa_nop'), InlineKeyboardButton(f'Next', 'nyaa_next')] 134 | if ttl_ended: 135 | buttons = [InlineKeyboardButton('Search Expired', 'nyaa_nop')] 136 | else: 137 | if current_page == 1: 138 | buttons.pop(0) 139 | if current_page == pages: 140 | buttons.pop() 141 | if ttl_ended or current_page != og_current_page: 142 | await callback_query.edit_message_text(text, reply_markup=InlineKeyboardMarkup([ 143 | buttons 144 | ])) 145 | message_info[message_identifier] = user_id, ttl, query, current_page, pages, sukebei 146 | if ttl_ended: 147 | ignore.add(message_identifier) 148 | await callback_query.answer() 149 | 150 | # Using upstream API based on: https://github.com/Ryuk-me/Torrents-Api 151 | # Implemented by https://github.com/jusidama18 152 | 153 | class TorrentSearch: 154 | index = 0 155 | query = None 156 | message = None 157 | response = None 158 | response_range = None 159 | 160 | RESULT_LIMIT = 4 161 | RESULT_STR = None 162 | 163 | def __init__(self, command: str, source: str, result_str: str): 164 | self.command = command 165 | self.source = source.rstrip('/') 166 | self.RESULT_STR = result_str 167 | 168 | app.add_handler(MessageHandler(self.find, filters.command([command]))) 169 | app.add_handler(CallbackQueryHandler(self.previous, filters.regex(f"{self.command}_previous"))) 170 | app.add_handler(CallbackQueryHandler(self.delete, filters.regex(f"{self.command}_delete"))) 171 | app.add_handler(CallbackQueryHandler(self.next, filters.regex(f"{self.command}_next"))) 172 | 173 | @staticmethod 174 | def format_magnet(string: str): 175 | if not string: 176 | return "" 177 | return string.split('&tr', 1)[0] 178 | 179 | def get_formatted_string(self, values): 180 | string = self.RESULT_STR.format(**values) 181 | extra = "" 182 | if "Files" in values: 183 | tmp_str = "➲[{Quality} - {Type} ({Size})]({Torrent}): `{magnet}`" 184 | extra += "\n".join( 185 | tmp_str.format(**f, magnet=self.format_magnet(f['Magnet'])) 186 | for f in values['Files'] 187 | ) 188 | else: 189 | magnet = values.get('magnet', values.get('Magnet')) # Avoid updating source dict 190 | if magnet: 191 | extra += f"➲Magnet: `{self.format_magnet(magnet)}`" 192 | if (extra): 193 | string += "\n" + extra 194 | return string 195 | 196 | async def update_message(self): 197 | prevBtn = InlineKeyboardButton(f"Prev", callback_data=f"{self.command}_previous") 198 | delBtn = InlineKeyboardButton(f"{emoji.CROSS_MARK}", callback_data=f"{self.command}_delete") 199 | nextBtn = InlineKeyboardButton(f"Next", callback_data=f"{self.command}_next") 200 | 201 | inline = [] 202 | if (self.index != 0): 203 | inline.append(prevBtn) 204 | inline.append(delBtn) 205 | if (self.index != len(self.response_range) - 1): 206 | inline.append(nextBtn) 207 | 208 | res_lim = min(self.RESULT_LIMIT, len(self.response) - self.RESULT_LIMIT*self.index) 209 | result = f"**Page - {self.index+1}**\n\n" 210 | result += "\n\n=======================\n\n".join( 211 | self.get_formatted_string(self.response[self.response_range[self.index]+i]) 212 | for i in range(res_lim) 213 | ) 214 | 215 | await self.message.edit( 216 | result, 217 | reply_markup=InlineKeyboardMarkup([inline]), 218 | parse_mode="markdown", 219 | ) 220 | 221 | async def find(self, client, message): 222 | if len(message.command) < 2: 223 | await message.reply_text(f"Usage: /{self.command} query") 224 | return 225 | 226 | query = urlencode(message.text.split(None, 1)[1]) 227 | self.message = await message.reply_text("Searching") 228 | try: 229 | async with aiohttp.ClientSession() as session: 230 | async with session.get(f"{self.source}/{query}") as resp: 231 | if (resp.status != 200): 232 | raise Exception('unsuccessful request') 233 | result = await resp.json() 234 | if (result and isinstance(result[0], list)): 235 | result = list(itertools.chain(*result)) 236 | self.response = result 237 | self.response_range = range(0, len(self.response), self.RESULT_LIMIT) 238 | except: 239 | await self.message.edit("No Results Found.") 240 | return 241 | await self.update_message() 242 | 243 | async def delete(self, client, message): 244 | index = 0 245 | query = None 246 | message = None 247 | response = None 248 | response_range = None 249 | await self.message.delete() 250 | 251 | async def previous(self, client, message): 252 | self.index -= 1 253 | await self.update_message() 254 | 255 | async def next(self, client, message): 256 | self.index += 1 257 | await self.update_message() 258 | 259 | RESULT_STR_1337 = ( 260 | "➲Name: `{Name}`\n" 261 | "➲Size: {Size}\n" 262 | "➲Seeders: {Seeders} || ➲Leechers: {Leechers}" 263 | ) 264 | RESULT_STR_PIRATEBAY = ( 265 | "➲Name: `{Name}`\n" 266 | "➲Size: {Size}\n" 267 | "➲Seeders: {Seeders} || ➲Leechers: {Leechers}" 268 | ) 269 | RESULT_STR_TGX = ( 270 | "➲Name: `{Name}`\n" 271 | "➲Size: {Size}\n" 272 | "➲Seeders: {Seeders} || ➲Leechers: {Leechers}" 273 | ) 274 | RESULT_STR_YTS = ( 275 | "➲Name: `{Name}`" 276 | ) 277 | RESULT_STR_EZTV = ( 278 | "➲Name: `{Name}`\n" 279 | "➲Size: {Size}\n" 280 | "➲Seeders: {Seeders}" 281 | ) 282 | RESULT_STR_TORLOCK = ( 283 | "➲Name: `{Name}`\n" 284 | "➲Size: {Size}\n" 285 | "➲Seeders: {Seeders} || ➲Leechers: {Leechers}" 286 | ) 287 | RESULT_STR_RARBG = ( 288 | "➲Name: `{Name}`\n" 289 | "➲Size: {Size}\n" 290 | "➲Seeders: {Seeders} || ➲Leechers: {Leechers}" 291 | ) 292 | RESULT_STR_ALL = ( 293 | "➲Name: `{Name}`\n" 294 | "➲Size: {Size}\n" 295 | "➲Seeders: {Seeders} || ➲Leechers: {Leechers}" 296 | ) 297 | 298 | torrents_dict = { 299 | '1337x': {'source': "https://slam-api.herokuapp.com/api/1337x/", 'result_str': RESULT_STR_1337}, 300 | 'piratebay': {'source': "https://slam-api.herokuapp.com/api/piratebay/", 'result_str': RESULT_STR_PIRATEBAY}, 301 | 'tgx': {'source': "https://slam-api.herokuapp.com/api/tgx/", 'result_str': RESULT_STR_TGX}, 302 | 'yts': {'source': "https://slam-api.herokuapp.com/api/yts/", 'result_str': RESULT_STR_YTS}, 303 | 'eztv': {'source': "https://slam-api.herokuapp.com/api/eztv/", 'result_str': RESULT_STR_EZTV}, 304 | 'torlock': {'source': "https://slam-api.herokuapp.com/api/torlock/", 'result_str': RESULT_STR_TORLOCK}, 305 | 'rarbg': {'source': "https://slam-api.herokuapp.com/api/rarbg/", 'result_str': RESULT_STR_RARBG}, 306 | 'ts': {'source': "https://slam-api.herokuapp.com/api/all/", 'result_str': RESULT_STR_ALL} 307 | } 308 | 309 | torrent_handlers = [] 310 | for command, value in torrents_dict.items(): 311 | torrent_handlers.append(TorrentSearch(command, value['source'], value['result_str'])) 312 | 313 | def searchhelp(update, context): 314 | help_string = ''' 315 | • /nyaa [search query] 316 | • /sukebei [search query] 317 | • /1337x [search query] 318 | • /piratebay [search query] 319 | • /tgx [search query] 320 | • /yts [search query] 321 | • /eztv [search query] 322 | • /torlock [search query] 323 | • /rarbg [search query] 324 | • /ts [search query] 325 | ''' 326 | update.effective_message.reply_photo(IMAGE_URL, help_string, parse_mode=ParseMode.HTML) 327 | 328 | 329 | SEARCHHELP_HANDLER = CommandHandler("tshelp", searchhelp, filters=(CustomFilters.authorized_chat | CustomFilters.authorized_user) & CustomFilters.mirror_owner_filter, run_async=True) 330 | dispatcher.add_handler(SEARCHHELP_HANDLER) 331 | -------------------------------------------------------------------------------- /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 = ['https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/cloud-platform', 18 | 'https://www.googleapis.com/auth/iam'] 19 | project_create_ops = [] 20 | current_key_dump = [] 21 | sleep_time = 30 22 | 23 | 24 | # Create count SAs in project 25 | def _create_accounts(service, project, count): 26 | batch = service.new_batch_http_request(callback=_def_batch_resp) 27 | for i in range(count): 28 | aid = _generate_id('mfc-') 29 | batch.add(service.projects().serviceAccounts().create(name='projects/' + project, body={'accountId': aid, 30 | 'serviceAccount': { 31 | 'displayName': aid}})) 32 | batch.execute() 33 | 34 | 35 | # Create accounts needed to fill project 36 | def _create_remaining_accounts(iam, project): 37 | print('Creating accounts in %s' % project) 38 | sa_count = len(_list_sas(iam, project)) 39 | while sa_count != 100: 40 | _create_accounts(iam, project, 100 - sa_count) 41 | sa_count = len(_list_sas(iam, project)) 42 | 43 | 44 | # Generate a random id 45 | def _generate_id(prefix='saf-'): 46 | chars = '-abcdefghijklmnopqrstuvwxyz1234567890' 47 | return prefix + ''.join(choice(chars) for _ in range(25)) + choice(chars[1:]) 48 | 49 | 50 | # List projects using service 51 | def _get_projects(service): 52 | return [i['projectId'] for i in service.projects().list().execute()['projects']] 53 | 54 | 55 | # Default batch callback handler 56 | def _def_batch_resp(id, resp, exception): 57 | if exception is not None: 58 | if str(exception).startswith(' 0: 219 | current_count = len(_get_projects(cloud)) 220 | if current_count + create_projects <= max_projects: 221 | print('Creating %d projects' % (create_projects)) 222 | nprjs = _create_projects(cloud, create_projects) 223 | selected_projects = nprjs 224 | else: 225 | sys.exit('No, you cannot create %d new project (s).\n' 226 | 'Please reduce value of --quick-setup.\n' 227 | 'Remember that you can totally create %d projects (%d already).\n' 228 | 'Please do not delete existing projects unless you know what you are doing' % ( 229 | create_projects, max_projects, current_count)) 230 | else: 231 | print('Will overwrite all service accounts in existing projects.\n' 232 | 'So make sure you have some projects already.') 233 | input("Press Enter to continue...") 234 | 235 | if enable_services: 236 | ste = [] 237 | ste.append(enable_services) 238 | if enable_services == '~': 239 | ste = selected_projects 240 | elif enable_services == '*': 241 | ste = _get_projects(cloud) 242 | services = [i + '.googleapis.com' for i in services] 243 | print('Enabling services') 244 | _enable_services(serviceusage, ste, services) 245 | if create_sas: 246 | stc = [] 247 | stc.append(create_sas) 248 | if create_sas == '~': 249 | stc = selected_projects 250 | elif create_sas == '*': 251 | stc = _get_projects(cloud) 252 | for i in stc: 253 | _create_remaining_accounts(iam, i) 254 | if download_keys: 255 | try: 256 | os.mkdir(path) 257 | except OSError as e: 258 | if e.errno == errno.EEXIST: 259 | pass 260 | else: 261 | raise 262 | std = [] 263 | std.append(download_keys) 264 | if download_keys == '~': 265 | std = selected_projects 266 | elif download_keys == '*': 267 | std = _get_projects(cloud) 268 | _create_sa_keys(iam, std, path) 269 | if delete_sas: 270 | std = [] 271 | std.append(delete_sas) 272 | if delete_sas == '~': 273 | std = selected_projects 274 | elif delete_sas == '*': 275 | std = _get_projects(cloud) 276 | for i in std: 277 | print('Deleting service accounts in %s' % i) 278 | _delete_sas(iam, i) 279 | 280 | 281 | if __name__ == '__main__': 282 | parse = ArgumentParser(description='A tool to create Google service accounts.') 283 | parse.add_argument('--path', '-p', default='accounts', 284 | help='Specify an alternate directory to output the credential files.') 285 | parse.add_argument('--token', default='token_sa.pickle', help='Specify the pickle token file path.') 286 | parse.add_argument('--credentials', default='credentials.json', help='Specify the credentials file path.') 287 | parse.add_argument('--list-projects', default=False, action='store_true', 288 | help='List projects viewable by the user.') 289 | parse.add_argument('--list-sas', default=False, help='List service accounts in a project.') 290 | parse.add_argument('--create-projects', type=int, default=None, help='Creates up to N projects.') 291 | parse.add_argument('--max-projects', type=int, default=12, help='Max amount of project allowed. Default: 12') 292 | parse.add_argument('--enable-services', default=None, 293 | help='Enables services on the project. Default: IAM and Drive') 294 | parse.add_argument('--services', nargs='+', default=['iam', 'drive'], 295 | help='Specify a different set of services to enable. Overrides the default.') 296 | parse.add_argument('--create-sas', default=None, help='Create service accounts in a project.') 297 | parse.add_argument('--delete-sas', default=None, help='Delete service accounts in a project.') 298 | parse.add_argument('--download-keys', default=None, help='Download keys for all the service accounts in a project.') 299 | parse.add_argument('--quick-setup', default=None, type=int, 300 | help='Create projects, enable services, create service accounts and download keys. ') 301 | parse.add_argument('--new-only', default=False, action='store_true', help='Do not use exisiting projects.') 302 | args = parse.parse_args() 303 | # If credentials file is invalid, search for one. 304 | if not os.path.exists(args.credentials): 305 | options = glob('*.json') 306 | print('No credentials found at %s. Please enable the Drive API in:\n' 307 | 'https://developers.google.com/drive/api/v3/quickstart/python\n' 308 | 'and save the json file as credentials.json' % args.credentials) 309 | if len(options) < 1: 310 | exit(-1) 311 | else: 312 | i = 0 313 | print('Select a credentials file below.') 314 | inp_options = [str(i) for i in list(range(1, len(options) + 1))] + options 315 | while i < len(options): 316 | print(' %d) %s' % (i + 1, options[i])) 317 | i += 1 318 | inp = None 319 | while True: 320 | inp = input('> ') 321 | if inp in inp_options: 322 | break 323 | if inp in options: 324 | args.credentials = inp 325 | else: 326 | args.credentials = options[int(inp) - 1] 327 | print('Use --credentials %s next time to use this credentials file.' % args.credentials) 328 | if args.quick_setup: 329 | opt = '*' 330 | if args.new_only: 331 | opt = '~' 332 | args.services = ['iam', 'drive'] 333 | args.create_projects = args.quick_setup 334 | args.enable_services = opt 335 | args.create_sas = opt 336 | args.download_keys = opt 337 | resp = serviceaccountfactory( 338 | path=args.path, 339 | token=args.token, 340 | credentials=args.credentials, 341 | list_projects=args.list_projects, 342 | list_sas=args.list_sas, 343 | create_projects=args.create_projects, 344 | max_projects=args.max_projects, 345 | create_sas=args.create_sas, 346 | delete_sas=args.delete_sas, 347 | enable_services=args.enable_services, 348 | services=args.services, 349 | download_keys=args.download_keys 350 | ) 351 | if resp is not None: 352 | if args.list_projects: 353 | if resp: 354 | print('Projects (%d):' % len(resp)) 355 | for i in resp: 356 | print(' ' + i) 357 | else: 358 | print('No projects.') 359 | elif args.list_sas: 360 | if resp: 361 | print('Service accounts in %s (%d):' % (args.list_sas, len(resp))) 362 | for i in resp: 363 | print(' %s (%s)' % (i['email'], i['uniqueId'])) 364 | else: 365 | print('No service accounts.') 366 | --------------------------------------------------------------------------------