diff --git a/Dockerfile b/Dockerfile index e6e8ba0..4aac024 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,10 @@ -FROM breakdowns/mega-sdk-python:latest +FROM anasty17/megasdk:latest WORKDIR /usr/src/app RUN chmod 777 /usr/src/app +RUN apt-get install -y xz-utils neofetch unzip && apt-get autoremove -y + COPY requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt diff --git a/alive.py b/alive.py new file mode 100644 index 0000000..ee1273a --- /dev/null +++ b/alive.py @@ -0,0 +1,11 @@ +import time +import requests +import os +from dotenv import load_dotenv + +load_dotenv('config.env') + +url = os.environ.get("BASE_URL_OF_BOT") +while True: + time.sleep(1000) + status = requests.get(url).status_code diff --git a/aria.sh b/aria.sh index e293d30..506ee19 100755 --- a/aria.sh +++ b/aria.sh @@ -1,15 +1,10 @@ -export MAX_DOWNLOAD_SPEED=0 -tracker_list=$(curl -Ns https://raw.githubusercontent.com/XIU2/TrackersListCollection/master/all.txt https://ngosang.github.io/trackerslist/trackers_all_http.txt https://raw.githubusercontent.com/DeSireFire/animeTrackerList/master/AT_all.txt https://raw.githubusercontent.com/hezhijie0327/Trackerslist/main/trackerslist_combine.txt | awk '$0' | tr '\n' ',') -export MAX_CONCURRENT_DOWNLOADS=7 - -aria2c --enable-rpc --rpc-listen-all=false --check-certificate=false \ +aria2c --enable-rpc --check-certificate=false \ --max-connection-per-server=10 --rpc-max-request-size=1024M \ - --bt-tracker="[$tracker_list]" --bt-max-peers=0 --bt-tracker-connect-timeout=300 --bt-stop-timeout=1200 --min-split-size=10M \ - --follow-torrent=mem --split=10 \ - --daemon=true --allow-overwrite=true --max-overall-download-limit=$MAX_DOWNLOAD_SPEED \ - --max-overall-upload-limit=1K --max-concurrent-downloads=$MAX_CONCURRENT_DOWNLOADS \ + --bt-stop-timeout=1200 --min-split-size=10M --follow-torrent=mem --split=10 \ + --daemon=true --allow-overwrite=true --max-overall-download-limit=0 \ + --max-overall-upload-limit=1K --max-concurrent-downloads=15 --continue=true \ --peer-id-prefix=-qB4360- --user-agent=qBittorrent/4.3.6 --peer-agent=qBittorrent/4.3.6 \ - --disk-cache=64M --file-allocation=prealloc --continue=true \ - --max-file-not-found=0 --max-tries=20 --auto-file-renaming=true \ - --bt-enable-lpd=true --seed-time=0.01 --seed-ratio=1.0 \ - --content-disposition-default-utf8=true --http-accept-gzip=true --reuse-uri=true --netrc-path=/usr/src/app/.netrc + --disk-cache=64M --bt-enable-lpd=true --seed-time=0 --max-file-not-found=0 \ + --max-tries=20 --auto-file-renaming=true --reuse-uri=true --http-accept-gzip=true \ + --content-disposition-default-utf8=true --netrc-path=/usr/src/app/.netrc + diff --git a/bot/__init__.py b/bot/__init__.py index 80881fc..7dbaeb8 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -6,6 +6,7 @@ import string import aria2p +import qbittorrentapi as qba import telegram.ext as tg from dotenv import load_dotenv from pyrogram import Client @@ -66,6 +67,18 @@ def mktable(): ) ) + +def get_client() -> qba.TorrentsAPIMixIn: + qb_client = qba.Client(host="localhost", port=8090, username="admin", password="adminadmin") + try: + qb_client.auth_log_in() + qb_client.application.set_preferences({"disk_cache":64, "incomplete_files_ext":True, "max_connec":3000, "max_connec_per_torrent":300, "async_io_thread":32}) + return qb_client + except qba.LoginFailed as e: + LOGGER.error(str(e)) + return None + + DOWNLOAD_DIR = None BOT_TOKEN = None @@ -313,6 +326,31 @@ def mktable(): except KeyError: pass +try: + BASE_URL = getConfig('BASE_URL_OF_BOT') + if len(BASE_URL) == 0: + BASE_URL = None +except KeyError: + logging.warning('BASE_URL_OF_BOT not provided!') + BASE_URL = None + +try: + IS_VPS = getConfig('IS_VPS') + if IS_VPS.lower() == 'true': + IS_VPS = True + else: + IS_VPS = False +except KeyError: + IS_VPS = False + +try: + SERVER_PORT = getConfig('SERVER_PORT') + if len(SERVER_PORT) == 0: + SERVER_PORT = None +except KeyError: + logging.warning('SERVER_PORT not provided!') + SERVER_PORT = None + updater = tg.Updater(token=BOT_TOKEN) bot = updater.bot dispatcher = updater.dispatcher diff --git a/bot/__main__.py b/bot/__main__.py index a6cb1f7..0311e02 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,6 +1,7 @@ import shutil, psutil import signal import os +import asyncio from pyrogram import idle from bot import app @@ -8,7 +9,8 @@ from telegram import ParseMode from telegram.ext import CommandHandler -from bot import bot, dispatcher, updater, botStartTime, IGNORE_PENDING_REQUESTS +from wserver import start_server_async +from bot import bot, dispatcher, updater, botStartTime, IGNORE_PENDING_REQUESTS, IS_VPS, SERVER_PORT from bot.helper.ext_utils import fs_utils from bot.helper.telegram_helper.bot_commands import BotCommands from bot.helper.telegram_helper.message_utils import * @@ -201,6 +203,10 @@ def bot_help(update, context): def main(): fs_utils.start_cleanup() + + if IS_VPS: + asyncio.get_event_loop().run_until_complete(start_server_async(SERVER_PORT)) + # Check if the bot is restarting if os.path.isfile(".restartmsg"): with open(".restartmsg") as f: diff --git a/bot/helper/ext_utils/bot_utils.py b/bot/helper/ext_utils/bot_utils.py index 81fb3c5..6a080c4 100644 --- a/bot/helper/ext_utils/bot_utils.py +++ b/bot/helper/ext_utils/bot_utils.py @@ -26,6 +26,7 @@ class MirrorStatus: STATUS_CLONING = "Cloning...♻️" STATUS_WAITING = "Queued...📝" STATUS_FAILED = "Failed 🚫. Cleaning Download..." + STATUS_PAUSE = "Paused...⭕️" STATUS_ARCHIVING = "Archiving...🔐" STATUS_EXTRACTING = "Extracting...📂" @@ -76,6 +77,7 @@ def getDownloadByGid(gid): return dl return None + def getAllDownload(): with download_dict_lock: for dlDetails in list(download_dict.values()): @@ -84,6 +86,7 @@ def getAllDownload(): return dlDetails return None + def get_progress_bar_string(status): completed = status.processed_bytes() / 8 total = status.size_raw() / 8 @@ -134,6 +137,11 @@ def get_readable_message(): f" | Peers: {download.aria_download().connections}" except: pass + try: + msg += f"\nSeeders: {download.torrent_info().num_seeds}" \ + f" | Leechers: {download.torrent_info().num_leechs}" + except: + pass msg += f"\nTo Stop: /{BotCommands.CancelMirror} {download.gid()}" msg += "\n\n" if STATUS_LIMIT is not None: @@ -151,6 +159,7 @@ def get_readable_message(): return msg, button return msg, "" + def flip(update, context): query = update.callback_query query.answer() @@ -171,6 +180,7 @@ def flip(update, context): PAGE_NO -= 1 message_utils.update_all_messages() + def get_readable_time(seconds: int) -> str: result = '' (days, remainder) = divmod(seconds, 86400) @@ -196,12 +206,15 @@ def is_url(url: str): return True return False + def is_gdrive_link(url: str): return "drive.google.com" in url + def is_mega_link(url: str): return "mega.nz" in url or "mega.co.nz" in url + def get_mega_link_type(url: str): if "folder" in url: return "folder" @@ -211,12 +224,14 @@ def get_mega_link_type(url: str): return "folder" return "file" + def is_magnet(url: str): magnet = re.findall(MAGNET_REGEX, url) if magnet: return True return False + def new_thread(fn): """To use as decorator to make a function call threaded. Needs import @@ -229,6 +244,7 @@ def wrapper(*args, **kwargs): return wrapper + next_handler = CallbackQueryHandler(flip, pattern="nex", run_async=True) previous_handler = CallbackQueryHandler(flip, pattern="pre", run_async=True) dispatcher.add_handler(next_handler) diff --git a/bot/helper/ext_utils/fs_utils.py b/bot/helper/ext_utils/fs_utils.py index ce54ac0..32b7849 100644 --- a/bot/helper/ext_utils/fs_utils.py +++ b/bot/helper/ext_utils/fs_utils.py @@ -1,5 +1,5 @@ import sys -from bot import aria2, LOGGER, DOWNLOAD_DIR +from bot import aria2, LOGGER, DOWNLOAD_DIR, get_client import shutil import os import pathlib @@ -23,6 +23,7 @@ def start_cleanup(): def clean_all(): aria2.remove_all(True) + get_client().torrents_delete(torrent_hashes="all", delete_files=True) try: shutil.rmtree(DOWNLOAD_DIR) except FileNotFoundError: diff --git a/bot/helper/mirror_utils/download_utils/mega_downloader.py b/bot/helper/mirror_utils/download_utils/mega_downloader.py index edf2700..8f4778a 100644 --- a/bot/helper/mirror_utils/download_utils/mega_downloader.py +++ b/bot/helper/mirror_utils/download_utils/mega_downloader.py @@ -177,6 +177,7 @@ def add_download(mega_link: str, path: str, listener): if smsg: msg1 = "File/Folder is already available in Drive.\nHere are the search results:" sendMarkup(msg1, listener.bot, listener.update, button) + executor.continue_event.set() return if MEGA_LIMIT is not None or TAR_UNZIP_LIMIT is not None: limit = None @@ -193,10 +194,12 @@ def add_download(mega_link: str, path: str, listener): if 'G' in limit[1] or 'g' in limit[1]: if api.getSize(node) > limitint * 1024**3: sendMessage(msg3, listener.bot, listener.update) + executor.continue_event.set() return elif 'T' in limit[1] or 't' in limit[1]: if api.getSize(node) > limitint * 1024**4: sendMessage(msg3, listener.bot, listener.update) + executor.continue_event.set() return with download_dict_lock: download_dict[listener.uid] = MegaDownloadStatus(mega_listener, listener) diff --git a/bot/helper/mirror_utils/download_utils/qbit_downloader.py b/bot/helper/mirror_utils/download_utils/qbit_downloader.py new file mode 100644 index 0000000..f1f6f41 --- /dev/null +++ b/bot/helper/mirror_utils/download_utils/qbit_downloader.py @@ -0,0 +1,198 @@ +import os +import random +import string +import time +import logging + +import qbittorrentapi as qba +from urllib.parse import urlparse, parse_qs +from torrentool.api import Torrent +from telegram import InlineKeyboardMarkup +from telegram.ext import CallbackQueryHandler + +from bot import download_dict, download_dict_lock, BASE_URL, dispatcher, get_client +from bot.helper.mirror_utils.status_utils.qbit_download_status import QbDownloadStatus +from bot.helper.telegram_helper.message_utils import * +from bot.helper.ext_utils.bot_utils import setInterval, new_thread, MirrorStatus +from bot.helper.telegram_helper import button_build + +LOGGER = logging.getLogger(__name__) + + +class qbittorrent: + + + def __init__(self): + self.update_interval = 1.5 + self.meta_time = time.time() + + @new_thread + def add_torrent(self, link, dire, listener, qbitsel): + self.client = get_client() + self.listener = listener + is_file = False + count = 0 + pincode = "" + markup = None + try: + if os.path.exists(link): + is_file = True + self.ext_hash = get_hash_file(link) + else: + self.ext_hash = get_hash_magnet(link) + tor_info = self.client.torrents_info(torrent_hashes=self.ext_hash) + if len(tor_info) > 0: + sendMessage("This torrent is already in list.", listener.bot, listener.update) + return + if is_file: + op = self.client.torrents_add(torrent_files=[link], save_path=dire) + os.remove(link) + else: + op = self.client.torrents_add(link, save_path=dire) + if op.lower() == "ok.": + LOGGER.info(f"QbitDownload started: {self.ext_hash}") + tor_info = self.client.torrents_info(torrent_hashes=self.ext_hash) + if len(tor_info) == 0: + while True: + if time.time() - self.meta_time >= 300: + sendMessage("The torrent was not added. report when u see this error", listener.bot, listener.update) + return False + tor_info = self.client.torrents_info(torrent_hashes=self.ext_hash) + if len(tor_info) > 0: + break + else: + sendMessage("This is an unsupported/invalid link.", listener.bot, listener.update) + return + gid = ''.join(random.SystemRandom().choices(string.ascii_letters + string.digits, k=14)) + self.updater = setInterval(self.update_interval, self.update) + tor_info = tor_info[0] + if BASE_URL is not None and qbitsel: + if not is_file and (tor_info.state == "checkingResumeData" or tor_info.state == "metaDL"): + meta = sendMessage("Downloading Metadata...Please wait then you can select files or mirror torrent file if it have low seeders", listener.bot, listener.update) + while True: + tor_info = self.client.torrents_info(torrent_hashes=self.ext_hash) + if len(tor_info) == 0: + deleteMessage(listener.bot, meta) + return False + tor_info = tor_info[0] + if tor_info.state == "metaDL" or tor_info.state == "checkingResumeData": + time.sleep(1) + else: + break + deleteMessage(listener.bot, meta) + for n in str(self.ext_hash): + if n.isdigit(): + pincode += str(n) + count += 1 + if count == 4: + break + URL = f"{BASE_URL}/slam/files/{self.ext_hash}" + pindata = f"pin {gid} {pincode}" + donedata = f"done {gid} {self.ext_hash}" + buttons = button_build.ButtonMaker() + buttons.buildbutton("Select Files", URL) + buttons.sbutton("Pincode", pindata) + buttons.sbutton("Done Selecting", donedata) + QBBUTTONS = InlineKeyboardMarkup(buttons.build_menu(2)) + msg = "Your download paused. Choose files then press Done Selecting button to start downloading." + markup = sendMarkup(msg, listener.bot, listener.update, QBBUTTONS) + self.client.torrents_pause(torrent_hashes=self.ext_hash) + with download_dict_lock: + download_dict[listener.uid] = QbDownloadStatus(gid, listener, self.ext_hash, self.client, markup) + else: + with download_dict_lock: + download_dict[listener.uid] = QbDownloadStatus(gid, listener, self.ext_hash, self.client ,markup) + sendStatusMessage(listener.update, listener.bot) + except qba.UnsupportedMediaType415Error as e: + LOGGER.error(str(e)) + sendMessage("This is an unsupported/invalid link. {str(e)}", listener.bot, listener.update) + except Exception as e: + LOGGER.error(str(e)) + sendMessage(str(e), listener.bot, listener.update) + self.client.torrents_delete(torrent_hashes=self.ext_hash) + + + def update(self): + tor_info = self.client.torrents_info(torrent_hashes=self.ext_hash) + if len(tor_info) == 0: + self.updater.cancel() + return + else: + tor_info = tor_info[0] + if tor_info.state == "metaDL": + if time.time() - self.meta_time > 600: + self.client.torrents_delete(torrent_hashes=self.ext_hash) + self.listener.onDownloadError("Dead Torrent!") + self.updater.cancel() + return + elif tor_info.state == "error": + self.client.torrents_delete(torrent_hashes=self.ext_hash) + self.listener.onDownloadError("Error. IDK why, report in support group") + self.updater.cancel() + return + elif tor_info.state == "uploading" or tor_info.state.lower().endswith("up"): + self.client.torrents_pause(torrent_hashes=self.ext_hash) + self.listener.onDownloadComplete() + self.client.torrents_delete(torrent_hashes=self.ext_hash, delete_files=True) + self.updater.cancel() + + +def get_confirm(update, context): + query = update.callback_query + user_id = query.from_user.id + data = query.data + data = data.split(" ") + qdl = None + with download_dict_lock: + for dl in download_dict.values(): + if dl.status() == MirrorStatus.STATUS_PAUSE: + if dl.gid() == data[1]: + qdl = dl + break + if qdl is not None: + if user_id != qdl.listen().message.from_user.id: + query.answer(text="Don't waste your time!", show_alert=True) + return + if data[0] == "pin": + query.answer(text=data[2], show_alert=True) + elif data[0] == "done": + query.answer() + qdl.qbclient().torrents_resume(torrent_hashes=data[2]) + sendStatusMessage(qdl.listen().update, qdl.listen().bot) + deleteMessage(context.bot, qdl.mark()) + else: + query.answer(text="This task has been cancelled!", show_alert=True) + query.delete_message() + + +def get_hash_magnet(mgt): + if mgt.startswith('magnet:'): + _, _, _, _, query, _ = urlparse(mgt) + + qs = parse_qs(query) + v = qs.get('xt', None) + + if v == None or v == []: + LOGGER.error('Invalid magnet URI: no "xt" query parameter.') + return False + + v = v[0] + if not v.startswith('urn:btih:'): + LOGGER.error('Invalid magnet URI: "xt" value not valid for BitTorrent.') + return False + + mgt = v[len('urn:btih:'):] + return mgt.lower() + + +def get_hash_file(path): + tr = Torrent.from_file(path) + mgt = tr.magnet_link + return get_hash_magnet(mgt) + + +pin_handler = CallbackQueryHandler(get_confirm, pattern="pin", run_async=True) +done_handler = CallbackQueryHandler(get_confirm, pattern="done", run_async=True) +dispatcher.add_handler(pin_handler) +dispatcher.add_handler(done_handler) + diff --git a/bot/helper/mirror_utils/status_utils/qbit_download_status.py b/bot/helper/mirror_utils/status_utils/qbit_download_status.py new file mode 100644 index 0000000..6a5c0eb --- /dev/null +++ b/bot/helper/mirror_utils/status_utils/qbit_download_status.py @@ -0,0 +1,94 @@ +from bot import DOWNLOAD_DIR, LOGGER, get_client +from bot.helper.ext_utils.bot_utils import MirrorStatus, get_readable_file_size, get_readable_time +from .status import Status + + +class QbDownloadStatus(Status): + + def __init__(self, gid, listener, qbhash, client, markup): + super().__init__() + self.__gid = gid + self.__hash = qbhash + self.__client = client + self.__markup = markup + self.__uid = listener.uid + self.__listener = listener + self.message = listener.message + self.is_extracting = False + self.is_archiving = False + + + def progress(self): + """ + Calculates the progress of the mirror (upload or download) + :return: returns progress in percentage + """ + return f'{round(self.torrent_info().progress*100,2)}%' + + def size_raw(self): + """ + Gets total size of the mirror file/folder + :return: total size of mirror + """ + return self.torrent_info().total_size + + def processed_bytes(self): + return self.torrent_info().downloaded + + def speed(self): + return f"{get_readable_file_size(self.torrent_info().dlspeed)}/s" + + def name(self): + return self.torrent_info().name + + def path(self): + return f"{DOWNLOAD_DIR}{self.__uid}" + + def size(self): + return get_readable_file_size(self.torrent_info().total_size) + + def eta(self): + return get_readable_time(self.torrent_info().eta) + + def status(self): + download = self.torrent_info().state + if download == "queuedDL": + status = MirrorStatus.STATUS_WAITING + elif download == "metaDL": + status = MirrorStatus.STATUS_DOWNLOADING + " (Metadata)" + elif download == "pausedDL": + status = MirrorStatus.STATUS_PAUSE + else: + status = MirrorStatus.STATUS_DOWNLOADING + return status + + def torrent_info(self): + tor_info = self.__client.torrents_info(torrent_hashes=self.__hash) + if len(tor_info) == 0: + return None + else: + return tor_info[0] + + def download(self): + return self + + def uid(self): + return self.__uid + + def gid(self): + return self.__gid + + def listen(self): + return self.__listener + + def qbclient(self): + return self.__client + + def mark(self): + return self.__markup + + def cancel_download(self): + LOGGER.info(f"Cancelling Download: {self.name()}") + self.__listener.onDownloadError('Download stopped by user!') + self.__client.torrents_delete(torrent_hashes=self.__hash) + diff --git a/bot/helper/telegram_helper/message_utils.py b/bot/helper/telegram_helper/message_utils.py index 19b081a..1ad2653 100644 --- a/bot/helper/telegram_helper/message_utils.py +++ b/bot/helper/telegram_helper/message_utils.py @@ -4,8 +4,8 @@ import psutil, shutil import time from bot import AUTO_DELETE_MESSAGE_DURATION, LOGGER, bot, \ - status_reply_dict, status_reply_dict_lock, download_dict, download_dict_lock, botStartTime -from bot.helper.ext_utils.bot_utils import get_readable_message, get_readable_file_size, get_readable_time, MirrorStatus + status_reply_dict, status_reply_dict_lock, download_dict, download_dict_lock, botStartTime, Interval, DOWNLOAD_STATUS_UPDATE_INTERVAL +from bot.helper.ext_utils.bot_utils import get_readable_message, get_readable_file_size, get_readable_time, MirrorStatus, setInterval from telegram.error import TimedOut, BadRequest @@ -108,6 +108,8 @@ def update_all_messages(): def sendStatusMessage(msg, bot): + if len(Interval) == 0: + Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages)) total, used, free = shutil.disk_usage('.') free = get_readable_file_size(free) currentTime = get_readable_time(time.time() - botStartTime) diff --git a/bot/modules/cancel_mirror.py b/bot/modules/cancel_mirror.py index bf70fa0..369877f 100644 --- a/bot/modules/cancel_mirror.py +++ b/bot/modules/cancel_mirror.py @@ -42,9 +42,9 @@ def cancel_mirror(update, context): elif not mirror_message: sendMessage(msg, context.bot, update) return - if dl.status() == "Archiving...🔐": + if dl.status() == MirrorStatus.STATUS_ARCHIVING: sendMessage("Archival in Progress, You Can't Cancel It.", context.bot, update) - elif dl.status() == "Extracting...📂": + elif dl.status() == MirrorStatus.STATUS_EXTRACTING: sendMessage("Extract in Progress, You Can't Cancel It.", context.bot, update) else: dl.download().cancel_download() @@ -54,16 +54,15 @@ def cancel_mirror(update, context): def cancel_all(update, context): count = 0 - gid = 1 + gid = 0 while True: dl = getAllDownload() if dl: - if dl.gid() == gid: - continue - else: + if dl.gid() != gid: gid = dl.gid() dl.download().cancel_download() count += 1 + sleep(0.3) else: break sendMessage(f'{count} Download(s) has been Cancelled!', context.bot, update) diff --git a/bot/modules/clone.py b/bot/modules/clone.py index 8bde39a..9ef506f 100644 --- a/bot/modules/clone.py +++ b/bot/modules/clone.py @@ -4,8 +4,8 @@ from bot.helper.telegram_helper.filters import CustomFilters from bot.helper.telegram_helper.bot_commands import BotCommands from bot.helper.mirror_utils.status_utils.clone_status import CloneStatus -from bot import dispatcher, LOGGER, CLONE_LIMIT, STOP_DUPLICATE, download_dict, download_dict_lock, Interval, DOWNLOAD_STATUS_UPDATE_INTERVAL -from bot.helper.ext_utils.bot_utils import get_readable_file_size, setInterval +from bot import dispatcher, LOGGER, CLONE_LIMIT, STOP_DUPLICATE, download_dict, download_dict_lock, Interval +from bot.helper.ext_utils.bot_utils import get_readable_file_size import random import string @@ -50,8 +50,6 @@ def cloneNode(update, context): clone_status = CloneStatus(drive, clonesize, update, gid) with download_dict_lock: download_dict[update.message.message_id] = clone_status - if len(Interval) == 0: - Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages)) sendStatusMessage(update, context.bot) result, button = drive.clone(link) with download_dict_lock: diff --git a/bot/modules/mirror.py b/bot/modules/mirror.py index d191b56..d5b8f5f 100644 --- a/bot/modules/mirror.py +++ b/bot/modules/mirror.py @@ -3,12 +3,13 @@ from telegram import InlineKeyboardMarkup from bot import Interval, INDEX_URL, BUTTON_FOUR_NAME, BUTTON_FOUR_URL, BUTTON_FIVE_NAME, BUTTON_FIVE_URL, BUTTON_SIX_NAME, BUTTON_SIX_URL, BLOCK_MEGA_FOLDER, BLOCK_MEGA_LINKS, VIEW_LINK, aria2 -from bot import dispatcher, DOWNLOAD_DIR, DOWNLOAD_STATUS_UPDATE_INTERVAL, download_dict, download_dict_lock, SHORTENER, SHORTENER_API, TAR_UNZIP_LIMIT +from bot import dispatcher, DOWNLOAD_DIR, download_dict, download_dict_lock, SHORTENER, SHORTENER_API, TAR_UNZIP_LIMIT from bot.helper.ext_utils import fs_utils, bot_utils -from bot.helper.ext_utils.bot_utils import setInterval, get_mega_link_type +from bot.helper.ext_utils.bot_utils import get_mega_link_type from bot.helper.ext_utils.exceptions import DirectDownloadLinkException, NotSupportedExtractionArchive from bot.helper.mirror_utils.download_utils.aria2_download import AriaDownloadHelper from bot.helper.mirror_utils.download_utils.mega_downloader import MegaDownloadHelper +from bot.helper.mirror_utils.download_utils.qbit_downloader import qbittorrent from bot.helper.mirror_utils.download_utils.direct_link_generator import direct_link_generator from bot.helper.mirror_utils.download_utils.telegram_downloader import TelegramDownloadHelper from bot.helper.mirror_utils.status_utils import listeners @@ -235,8 +236,15 @@ def _mirror(bot, update, isTar=False, extract=False): mesg = update.message.text.split('\n') message_args = mesg[0].split(' ') name_args = mesg[0].split('|') + qbit = False + qbitsel = False try: link = message_args[1] + if link == "qb" or link == "qbs": + qbit = True + if link == "qbs": + qbitsel = True + link = message_args[2] print(link) if link.startswith("|") or link.startswith("pswd: "): link = '' @@ -281,11 +289,13 @@ def _mirror(bot, update, isTar=False, extract=False): tg_downloader = TelegramDownloadHelper(listener) ms = update.message tg_downloader.add_download(ms, f'{DOWNLOAD_DIR}{listener.uid}/', name) - if len(Interval) == 0: - Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages)) return else: - link = file.get_file().file_path + if qbit: + file.get_file().download(custom_path=f"/usr/src/app/{file.file_name}") + link = f"/usr/src/app/{file.file_name}" + else: + link = file.get_file().file_path else: tag = None if not bot_utils.is_url(link) and not bot_utils.is_magnet(link): @@ -333,8 +343,6 @@ def _mirror(bot, update, isTar=False, extract=False): download_status = DownloadStatus(drive, size, listener, gid) with download_dict_lock: download_dict[listener.uid] = download_status - if len(Interval) == 0: - Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages)) sendStatusMessage(update, bot) drive.download(link) @@ -347,11 +355,14 @@ def _mirror(bot, update, isTar=False, extract=False): else: mega_dl = MegaDownloadHelper() mega_dl.add_download(link, f'{DOWNLOAD_DIR}/{listener.uid}/', listener) + + elif qbit and (bot_utils.is_magnet(link) or os.path.exists(link)): + qbit = qbittorrent() + qbit.add_torrent(link, f'{DOWNLOAD_DIR}{listener.uid}/', listener, qbitsel) + else: ariaDlManager.add_download(link, f'{DOWNLOAD_DIR}/{listener.uid}/', listener, name) sendStatusMessage(update, bot) - if len(Interval) == 0: - Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages)) def mirror(update, context): diff --git a/bot/modules/watch.py b/bot/modules/watch.py index 97c469e..b6f96d8 100644 --- a/bot/modules/watch.py +++ b/bot/modules/watch.py @@ -1,8 +1,7 @@ from telegram.ext import CommandHandler from telegram import Bot, Update -from bot import Interval, DOWNLOAD_DIR, DOWNLOAD_STATUS_UPDATE_INTERVAL, dispatcher, LOGGER -from bot.helper.ext_utils.bot_utils import setInterval -from bot.helper.telegram_helper.message_utils import update_all_messages, sendMessage, sendStatusMessage +from bot import DOWNLOAD_DIR, dispatcher, LOGGER +from bot.helper.telegram_helper.message_utils import sendMessage, sendStatusMessage from .mirror import MirrorListener from bot.helper.mirror_utils.download_utils.youtube_dl_download_helper import YoutubeDLHelper from bot.helper.telegram_helper.bot_commands import BotCommands @@ -50,8 +49,6 @@ def _watch(bot: Bot, update, isTar=False): ydl = YoutubeDLHelper(listener) threading.Thread(target=ydl.add_download,args=(link, f'{DOWNLOAD_DIR}{listener.uid}', qual, name)).start() sendStatusMessage(update, bot) - if len(Interval) == 0: - Interval.append(setInterval(DOWNLOAD_STATUS_UPDATE_INTERVAL, update_all_messages)) def watchTar(update, context): diff --git a/config_sample.env b/config_sample.env index 7442aba..3d7f7e7 100644 --- a/config_sample.env +++ b/config_sample.env @@ -30,6 +30,10 @@ BLOCK_MEGA_LINKS = "" STOP_DUPLICATE = "" SHORTENER = "" SHORTENER_API = "" +#Qbittorrent +IS_VPS = "" +SERVER_PORT = "80" #For VPS +BASE_URL_OF_BOT = "" # If you want to use Credentials externally from Index Links, fill these vars with the direct links # These are optional, if you don't know, simply leave them, don't fill anything in them. ACCOUNTS_ZIP_URL = "" diff --git a/heroku.yml b/heroku.yml index 75581ef..a3d3035 100644 --- a/heroku.yml +++ b/heroku.yml @@ -1,5 +1,5 @@ build: docker: - worker: Dockerfile + web: Dockerfile run: - worker: bash start.sh \ No newline at end of file + web: bash start.sh diff --git a/nodes.py b/nodes.py new file mode 100644 index 0000000..f4549a2 --- /dev/null +++ b/nodes.py @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- +# (c) YashDK [yash-dk@github] + +from anytree import NodeMixin, RenderTree, PreOrderIter +import qbittorrentapi as qba + +SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] + +class TorNode(NodeMixin): + def __init__(self, name, is_folder=False, is_file=False, parent=None, progress=None, size=None, priority=None, file_id=None): + super().__init__() + self.name = name + self.is_folder = is_folder + self.is_file = is_file + + if parent is not None: + self.parent = parent + if progress is not None: + self.progress = progress + if size is not None: + self.size = size + if priority is not None: + self.priority = priority + if file_id is not None: + self.file_id = file_id + + +def get_folders(path): + path_seperator = "/" + folders = path.split(path_seperator) + return folders + + +def make_tree(res): + """This function takes the list of all the torrent files. The files are name hierarchically. + Felt a need to document to save time. + + Args: + res (list): Torrent files list. + + Returns: + TorNode: Parent node of the tree constructed and can be used further. + """ + parent = TorNode("Torrent") + #nodes = dict() + l = 0 + + for i in res: + # Get the hierarchy of the folders by splitting based on '/' + folders = get_folders(i.name) + # Check if the file is alone for if its in folder + if len(folders) > 1: + # Enter here if in folder + + # Set the parent + previous_node = parent + + # Traverse till second last assuming the last is a file. + for j in range(len(folders)-1): + current_node = None + + if previous_node is not None: + # As we are traversing the folder from top to bottom we are searching + # the first folder (folders list) under the parent node in first iteration. + # If the node is found then it becomes the current node else the current node + # is left None. + for k in previous_node.children: + if k.name == folders[j]: + current_node = k + break + else: + # think its useless afterall + for k in parent.children: + if k.name == folders[j]: + current_node = k + break + + # if the node is not found then create the folder node + # if the node is found then use it as base for the next + if current_node is None: + previous_node = TorNode(folders[j],parent=previous_node,is_folder=True) + else: + previous_node = current_node + # at this point the previous_node will contain the deepest folder in it so add the file to it + TorNode(folders[-1],is_file=True,parent=previous_node,progress=i.progress,size=i.size,priority=i.priority,file_id=l) + l += 1 + else: + # at the file to the parent if no folders are there + TorNode(folders[-1],is_file=True,parent=parent,progress=i.progress,size=i.size,priority=i.priority,file_id=l) + l += 1 + + + return parent + + +def print_tree(parent): + for pre, _, node in RenderTree(parent): + treestr = u"%s%s" % (pre, node.name) + print(treestr.ljust(8), node.is_folder, node.is_file) + + +def create_list(par, msg): + if par.name != ".unwanted": + msg[0] += "" + +def get_readable_file_size(size_in_bytes) -> str: + if size_in_bytes is None: + return '0B' + index = 0 + while size_in_bytes >= 1024: + size_in_bytes /= 1024 + index += 1 + try: + return f'{round(size_in_bytes, 2)}{SIZE_UNITS[index]}' + except IndexError: + return 'File too large' + diff --git a/requirements.txt b/requirements.txt index 7a9ee73..857f5e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ aiohttp +anytree aria2p appdirs beautifulsoup4 @@ -8,6 +9,7 @@ gitpython google-api-python-client google-auth-httplib2 google-auth-oauthlib +gunicorn heroku3 js2py lk21 @@ -19,9 +21,12 @@ pyrogram python-dotenv python-magic python-telegram-bot +qbittorrent-api requests speedtest-cli telegraph tenacity TgCrypto +torrentool youtube_dl +urllib3 diff --git a/start.sh b/start.sh index 3afbb78..db2ce37 100755 --- a/start.sh +++ b/start.sh @@ -8,4 +8,4 @@ if [[ -n $ACCOUNTS_ZIP_URL ]]; then rm accounts.zip fi -./aria.sh; python3 -m bot +gunicorn wserver:start_server --bind 0.0.0.0:$PORT --worker-class aiohttp.GunicornWebWorker & qbittorrent-nox -d --webui-port=8090 & python3 alive.py & ./aria.sh; python3 -m bot diff --git a/wserver.py b/wserver.py new file mode 100644 index 0000000..22d2917 --- /dev/null +++ b/wserver.py @@ -0,0 +1,309 @@ +# -*- coding: utf-8 -*- +# (c) YashDK [yash-dk@github] + +import os +import time +import logging +import qbittorrentapi as qba +import asyncio + +from aiohttp import web +import nodes + +LOGGER = logging.getLogger(__name__) + +routes = web.RouteTableDef() + +page = """ + + + + + + + +

Slam-mirror-bot : Github

+
+ +{My_content} + + +
+ + + + + +""" + +code_page = """ + + + + +Slam Torrent Files + + + + +
+
+
+ + + Dont mess around. You download will get messed up. +
+ +
+
+ + +""" + + +@routes.get('/slam/files/{hash_id}') +async def list_torrent_contents(request): + + torr = request.match_info["hash_id"] + + gets = request.query + + if not "pin_code" in gets.keys(): + rend_page = code_page.replace("{form_url}",f"/slam/files/{torr}") + return web.Response(text=rend_page,content_type='text/html') + + + client = qba.Client(host="localhost",port="8090",username="admin",password="adminadmin") + client.auth_log_in() + try: + res = client.torrents_files(torrent_hash=torr) + except qba.NotFound404Error: + raise web.HTTPNotFound() + count = 0 + passw = "" + for n in str(torr): + if n.isdigit(): + passw += str(n) + count += 1 + if count == 4: + break + if isinstance(passw, bool): + raise web.HTTPNotFound() + pincode = passw + if gets["pin_code"] != pincode: + return web.Response(text="Incorrect pin code") + + + par = nodes.make_tree(res) + + cont = ["",0] + nodes.create_list(par,cont) + + rend_page = page.replace("{My_content}",cont[0]) + rend_page = rend_page.replace("{form_url}",f"/slam/files/{torr}?pin_code={pincode}") + client.auth_log_out() + return web.Response(text=rend_page,content_type='text/html') + + +async def re_verfiy(paused, resumed, client, torr): + + paused = paused.strip() + resumed = resumed.strip() + if paused: + paused = paused.split("|") + if resumed: + resumed = resumed.split("|") + k = 0 + while True: + + res = client.torrents_files(torrent_hash=torr) + verify = True + + for i in res: + if str(i.id) in paused: + if i.priority == 0: + continue + else: + verify = False + break + + if str(i.id) in resumed: + if i.priority != 0: + continue + else: + verify = False + break + + + if not verify: + LOGGER.info("Reverification Failed :- correcting stuff") + # reconnect and issue the request again + client.auth_log_out() + client = qba.Client(host="localhost",port="8090",username="admin",password="adminadmin") + client.auth_log_in() + try: + client.torrents_file_priority(torrent_hash=torr,file_ids=paused,priority=0) + except: + LOGGER.error("Errored in reverification paused") + try: + client.torrents_file_priority(torrent_hash=torr,file_ids=resumed,priority=1) + except: + LOGGER.error("Errored in reverification resumed") + client.auth_log_out() + else: + break + k += 1 + if k >= 2: + # avoid an infite loop here + return False + return True + + + +@routes.post('/slam/files/{hash_id}') +async def set_priority(request): + + torr = request.match_info["hash_id"] + client = qba.Client(host="localhost",port="8090",username="admin",password="adminadmin") + client.auth_log_in() + + data = await request.post() + resume = "" + pause = "" + data = dict(data) + + for i in data.keys(): + if i.find("filenode") != -1: + node_no = i.split("_")[-1] + + if data[i] == "on": + resume += f"{node_no}|" + else: + pause += f"{node_no}|" + + pause = pause.strip("|") + resume = resume.strip("|") + LOGGER.info(f"Paused {pause} of {torr}") + LOGGER.info(f"Resumed {resume} of {torr}") + + try: + client.torrents_file_priority(torrent_hash=torr,file_ids=pause,priority=0) + except qba.NotFound404Error: + raise web.HTTPNotFound() + except: + LOGGER.info("Errored in paused") + + try: + client.torrents_file_priority(torrent_hash=torr,file_ids=resume,priority=1) + except qba.NotFound404Error: + raise web.HTTPNotFound() + except: + LOGGER.info("Errored in resumed") + + await asyncio.sleep(2) + if not await re_verfiy(pause,resume,client,torr): + LOGGER.error("The torrent choose errored reverification failed") + client.auth_log_out() + return await list_torrent_contents(request) + +@routes.get('/') +async def homepage(request): + + return web.Response(text="

See slam-mirror-bot @GitHubBy Slam

",content_type="text/html") + +async def e404_middleware(app, handler): + + async def middleware_handler(request): + + try: + response = await handler(request) + if response.status == 404: + return web.Response(text="

404: Page not found


Slam

",content_type="text/html") + return response + except web.HTTPException as ex: + if ex.status == 404: + return web.Response(text="

404: Page not found


Slam

",content_type="text/html") + raise + return middleware_handler + +async def start_server(): + + app = web.Application(middlewares=[e404_middleware]) + app.add_routes(routes) + return app + +async def start_server_async(port = 8080): + + app = web.Application(middlewares=[e404_middleware]) + app.add_routes(routes) + runner = web.AppRunner(app) + await runner.setup() + await web.TCPSite(runner,"0.0.0.0", port).start()