diff --git a/Dockerfile b/Dockerfile index ff19a0fe2..57ce1bb4b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM 5hojib/aeon:latest -WORKDIR /usr/src/app -RUN chmod 777 /usr/src/app +WORKDIR /app +RUN chmod 777 /app RUN uv venv COPY requirements.txt . diff --git a/bot/__init__.py b/bot/__init__.py index 44cb46bd8..8745656fa 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -70,9 +70,11 @@ def format(self, record: LogRecord) -> str: LOGGER = getLogger(__name__) -cpu_no = os.cpu_count() +cpu_no = 8 +threads = 4 +cores = "0,1,2,3" -DOWNLOAD_DIR = "/usr/src/app/downloads/" +DOWNLOAD_DIR = "/app/downloads/" intervals = { "status": {}, "qb": "", @@ -94,6 +96,7 @@ def format(self, record: LogRecord) -> str: rss_dict = {} auth_chats = {} excluded_extensions = ["aria2", "!qB"] +included_extensions = [] drives_names = [] drives_ids = [] index_urls = [] diff --git a/bot/__main__.py b/bot/__main__.py index 675ad2f69..305715a4a 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -14,7 +14,7 @@ bot_loop.run_until_complete(load_settings()) -from .core.aeon_client import TgClient +from .core.telegram_manager import TgClient from .helper.telegram_helper.bot_commands import BotCommands COMMANDS = { diff --git a/bot/core/config_manager.py b/bot/core/config_manager.py index e78cef5df..5b3ba44f7 100644 --- a/bot/core/config_manager.py +++ b/bot/core/config_manager.py @@ -18,6 +18,7 @@ class Config: DATABASE_URL: str = "" DEFAULT_UPLOAD: str = "gd" EXCLUDED_EXTENSIONS: str = "" + INCLUDED_EXTENSIONS: str = "" FFMPEG_CMDS: ClassVar[dict[str, list[str]]] = {} FILELION_API: str = "" GDRIVE_ID: str = "" @@ -34,7 +35,7 @@ class Config: HYBRID_LEECH: bool = False HYDRA_IP: str = "" HYDRA_API_KEY: str = "" - NAME_SUBSTITUTE: str = "" + NAME_SUBSTITUTE: str = r"" OWNER_ID: int = 0 QUEUE_ALL: int = 0 QUEUE_DOWNLOAD: int = 0 diff --git a/bot/core/handlers.py b/bot/core/handlers.py index f7dafdbe6..117464da7 100644 --- a/bot/core/handlers.py +++ b/bot/core/handlers.py @@ -10,7 +10,7 @@ from bot.helper.telegram_helper.filters import CustomFilters from bot.modules import * -from .aeon_client import TgClient +from .telegram_manager import TgClient def add_handlers(): diff --git a/bot/core/jdownloader_booter.py b/bot/core/jdownloader_booter.py index f7e8ee46c..800dce0a9 100644 --- a/bot/core/jdownloader_booter.py +++ b/bot/core/jdownloader_booter.py @@ -9,8 +9,8 @@ from bot.helper.ext_utils.bot_utils import cmd_exec, new_task from myjd import MyJdApi -from .aeon_client import TgClient from .config_manager import Config +from .telegram_manager import TgClient class JDownloader(MyJdApi): diff --git a/bot/core/startup.py b/bot/core/startup.py index 4bb33c3d2..8024f2cb8 100644 --- a/bot/core/startup.py +++ b/bot/core/startup.py @@ -1,4 +1,4 @@ -from asyncio import create_subprocess_exec, create_subprocess_shell +from asyncio import create_subprocess_exec, create_subprocess_shell, sleep from os import environ import aiohttp @@ -14,6 +14,7 @@ drives_ids, drives_names, excluded_extensions, + included_extensions, index_urls, nzb_options, qbit_options, @@ -25,13 +26,14 @@ ) from bot.helper.ext_utils.db_handler import database -from .aeon_client import TgClient from .config_manager import Config +from .telegram_manager import TgClient from .torrent_manager import TorrentManager async def update_qb_options(): """Updates qBittorrent options either from current preferences or saved configuration.""" + LOGGER.info("Get qBittorrent options from server") if not qbit_options: opt = await TorrentManager.qbittorrent.app.preferences() qbit_options.update(opt) @@ -49,6 +51,7 @@ async def update_qb_options(): async def update_aria2_options(): """Updates Aria2c global options either from current settings or saved configuration.""" + LOGGER.info("Get aria2 options from server") if not aria2_options: op = await TorrentManager.aria2.getGlobalOption() aria2_options.update(op) @@ -58,8 +61,15 @@ async def update_aria2_options(): async def update_nzb_options(): """Updates NZB options from Sabnzbd client configuration.""" - no = (await sabnzbd_client.get_config())["config"]["misc"] - nzb_options.update(no) + LOGGER.info("Get SABnzbd options from server") + while True: + try: + no = (await sabnzbd_client.get_config())["config"]["misc"] + nzb_options.update(no) + except Exception: + await sleep(0.5) + continue + break async def load_settings(): @@ -260,6 +270,11 @@ async def update_variables(): x = x.lstrip(".") excluded_extensions.append(x.strip().lower()) + if Config.INCLUDED_EXTENSIONS: + fx = Config.INCLUDED_EXTENSIONS.split() + for x in fx: + x = x.lstrip(".") + included_extensions.append(x.strip().lower()) if Config.GDRIVE_ID: drives_names.append("Main") drives_ids.append(Config.GDRIVE_ID) @@ -273,7 +288,7 @@ async def update_variables(): drives_ids.append(temp[1]) drives_names.append(temp[0].replace("_", " ")) if len(temp) > 2: - index_urls.append(temp[2].strip("/")) + index_urls.append(temp[2]) else: index_urls.append("") diff --git a/bot/core/aeon_client.py b/bot/core/telegram_manager.py similarity index 83% rename from bot/core/aeon_client.py rename to bot/core/telegram_manager.py index e10abbdae..9e9ef7f76 100644 --- a/bot/core/aeon_client.py +++ b/bot/core/telegram_manager.py @@ -1,6 +1,7 @@ from asyncio import Lock from pyrogram import Client, enums +from pyrogram.types import LinkPreviewOptions from bot import LOGGER @@ -26,9 +27,13 @@ async def start_bot(cls): Config.TELEGRAM_HASH, proxy=Config.TG_PROXY, bot_token=Config.BOT_TOKEN, - workdir="/usr/src/app", + workdir="/app", parse_mode=enums.ParseMode.HTML, max_concurrent_transmissions=100, + max_message_cache_size=15000, + max_topic_cache_size=15000, + sleep_threshold=0, + link_preview_options=LinkPreviewOptions(is_disabled=True), ) await cls.bot.start() cls.NAME = cls.bot.me.username @@ -44,9 +49,13 @@ async def start_user(cls): Config.TELEGRAM_HASH, proxy=Config.TG_PROXY, session_string=Config.USER_SESSION_STRING, + workdir="/app", parse_mode=enums.ParseMode.HTML, no_updates=True, max_concurrent_transmissions=100, + max_message_cache_size=15000, + max_topic_cache_size=15000, + link_preview_options=LinkPreviewOptions(is_disabled=True), ) await cls.user.start() cls.IS_PREMIUM_USER = cls.user.me.is_premium diff --git a/bot/helper/aeon_utils/access_check.py b/bot/helper/aeon_utils/access_check.py index 76ab7538a..a129c2cde 100644 --- a/bot/helper/aeon_utils/access_check.py +++ b/bot/helper/aeon_utils/access_check.py @@ -8,8 +8,8 @@ LOGGER, user_data, ) -from bot.core.aeon_client import TgClient from bot.core.config_manager import Config +from bot.core.telegram_manager import TgClient from bot.helper.aeon_utils.shorteners import short from bot.helper.ext_utils.db_handler import database from bot.helper.ext_utils.help_messages import nsfw_keywords diff --git a/bot/helper/common.py b/bot/helper/common.py index 0d0500a5c..c6bf27ce2 100644 --- a/bot/helper/common.py +++ b/bot/helper/common.py @@ -17,16 +17,18 @@ from bot import ( DOWNLOAD_DIR, LOGGER, + cores, cpu_eater_lock, excluded_extensions, + included_extensions, intervals, multi_tags, task_dict, task_dict_lock, user_data, ) -from bot.core.aeon_client import TgClient from bot.core.config_manager import Config +from bot.core.telegram_manager import TgClient from bot.helper.aeon_utils.command_gen import ( get_embed_thumb_cmd, get_metadata_cmd, @@ -130,12 +132,14 @@ def __init__(self): self.is_file = False self.bot_trans = False self.user_trans = False + self.is_rss = False self.progress = True self.ffmpeg_cmds = None self.chat_thread_id = None self.subproc = None self.thumb = None self.excluded_extensions = [] + self.included_extensions = [] self.files_to_proceed = [] self.is_super_chat = self.message.chat.type.name in [ "SUPERGROUP", @@ -226,6 +230,11 @@ async def before_start(self): if "EXCLUDED_EXTENSIONS" not in self.user_dict else ["aria2", "!qB"] ) + self.included_extensions = self.user_dict.get("INCLUDED_EXTENSIONS") or ( + included_extensions + if "INCLUDED_EXTENSIONS" not in self.user_dict + else [] + ) if not self.rc_flags: if self.user_dict.get("RCLONE_FLAGS"): self.rc_flags = self.user_dict["RCLONE_FLAGS"] @@ -268,41 +277,44 @@ async def before_start(self): if self.up_dest in self.user_dict["UPLOAD_PATHS"]: self.up_dest = self.user_dict["UPLOAD_PATHS"][self.up_dest] elif ( - "UPLOAD_PATHS" not in self.user_dict + ( + "UPLOAD_PATHS" not in self.user_dict + or not self.user_dict["UPLOAD_PATHS"] + ) and Config.UPLOAD_PATHS and self.up_dest in Config.UPLOAD_PATHS ): self.up_dest = Config.UPLOAD_PATHS[self.up_dest] - if self.ffmpeg_cmds and not isinstance(self.ffmpeg_cmds, list): + if self.ffmpeg_cmds: if self.user_dict.get("FFMPEG_CMDS", None): ffmpeg_dict = deepcopy(self.user_dict["FFMPEG_CMDS"]) - elif "FFMPEG_CMDS" not in self.user_dict and Config.FFMPEG_CMDS: + elif ( + "FFMPEG_CMDS" not in self.user_dict + or not self.user_dict["FFMPEG_CMDS"] + ) and Config.FFMPEG_CMDS: ffmpeg_dict = deepcopy(Config.FFMPEG_CMDS) else: ffmpeg_dict = None - if ffmpeg_dict is None: - self.ffmpeg_cmds = ffmpeg_dict - else: - cmds = [] - for key in list(self.ffmpeg_cmds): - if isinstance(key, tuple): - cmds.extend(list(key)) - elif key in ffmpeg_dict: - for ind, vl in enumerate(ffmpeg_dict[key]): - if variables := set(findall(r"\{(.*?)\}", vl)): - ff_values = ( - self.user_dict.get("FFMPEG_VARIABLES", {}) - .get(key, {}) - .get(str(ind), {}) - ) - if Counter(list(variables)) == Counter( - list(ff_values.keys()) - ): - cmds.append(vl.format(**ff_values)) - else: - cmds.append(vl) - self.ffmpeg_cmds = cmds + cmds = [] + for key in list(self.ffmpeg_cmds): + if isinstance(key, tuple): + cmds.extend(list(key)) + elif ffmpeg_dict is not None and key in ffmpeg_dict: + for ind, vl in enumerate(ffmpeg_dict[key]): + if variables := set(findall(r"\{(.*?)\}", vl)): + ff_values = ( + self.user_dict.get("FFMPEG_VARIABLES", {}) + .get(key, {}) + .get(str(ind), {}) + ) + if Counter(list(variables)) == Counter( + list(ff_values.keys()) + ): + cmds.append(vl.format(**ff_values)) + else: + cmds.append(vl) + self.ffmpeg_cmds = cmds if not self.is_leech: self.stop_duplicate = self.user_dict.get("STOP_DUPLICATE") or ( "STOP_DUPLICATE" not in self.user_dict and Config.STOP_DUPLICATE @@ -343,20 +355,18 @@ async def before_start(self): raise ValueError( "GoFile API token not configured! Please set your GoFile token in user settings or configure a global token." ) - if is_gdrive_id(self.up_dest): - if not self.up_dest.startswith( - ("mtp:", "tp:", "sa:"), - ) and self.user_dict.get("USER_TOKENS", False): - self.up_dest = f"mtp:{self.up_dest}" - elif is_rclone_path(self.up_dest): - if not self.up_dest.startswith("mrcc:") and self.user_dict.get( - "USER_TOKENS", - False, - ): - self.up_dest = f"mrcc:{self.up_dest}" - self.up_dest = self.up_dest.strip("/") - if self.up_dest not in ["rcl", "gdl"]: + if is_gdrive_id(self.up_dest): + if not self.up_dest.startswith( + ("mtp:", "tp:", "sa:") + ) and self.user_dict.get("USER_TOKENS", False): + self.up_dest = f"mtp:{self.up_dest}" + elif is_rclone_path(self.up_dest): + if not self.up_dest.startswith("mrcc:") and self.user_dict.get( + "USER_TOKENS", False + ): + self.up_dest = f"mrcc:{self.up_dest}" + self.up_dest = self.up_dest.strip("/") await self.is_token_exists(self.up_dest, "up") if self.up_dest == "rcl": @@ -441,26 +451,36 @@ async def before_start(self): except Exception: chat = None if chat is None: + LOGGER.warning( + "Account of user session can't find the the destination chat!" + ) self.user_transmission = False self.hybrid_leech = False - else: - uploader_id = TgClient.user.me.id - if chat.type.name not in [ - "SUPERGROUP", - "CHANNEL", - "GROUP", - "FORUM", - ]: + elif chat.type.name not in [ + "SUPERGROUP", + "CHANNEL", + "GROUP", + "FORUM", + ]: + self.user_transmission = False + self.hybrid_leech = False + elif chat.is_admin: + member = await chat.get_member(TgClient.user.me.id) + if ( + not member.privileges.can_manage_chat + or not member.privileges.can_delete_messages + ): self.user_transmission = False self.hybrid_leech = False - else: - member = await chat.get_member(uploader_id) - if ( - not member.privileges.can_manage_chat - or not member.privileges.can_delete_messages - ): - self.user_transmission = False - self.hybrid_leech = False + LOGGER.warning( + "Enable manage chat and delete messages to account of the user session from administration settings!" + ) + else: + LOGGER.warning( + "Promote the account of the user session to admin in the chat to get the benefit of user transmission!" + ) + self.user_transmission = False + self.hybrid_leech = False if not self.user_transmission or self.hybrid_leech: try: @@ -472,34 +492,36 @@ async def before_start(self): self.hybrid_leech = False else: raise ValueError("Chat not found!") - else: - uploader_id = self.client.me.id - if chat.type.name in [ - "SUPERGROUP", - "CHANNEL", - "GROUP", - "FORUM", - ]: - member = await chat.get_member(uploader_id) - if ( - not member.privileges.can_manage_chat - or not member.privileges.can_delete_messages - ): - if not self.user_transmission: - raise ValueError( - "You don't have enough privileges in this chat!", - ) - self.hybrid_leech = False - else: - try: - await self.client.send_chat_action( - self.up_dest, - ChatAction.TYPING, - ) - except Exception: + elif chat.type.name in [ + "SUPERGROUP", + "CHANNEL", + "GROUP", + "FORUM", + ]: + if not chat.is_admin: + raise ValueError( + "Bot is not admin in the destination chat!" + ) + member = await chat.get_member(self.client.me.id) + if ( + not member.privileges.can_manage_chat + or not member.privileges.can_delete_messages + ): + if not self.user_transmission: raise ValueError( - "Start the bot and try again!", - ) from None + "You don't have enough privileges in this chat! Enable manage chat and delete messages for this bot!" + ) + self.hybrid_leech = False + else: + try: + await self.client.send_chat_action( + self.up_dest, + ChatAction.TYPING, + ) + except Exception: + raise ValueError( + "Start the bot and try again!", + ) from None elif ( self.user_transmission or self.hybrid_leech ) and not self.is_super_chat: @@ -623,13 +645,16 @@ def get_cleaned_value(value, default, allowed=None, to_lower=False): async def get_tag(self, text: list): if len(text) > 1 and text[1].startswith("Tag: "): + self.is_rss = True user_info = text[1].split("Tag: ") if len(user_info) >= 3: id_ = user_info[-1] self.tag = " ".join(user_info[:-1]) else: self.tag, id_ = text[1].split("Tag: ")[1].split() - self.user = self.message.from_user = await self.client.get_users(id_) + self.user = self.message.from_user = await self.client.get_users( + int(id_) + ) self.user_id = self.user.id self.user_dict = user_data.get(self.user_id, {}) with contextlib.suppress(Exception): @@ -714,7 +739,7 @@ async def init_bulk(self, input_list, bulk_start, bulk_end, obj): index = self.options.index("-b") del self.options[index] if bulk_start or bulk_end: - del self.options[index + 1] + del self.options[index] self.options = " ".join(self.options) b_msg.append(f"{self.bulk[0]} -i {len(self.bulk)} {self.options}") msg = " ".join(b_msg) @@ -822,6 +847,9 @@ async def proceed_ffmpeg(self, dl_path, gid): for ffmpeg_cmd in cmds: self.proceed_count = 0 cmd = [ + "taskset", + "-c", + f"{cores}", "xtra", "-hide_banner", "-loglevel", @@ -840,10 +868,17 @@ async def proceed_ffmpeg(self, dl_path, gid): input_indexes = [ index for index, value in enumerate(cmd) if value == "-i" ] - for index in input_indexes: - if cmd[index + 1].startswith("mltb"): - input_file = cmd[index + 1] - break + input_file = next( + ( + cmd[index + 1] + for index in input_indexes + if cmd[index + 1].startswith("mltb") + ), + "", + ) + if not input_file: + LOGGER.error("Wrong FFmpeg cmd!") + return dl_path if input_file.lower().endswith(".video"): ext = "video" elif input_file.lower().endswith(".audio"): @@ -886,16 +921,17 @@ async def proceed_ffmpeg(self, dl_path, gid): await cpu_eater_lock.acquire() self.progress = True LOGGER.info(f"Running FFmpeg command for: {file_path}") + var_cmd = cmd.copy() for index in input_indexes: if cmd[index + 1].startswith("mltb"): - cmd[index + 1] = file_path + var_cmd[index + 1] = file_path elif is_telegram_link(cmd[index + 1]): msg = (await get_tg_link_message(cmd[index + 1]))[0] file_dir = await temp_download(msg) inputs[index + 1] = file_dir - cmd[index + 1] = file_dir + var_cmd[index + 1] = file_dir self.subsize = self.size - res = await ffmpeg.ffmpeg_cmds(cmd, file_path) + res = await ffmpeg.ffmpeg_cmds(var_cmd, file_path) if res: if delete_files: await remove(file_path) @@ -943,7 +979,16 @@ async def proceed_ffmpeg(self, dl_path, gid): ): continue self.proceed_count += 1 - var_cmd[index + 1] = f_path + for index in input_indexes: + if cmd[index + 1].startswith("mltb"): + var_cmd[index + 1] = f_path + elif is_telegram_link(cmd[index + 1]): + msg = ( + await get_tg_link_message(cmd[index + 1]) + )[0] + file_dir = await temp_download(msg) + inputs[index + 1] = file_dir + var_cmd[index + 1] = file_dir if not checked: checked = True async with task_dict_lock: @@ -983,6 +1028,8 @@ def perform_substitution(name, substitutions): for substitution in substitutions: sen = False pattern = substitution[0] + if pattern.startswith('"') and pattern.endswith('"'): + pattern = pattern.strip('"') if len(substitution) > 1: if len(substitution) > 2: sen = substitution[2] == "s" @@ -995,7 +1042,7 @@ def perform_substitution(name, substitutions): res = "" try: name = sub( - rf"{pattern}", + pattern, res, name, flags=IGNORECASE if sen else 0, diff --git a/bot/helper/ext_utils/bot_utils.py b/bot/helper/ext_utils/bot_utils.py index d3ca3007e..e6ee53180 100644 --- a/bot/helper/ext_utils/bot_utils.py +++ b/bot/helper/ext_utils/bot_utils.py @@ -192,6 +192,9 @@ def arg_parser(items, arg_base): sub_list = [] for j in range(i + 1, total): if items[j] in arg_base: + if part == "-c" and items[j] == "-c": + sub_list.append(items[j]) + continue if part in bool_arg_set and not sub_list: arg_base[part] = True break diff --git a/bot/helper/ext_utils/bulk_links.py b/bot/helper/ext_utils/bulk_links.py index 5519eb46b..9183b2df3 100644 --- a/bot/helper/ext_utils/bulk_links.py +++ b/bot/helper/ext_utils/bulk_links.py @@ -2,25 +2,10 @@ from aiofiles.os import remove -def filter_links(links_list: list, bulk_start: int, bulk_end: int) -> list: - """ - Filters a list of links based on start and end indices. - - Args: - links_list: The list of links to filter. - bulk_start: The starting index (1-based). If 0, no start filtering. - bulk_end: The ending index. If 0, no end filtering. - - Returns: - The filtered list of links. - """ - if bulk_start != 0 and bulk_end != 0: - links_list = links_list[bulk_start:bulk_end] - elif bulk_start != 0: - links_list = links_list[bulk_start:] - elif bulk_end != 0: - links_list = links_list[:bulk_end] - return links_list +def filter_links(links_list, bulk_start, bulk_end): + start = bulk_start if bulk_start > 0 else None + end = bulk_end if bulk_end > 0 else None + return links_list[start:end] def get_links_from_message(text: str) -> list: diff --git a/bot/helper/ext_utils/db_handler.py b/bot/helper/ext_utils/db_handler.py index 659ae1283..44f6b38ad 100644 --- a/bot/helper/ext_utils/db_handler.py +++ b/bot/helper/ext_utils/db_handler.py @@ -7,8 +7,8 @@ from pymongo.server_api import ServerApi from bot import LOGGER, qbit_options, rss_dict, user_data -from bot.core.aeon_client import TgClient from bot.core.config_manager import Config +from bot.core.telegram_manager import TgClient class DbManager: @@ -28,6 +28,8 @@ async def connect(self): self._conn = AsyncMongoClient( Config.DATABASE_URL, server_api=ServerApi("1"), + connectTimeoutMS=60000, + serverSelectionTimeoutMS=60000, ) self.db = self._conn.luna self._return = False diff --git a/bot/helper/ext_utils/files_utils.py b/bot/helper/ext_utils/files_utils.py index 0aae3c2a1..08cf2d5dd 100644 --- a/bot/helper/ext_utils/files_utils.py +++ b/bot/helper/ext_utils/files_utils.py @@ -1,4 +1,4 @@ -from asyncio import create_subprocess_exec, sleep, wait_for +from asyncio import create_subprocess_exec, wait_for from asyncio.subprocess import PIPE from os import path as ospath from os import readlink, walk @@ -237,11 +237,23 @@ def get_mime_type(file_path: str) -> str: async def remove_excluded_files(fpath, ee): for root, _, files in await sync_to_async(walk, fpath): + if root.strip().endswith("/yt-dlp-thumb"): + continue for f in files: if f.strip().lower().endswith(tuple(ee)): await remove(ospath.join(root, f)) +async def remove_non_included_files(fpath, ie): + for root, _, files in await sync_to_async(walk, fpath): + if root.strip().endswith("/yt-dlp-thumb"): + continue + for f in files: + if f.strip().lower().endswith(tuple(ie)): + continue + await remove(ospath.join(root, f)) + + async def join_files(opath): files = await listdir(opath) results = [] @@ -317,7 +329,7 @@ def progress(self): return self._percentage async def _sevenz_progress(self): - pattern = r"(\d+)\s+bytes|Total Physical Size\s*=\s*(\d+)" + pattern = r"(\d+)\s+bytes|Total Physical Size\s*=\s*(\d+)|Physical Size\s*=\s*(\d+)" while not ( self._listener.subproc.returncode is not None or self._listener.is_cancelled @@ -328,9 +340,18 @@ async def _sevenz_progress(self): except Exception: break line = line.decode().strip() + if "%" in line: + perc = line.split("%", 1)[0] + if perc.isdigit(): + self._percentage = f"{perc}%" + self._processed_bytes = ( + int(perc) / 100 + ) * self._listener.subsize + else: + self._percentage = "0%" + continue if match := re_search(pattern, line): - self._listener.subsize = int(match[1] or match[2]) - await sleep(0.05) + self._listener.subsize = int(match[1] or match[2] or match[3]) s = b"" while not ( self._listener.is_cancelled @@ -354,7 +375,6 @@ async def _sevenz_progress(self): self._processed_bytes = 0 self._percentage = "0%" s = b"" - await sleep(0.05) self._processed_bytes = 0 self._percentage = "0%" diff --git a/bot/helper/ext_utils/help_messages.py b/bot/helper/ext_utils/help_messages.py index f0ad3e8f7..fa22baf12 100644 --- a/bot/helper/ext_utils/help_messages.py +++ b/bot/helper/ext_utils/help_messages.py @@ -1,4 +1,4 @@ -from bot.core.aeon_client import TgClient +from bot.core.telegram_manager import TgClient from bot.helper.telegram_helper.bot_commands import BotCommands nsfw_keywords = [ @@ -277,7 +277,7 @@ Examples: ["-i mltb.mkv -c copy -c:s srt mltb.mkv", "-i mltb.video -c copy -c:s srt mltb", "-i mltb.m4a -c:a libmp3lame -q:a 2 mltb.mp3", "-i mltb.audio -c:a libmp3lame -q:a 2 mltb.mp3", "-i mltb -map 0:a -c copy mltb.mka -map 0:s -c copy mltb.srt", "-i mltb -i tg://openmessage?user_id=5272663208&message_id=322801 -filter_complex 'overlay=W-w-10:H-h-10' -c:a copy mltb"] Here I will explain how to use mltb.* which is reference to files you want to work on. 1. First cmd: the input is mltb.mkv so this cmd will work only on mkv videos and the output is mltb.mkv also so all outputs is mkv. -del will delete the original media after complete run of the cmd. -2. Second cmd: the input is mltb.video so this cmd will work on all videos and the output is only mltb so the extenstion is same as input files. +2. Second cmd: the input is mltb.video so this cmd will work on all videos and the output is only mltb so the extension is same as input files. 3. Third cmd: the input in mltb.m4a so this cmd will work only on m4a audios and the output is mltb.mp3 so the output extension is mp3. 4. Fourth cmd: the input is mltb.audio so this cmd will work on all audios and the output is mltb.mp3 so the output extension is mp3. 5. Fifth cmd: You can add telegram link for small size input like photo to set watermark""" @@ -391,7 +391,8 @@ "GDRIVE_ID": "Send Gdrive ID. If you want to use your token.pickle edit using owner/user token from usetting or add mtp: before the id. Example: mtp:F435RGGRDXXXXXX . Timeout: 60 sec", "INDEX_URL": "Send Index URL. Timeout: 60 sec", "UPLOAD_PATHS": "Send Dict of keys that have path values. Example: {'path 1': 'remote:rclonefolder', 'path 2': 'gdrive1 id', 'path 3': 'tg chat id', 'path 4': 'mrcc:remote:', 'path 5': b:@username} . Timeout: 60 sec", - "EXCLUDED_EXTENSIONS": "Send exluded extenions separated by space without dot at beginning. Timeout: 60 sec", + "EXCLUDED_EXTENSIONS": "Send excluded extensions separated by space without dot at beginning. Timeout: 60 sec", + "INCLUDED_EXTENSIONS": "Send included extensions separated by space without dot at beginning. Timeout: 60 sec", "NAME_SUBSTITUTE": r"""Word Subtitions. You can add pattern instead of normal text. Timeout: 60 sec NOTE: You must add \ before any character, those are the characters: \^$.|?*+()[]{}- Example: script/code/s | mirror/leech | tea/ /s | clone | cpu/ | \[mltb\]/mltb | \\text\\/text/s diff --git a/bot/helper/ext_utils/media_utils.py b/bot/helper/ext_utils/media_utils.py index a70e756b6..b5b2c38f2 100644 --- a/bot/helper/ext_utils/media_utils.py +++ b/bot/helper/ext_utils/media_utils.py @@ -1,5 +1,5 @@ import contextlib -from asyncio import create_subprocess_exec, gather, sleep, wait_for +from asyncio import create_subprocess_exec, gather, wait_for from asyncio.subprocess import PIPE from os import path as ospath from re import escape @@ -11,7 +11,7 @@ from aioshutil import rmtree from PIL import Image -from bot import DOWNLOAD_DIR, LOGGER, cpu_no +from bot import DOWNLOAD_DIR, LOGGER, cores, threads from .bot_utils import cmd_exec, sync_to_async from .files_utils import get_mime_type, is_archive, is_archive_split @@ -130,6 +130,9 @@ async def take_ss(video_file, ss_nb) -> bool: for i in range(ss_nb): output = f"{dirpath}/SS.{name}_{i:02}.png" cmd = [ + "taskset", + "-c", + f"{cores}", "xtra", "-hide_banner", "-loglevel", @@ -143,7 +146,7 @@ async def take_ss(video_file, ss_nb) -> bool: "-frames:v", "1", "-threads", - f"{max(1, cpu_no // 2)}", + f"{threads}", output, ] cap_time += interval @@ -152,13 +155,13 @@ async def take_ss(video_file, ss_nb) -> bool: resutls = await wait_for(gather(*cmds), timeout=60) if resutls[0][2] != 0: LOGGER.error( - f"Error while creating sreenshots from video. Path: {video_file}. stderr: {resutls[0][1]}", + f"Error while creating screenshots from video. Path: {video_file}. stderr: {resutls[0][1]}", ) await rmtree(dirpath, ignore_errors=True) return False except Exception: LOGGER.error( - f"Error while creating sreenshots from video. Path: {video_file}. Error: Timeout some issues with ffmpeg with specific arch!", + f"Error while creating screenshots from video. Path: {video_file}. Error: Timeout some issues with ffmpeg with specific arch!", ) await rmtree(dirpath, ignore_errors=True) return False @@ -172,6 +175,9 @@ async def get_audio_thumbnail(audio_file): await makedirs(output_dir, exist_ok=True) output = ospath.join(output_dir, f"{time()}.jpg") cmd = [ + "taskset", + "-c", + f"{cores}", "xtra", "-hide_banner", "-loglevel", @@ -182,7 +188,7 @@ async def get_audio_thumbnail(audio_file): "-vcodec", "copy", "-threads", - f"{max(1, cpu_no // 2)}", + f"{threads}", output, ] try: @@ -211,6 +217,9 @@ async def get_video_thumbnail(video_file, duration): duration = 3 duration = duration // 2 cmd = [ + "taskset", + "-c", + f"{cores}", "xtra", "-hide_banner", "-loglevel", @@ -254,6 +263,9 @@ async def get_multiple_frames_thumbnail(video_file, layout, keep_screenshots): await makedirs(output_dir, exist_ok=True) output = ospath.join(output_dir, f"{time()}.jpg") cmd = [ + "taskset", + "-c", + f"{cores}", "xtra", "-hide_banner", "-loglevel", @@ -271,7 +283,7 @@ async def get_multiple_frames_thumbnail(video_file, layout, keep_screenshots): "-f", "mjpeg", "-threads", - f"{max(1, cpu_no // 2)}", + f"{threads}", output, ] try: @@ -376,7 +388,6 @@ async def _ffmpeg_progress(self): except Exception: self._progress_raw = 0 self._eta_raw = 0 - await sleep(0.05) async def ffmpeg_cmds(self, ffmpeg, f_path): self.clear() @@ -471,6 +482,9 @@ async def convert_video(self, video_file, ext, retry=False): output = f"{base_name}.{ext}" if retry: cmd = [ + "taskset", + "-c", + f"{cores}", "xtra", "-hide_banner", "-loglevel", @@ -486,17 +500,20 @@ async def convert_video(self, video_file, ext, retry=False): "-c:a", "aac", "-threads", - f"{max(1, cpu_no // 2)}", + f"{threads}", output, ] if ext == "mp4": - cmd[14:14] = ["-c:s", "mov_text"] + cmd[17:17] = ["-c:s", "mov_text"] elif ext == "mkv": - cmd[14:14] = ["-c:s", "ass"] + cmd[17:17] = ["-c:s", "ass"] else: - cmd[14:14] = ["-c:s", "copy"] + cmd[17:17] = ["-c:s", "copy"] else: cmd = [ + "taskset", + "-c", + f"{cores}", "xtra", "-hide_banner", "-loglevel", @@ -510,7 +527,7 @@ async def convert_video(self, video_file, ext, retry=False): "-c", "copy", "-threads", - f"{max(1, cpu_no // 2)}", + f"{threads}", output, ] if self._listener.is_cancelled: @@ -549,6 +566,9 @@ async def convert_audio(self, audio_file, ext): base_name = ospath.splitext(audio_file)[0] output = f"{base_name}.{ext}" cmd = [ + "taskset", + "-c", + f"{cores}", "xtra", "-hide_banner", "-loglevel", @@ -558,7 +578,7 @@ async def convert_audio(self, audio_file, ext): "-i", audio_file, "-threads", - f"{max(1, cpu_no // 2)}", + f"{threads}", output, ] if self._listener.is_cancelled: @@ -620,6 +640,9 @@ async def sample_video(self, video_file, sample_duration, part_duration): filter_complex += f"concat=n={len(segments)}:v=1:a=1[vout][aout]" cmd = [ + "taskset", + "-c", + f"{cores}", "xtra", "-hide_banner", "-loglevel", @@ -639,7 +662,7 @@ async def sample_video(self, video_file, sample_duration, part_duration): "-c:a", "aac", "-threads", - f"{max(1, cpu_no // 2)}", + f"{threads}", output_file, ] @@ -682,6 +705,9 @@ async def split(self, f_path, file_, parts, split_size): while i <= parts or start_time < duration - 4: out_path = f_path.replace(file_, f"{base_name}.part{i:03}{extension}") cmd = [ + "taskset", + "-c", + f"{cores}", "xtra", "-hide_banner", "-loglevel", @@ -705,12 +731,12 @@ async def split(self, f_path, file_, parts, split_size): "-c", "copy", "-threads", - f"{max(1, cpu_no // 2)}", + f"{threads}", out_path, ] if not multi_streams: - del cmd[12] - del cmd[12] + del cmd[15] + del cmd[15] if self._listener.is_cancelled: return False self._listener.subproc = await create_subprocess_exec( @@ -759,7 +785,7 @@ async def split(self, f_path, file_, parts, split_size): break if duration == lpd: LOGGER.warning( - f"This file has been splitted with default stream and audio, so you will only see one part with less size from orginal one because it doesn't have all streams and audios. This happens mostly with MKV videos. Path: {f_path}", + f"This file has been splitted with default stream and audio, so you will only see one part with less size from original one because it doesn't have all streams and audios. This happens mostly with MKV videos. Path: {f_path}", ) break if lpd <= 3: diff --git a/bot/helper/ext_utils/task_manager.py b/bot/helper/ext_utils/task_manager.py index 2d67b19b5..2e1443239 100644 --- a/bot/helper/ext_utils/task_manager.py +++ b/bot/helper/ext_utils/task_manager.py @@ -18,13 +18,11 @@ async def stop_duplicate_check(listener): if ( - isinstance(listener.up_dest, int) - or listener.is_leech - or listener.select - or not is_gdrive_id(listener.up_dest) - or (listener.up_dest.startswith("mtp:") and listener.stop_duplicate) + listener.is_leech or not listener.stop_duplicate or listener.same_dir + or listener.select + or not is_gdrive_id(listener.up_dest) ): return False, None diff --git a/bot/helper/listeners/aria2_listener.py b/bot/helper/listeners/aria2_listener.py index 384512bf8..b891ec1cf 100644 --- a/bot/helper/listeners/aria2_listener.py +++ b/bot/helper/listeners/aria2_listener.py @@ -173,12 +173,14 @@ async def _on_download_error(api, data): await sleep(1) LOGGER.info(f"onDownloadError: {gid}") error = "None" - with contextlib.suppress(TimeoutError, ClientError, Exception): + try: download = await api.tellStatus(gid) options = await api.getOption(gid) error = download.get("errorMessage", "") LOGGER.info(f"Download Error: {error}") - if options.get("follow-torrent", "") == "false": + if options.get("follow-torrent", "") == "false": + return + except (TimeoutError, ClientError, Exception): return if task := await get_task_by_gid(gid): await task.listener.on_download_error(error) diff --git a/bot/helper/listeners/task_listener.py b/bot/helper/listeners/task_listener.py index 7029bd734..284ea50c8 100644 --- a/bot/helper/listeners/task_listener.py +++ b/bot/helper/listeners/task_listener.py @@ -32,15 +32,18 @@ get_path_size, join_files, remove_excluded_files, + remove_non_included_files, ) from bot.helper.ext_utils.links_utils import is_gdrive_id from bot.helper.ext_utils.status_utils import get_readable_file_size from bot.helper.ext_utils.task_manager import check_running_tasks, start_from_queued from bot.helper.mirror_leech_utils.gdrive_utils.upload import GoogleDriveUpload +from bot.helper.mirror_leech_utils.gofile_utils.upload import GoFileUpload from bot.helper.mirror_leech_utils.rclone_utils.transfer import RcloneTransferHelper from bot.helper.mirror_leech_utils.status_utils.gdrive_status import ( GoogleDriveStatus, ) +from bot.helper.mirror_leech_utils.status_utils.gofile_status import GoFileStatus from bot.helper.mirror_leech_utils.status_utils.queue_status import QueueStatus from bot.helper.mirror_leech_utils.status_utils.rclone_status import RcloneStatus from bot.helper.mirror_leech_utils.status_utils.telegram_status import TelegramStatus @@ -196,10 +199,14 @@ async def on_download_complete(self): else: up_dir = self.dir up_path = dl_path - await remove_excluded_files( - self.up_dir or self.dir, - self.excluded_extensions, - ) + if not self.included_extensions: + await remove_excluded_files( + self.up_dir or self.dir, self.excluded_extensions + ) + else: + await remove_non_included_files( + self.up_dir or self.dir, self.included_extensions + ) if not Config.QUEUE_ALL: async with queue_dict_lock: if self.mid in non_queued_dl: @@ -217,7 +224,10 @@ async def on_download_complete(self): self.name = up_path.replace(f"{up_dir}/", "").split("/", 1)[0] self.size = await get_path_size(up_dir) self.clear() - await remove_excluded_files(up_dir, self.excluded_extensions) + if not self.included_extensions: + await remove_excluded_files(up_dir, self.excluded_extensions) + else: + await remove_non_included_files(up_dir, self.included_extensions) if self.watermark: up_path = await self.proceed_watermark( @@ -256,6 +266,7 @@ async def on_download_complete(self): self.clear() if self.name_sub: + LOGGER.info(f"Start Name Substitution {up_path}") up_path = await self.substitute(up_path) if self.is_cancelled: return @@ -400,13 +411,6 @@ async def on_download_complete(self): del yt elif self.up_dest == "gofile": LOGGER.info(f"GoFile Upload Name: {self.name}") - from bot.helper.mirror_leech_utils.gofile_utils.upload import ( - GoFileUpload, - ) - from bot.helper.mirror_leech_utils.status_utils.gofile_status import ( - GoFileStatus, - ) - gofile = GoFileUpload(self, up_path) async with task_dict_lock: task_dict[self.mid] = GoFileStatus(self, gofile, gid, "up") @@ -647,12 +651,10 @@ async def on_upload_complete( elif Config.INDEX_URL: INDEX_URL = Config.INDEX_URL if INDEX_URL: - share_url = f"{INDEX_URL}/findpath?id={dir_id}" + share_url = f"{INDEX_URL}findpath?id={dir_id}" buttons.url_button("Index Link", share_url) if mime_type.startswith(("image", "video", "audio")): - share_urls = ( - f"{INDEX_URL}/findpath?id={dir_id}&view=true" - ) + share_urls = f"{INDEX_URL}findpath?id={dir_id}&view=true" buttons.url_button("🌐 View Link", share_urls) button = buttons.build_menu(2) else: diff --git a/bot/helper/mirror_leech_utils/download_utils/aria2_download.py b/bot/helper/mirror_leech_utils/download_utils/aria2_download.py index 0e5c27cdd..6b96cc909 100644 --- a/bot/helper/mirror_leech_utils/download_utils/aria2_download.py +++ b/bot/helper/mirror_leech_utils/download_utils/aria2_download.py @@ -70,8 +70,10 @@ async def add_aria2_download(listener, dpath, header, ratio, seed_time): if add_to_queue: LOGGER.info(f"Added to Queue/Download: {name}. Gid: {gid}") if ( - not listener.select or "bittorrent" not in download - ) and listener.multi <= 1: + (not listener.select or "bittorrent" not in download) + and listener.multi <= 1 + and not listener.is_rss + ): await send_status_message(listener.message) else: LOGGER.info(f"Aria2Download started: {name}. Gid: {gid}") @@ -82,6 +84,7 @@ async def add_aria2_download(listener, dpath, header, ratio, seed_time): not add_to_queue and (not listener.select or not Config.BASE_URL) and listener.multi <= 1 + and not listener.is_rss ): await send_status_message(listener.message) elif listener.select and "bittorrent" in download and not is_metadata(download): diff --git a/bot/helper/mirror_leech_utils/download_utils/direct_downloader.py b/bot/helper/mirror_leech_utils/download_utils/direct_downloader.py index 5b5b13c9c..29625f2bc 100644 --- a/bot/helper/mirror_leech_utils/download_utils/direct_downloader.py +++ b/bot/helper/mirror_leech_utils/download_utils/direct_downloader.py @@ -34,7 +34,7 @@ async def add_direct_download(listener, path): async with task_dict_lock: task_dict[listener.mid] = QueueStatus(listener, gid, "dl") await listener.on_download_start() - if listener.multi <= 1: + if listener.multi <= 1 and not listener.is_rss: await send_status_message(listener.message) await event.wait() if listener.is_cancelled: @@ -54,7 +54,7 @@ async def add_direct_download(listener, path): else: LOGGER.info(f"Download from Direct Download: {listener.name}") await listener.on_download_start() - if listener.multi <= 1: + if listener.multi <= 1 and not listener.is_rss: await send_status_message(listener.message) await directListener.download(contents) diff --git a/bot/helper/mirror_leech_utils/download_utils/jd_download.py b/bot/helper/mirror_leech_utils/download_utils/jd_download.py index d59368cb6..4d15d7972 100644 --- a/bot/helper/mirror_leech_utils/download_utils/jd_download.py +++ b/bot/helper/mirror_leech_utils/download_utils/jd_download.py @@ -166,8 +166,10 @@ async def add_jd_download(listener, path): ) await sleep(1) + LOGGER.info(f"JDownloader Collecting Data: {listener.link}") while await jdownloader.device.linkgrabber.is_collecting(): - pass + await sleep(0.5) + LOGGER.info(f"JDownloader Finished Collecting Data: {listener.link}") start_time = time() online_packages = [] corrupted_packages = [] diff --git a/bot/helper/mirror_leech_utils/download_utils/qbit_download.py b/bot/helper/mirror_leech_utils/download_utils/qbit_download.py index e7d2ab242..45327ed2c 100644 --- a/bot/helper/mirror_leech_utils/download_utils/qbit_download.py +++ b/bot/helper/mirror_leech_utils/download_utils/qbit_download.py @@ -111,7 +111,7 @@ async def add_qb_torrent(listener, path, ratio, seed_time): SBUTTONS = bt_selection_buttons(ext_hash) msg = "Your download paused. Choose files then press Done Selecting button to start downloading." await send_message(listener.message, msg, SBUTTONS) - elif listener.multi <= 1: + elif listener.multi <= 1 and not listener.is_rss: await send_status_message(listener.message) if event is not None: diff --git a/bot/helper/mirror_leech_utils/download_utils/telegram_download.py b/bot/helper/mirror_leech_utils/download_utils/telegram_download.py index 5721f95d4..c0b4f4ac7 100644 --- a/bot/helper/mirror_leech_utils/download_utils/telegram_download.py +++ b/bot/helper/mirror_leech_utils/download_utils/telegram_download.py @@ -5,7 +5,7 @@ from pyrogram.errors import FloodPremiumWait, FloodWait from bot import LOGGER, task_dict, task_dict_lock -from bot.core.aeon_client import TgClient +from bot.core.telegram_manager import TgClient from bot.helper.ext_utils.task_manager import ( check_running_tasks, stop_duplicate_check, diff --git a/bot/helper/mirror_leech_utils/download_utils/yt_dlp_download.py b/bot/helper/mirror_leech_utils/download_utils/yt_dlp_download.py index 22d624429..640cc4f45 100644 --- a/bot/helper/mirror_leech_utils/download_utils/yt_dlp_download.py +++ b/bot/helper/mirror_leech_utils/download_utils/yt_dlp_download.py @@ -1,4 +1,4 @@ -# ruff: noqa: ARG005, B023 +import builtins import contextlib from logging import getLogger from os import listdir @@ -27,11 +27,9 @@ def __init__(self, obj, listener): self._listener = listener def debug(self, msg): + # Hack to fix changing extension if not self._obj.is_playlist and ( - match := re_search( - r".Merger..Merging formats into..(.*?).$", - msg, - ) + match := re_search(r".Merger..Merging formats into..(.*?).$", msg) or re_search(r".ExtractAudio..Destination..(.*?)$", msg) ): LOGGER.info(msg) @@ -60,6 +58,7 @@ def __init__(self, listener): self._gid = "" self._ext = "" self.is_playlist = False + self.keep_thumb = False self.opts = { "progress_hooks": [self._on_download_progress], "logger": MyLogger(self, self._listener), @@ -76,10 +75,10 @@ def __init__(self, listener): "fragment_retries": 10, "retries": 10, "retry_sleep_functions": { - "http": lambda n: 3, - "fragment": lambda n: 3, - "file_access": lambda n: 3, - "extractor": lambda n: 3, + "http": lambda _: 3, + "fragment": lambda _: 3, + "file_access": lambda _: 3, + "extractor": lambda _: 3, }, } @@ -123,19 +122,17 @@ def _on_download_progress(self, d): self._listener.size = d["total_bytes_estimate"] or 0 self._downloaded_bytes = d["downloaded_bytes"] or 0 self._eta = d.get("eta", "-") or "-" - with contextlib.suppress(Exception): + with contextlib.suppress(builtins.BaseException): self._progress = (self._downloaded_bytes / self._listener.size) * 100 async def _on_download_start(self, from_queue=False): async with task_dict_lock: task_dict[self._listener.mid] = YtDlpStatus( - self._listener, - self, - self._gid, + self._listener, self, self._gid ) if not from_queue: await self._listener.on_download_start() - if self._listener.multi <= 1: + if self._listener.multi <= 1 and not self._listener.is_rss: await send_status_message(self._listener.message) def _on_download_error(self, error): @@ -163,21 +160,22 @@ def _extract_meta_data(self): if not self._listener.name: outtmpl_ = "%(series,playlist_title,channel)s%(season_number& |)s%(season_number&S|)s%(season_number|)02d.%(ext)s" self._listener.name, ext = ospath.splitext( - ydl.prepare_filename(entry, outtmpl=outtmpl_), + ydl.prepare_filename(entry, outtmpl=outtmpl_) ) if not self._ext: self._ext = ext - return None - outtmpl_ = "%(title,fulltitle,alt_title)s%(season_number& |)s%(season_number&S|)s%(season_number|)02d%(episode_number&E|)s%(episode_number|)02d%(height& |)s%(height|)s%(height&p|)s%(fps|)s%(fps&fps|)s%(tbr& |)s%(tbr|)d.%(ext)s" - realName = ydl.prepare_filename(result, outtmpl=outtmpl_) - ext = ospath.splitext(realName)[-1] - self._listener.name = ( - f"{self._listener.name}{ext}" if self._listener.name else realName - ) - if not self._ext: - self._ext = ext - return None - return None + else: + outtmpl_ = "%(title,fulltitle,alt_title)s%(season_number& |)s%(season_number&S|)s%(season_number|)02d%(episode_number&E|)s%(episode_number|)02d%(height& |)s%(height|)s%(height&p|)s%(fps|)s%(fps&fps|)s%(tbr& |)s%(tbr|)d.%(ext)s" + realName = ydl.prepare_filename(result, outtmpl=outtmpl_) + ext = ospath.splitext(realName)[-1] + self._listener.name = ( + f"{self._listener.name}{ext}" + if self._listener.name + else realName + ) + if not self._ext: + self._ext = ext + return None def _download(self, path): try: @@ -192,7 +190,7 @@ def _download(self, path): not ospath.exists(path) or len(listdir(path)) == 0 ): self._on_download_error( - "No video available to download from this playlist. Check logs for more details", + "No video available to download from this playlist. Check logs for more details" ) return if self._listener.is_cancelled: @@ -217,7 +215,7 @@ async def add_download(self, path, qual, playlist, options): "add_infojson": "if_exists", "add_metadata": True, "key": "FFmpegMetadata", - }, + } ] if qual.startswith("ba/b-"): @@ -230,7 +228,7 @@ async def add_download(self, path, qual, playlist, options): "key": "FFmpegExtractAudio", "preferredcodec": audio_format, "preferredquality": rate, - }, + } ) if audio_format == "vorbis": self._ext = ".ogg" @@ -239,6 +237,9 @@ async def add_download(self, path, qual, playlist, options): else: self._ext = f".{audio_format}" + if not self._listener.is_leech or self._listener.thumbnail_layout: + self.opts["writethumbnail"] = False + if options: self._set_options(options) @@ -258,15 +259,16 @@ async def add_download(self, path, qual, playlist, options): ) base_name = ospath.splitext(self._listener.name)[0] + start_path = path if self.keep_thumb else f"{path}/yt-dlp-thumb" if self.is_playlist: self.opts["outtmpl"] = { "default": f"{path}/{self._listener.name}/%(title,fulltitle,alt_title)s%(season_number& |)s%(season_number&S|)s%(season_number|)02d%(episode_number&E|)s%(episode_number|)02d%(height& |)s%(height|)s%(height&p|)s%(fps|)s%(fps&fps|)s%(tbr& |)s%(tbr|)d.%(ext)s", - "thumbnail": f"{path}/yt-dlp-thumb/%(title,fulltitle,alt_title)s%(season_number& |)s%(season_number&S|)s%(season_number|)02d%(episode_number&E|)s%(episode_number|)02d%(height& |)s%(height|)s%(height&p|)s%(fps|)s%(fps&fps|)s%(tbr& |)s%(tbr|)d.%(ext)s", + "thumbnail": f"{start_path}/%(title,fulltitle,alt_title)s%(season_number& |)s%(season_number&S|)s%(season_number|)02d%(episode_number&E|)s%(episode_number|)02d%(height& |)s%(height|)s%(height&p|)s%(fps|)s%(fps&fps|)s%(tbr& |)s%(tbr|)d.%(ext)s", } elif "download_ranges" in options: self.opts["outtmpl"] = { "default": f"{path}/{base_name}/%(section_number|)s%(section_number&.|)s%(section_title|)s%(section_title&-|)s%(title,fulltitle,alt_title)s %(section_start)s to %(section_end)s.%(ext)s", - "thumbnail": f"{path}/yt-dlp-thumb/%(section_number|)s%(section_number&.|)s%(section_title|)s%(section_title&-|)s%(title,fulltitle,alt_title)s %(section_start)s to %(section_end)s.%(ext)s", + "thumbnail": f"{start_path}/%(section_number|)s%(section_number&.|)s%(section_title|)s%(section_title&-|)s%(title,fulltitle,alt_title)s %(section_start)s to %(section_end)s.%(ext)s", } elif any( key in options @@ -276,31 +278,32 @@ async def add_download(self, path, qual, playlist, options): "writeannotations", "writedesktoplink", "writewebloclink", + "writelink", "writeurllink", "writesubtitles", - "writeautomaticsub", + "write_all_thumbnails", ] ): self.opts["outtmpl"] = { "default": f"{path}/{base_name}/{self._listener.name}", - "thumbnail": f"{path}/yt-dlp-thumb/{base_name}.%(ext)s", + "thumbnail": f"{start_path}/{base_name}.%(ext)s", } else: self.opts["outtmpl"] = { "default": f"{path}/{self._listener.name}", - "thumbnail": f"{path}/yt-dlp-thumb/{base_name}.%(ext)s", + "thumbnail": f"{start_path}/{base_name}.%(ext)s", } if qual.startswith("ba/b"): self._listener.name = f"{base_name}{self._ext}" - if self._listener.is_leech and not self._listener.thumbnail_layout: + if self.opts["writethumbnail"]: self.opts["postprocessors"].append( { "format": "jpg", "key": "FFmpegThumbnailsConvertor", "when": "before_dl", - }, + } ) if self._ext in [ ".mp3", @@ -316,15 +319,10 @@ async def add_download(self, path, qual, playlist, options): ]: self.opts["postprocessors"].append( { - "already_have_thumbnail": bool( - self._listener.is_leech - and not self._listener.thumbnail_layout, - ), + "already_have_thumbnail": self.opts["writethumbnail"], "key": "EmbedThumbnail", - }, + } ) - elif not self._listener.is_leech: - self.opts["writethumbnail"] = False msg, button = await stop_duplicate_check(self._listener) if msg: @@ -336,9 +334,7 @@ async def add_download(self, path, qual, playlist, options): LOGGER.info(f"Added to Queue/Download: {self._listener.name}") async with task_dict_lock: task_dict[self._listener.mid] = QueueStatus( - self._listener, - self._gid, - "dl", + self._listener, self._gid, "dl" ) await event.wait() if self._listener.is_cancelled: @@ -365,6 +361,8 @@ def _set_options(self, options): self.opts[key].append(value) elif key == "download_ranges": if isinstance(value, list): - self.opts[key] = lambda info, ytdl: value + self.opts[key] = lambda _, __, value=value: value else: + if key == "writethumbnail" and value is True: + self.keep_thumb = True self.opts[key] = value diff --git a/bot/helper/mirror_leech_utils/gdrive_utils/clone.py b/bot/helper/mirror_leech_utils/gdrive_utils/clone.py index f43a8e4cc..d9f94cca7 100644 --- a/bot/helper/mirror_leech_utils/gdrive_utils/clone.py +++ b/bot/helper/mirror_leech_utils/gdrive_utils/clone.py @@ -121,11 +121,20 @@ def _clone_folder(self, folder_name, folder_id, dest_id): current_dir_id = self.create_directory(file.get("name"), dest_id) self._clone_folder(file_path, file.get("id"), current_dir_id) elif ( - not file.get("name") + self.listener.included_extensions + and not file.get("name") + .strip() + .lower() + .endswith(tuple(self.listener.included_extensions)) + ) or ( + not self.listener.included_extensions + and file.get("name") .strip() .lower() .endswith(tuple(self.listener.excluded_extensions)) ): + continue + else: self.total_files += 1 self._copy_file(file.get("id"), dest_id) self.proc_bytes += int(file.get("size", 0)) diff --git a/bot/helper/mirror_leech_utils/gdrive_utils/download.py b/bot/helper/mirror_leech_utils/gdrive_utils/download.py index 6c65415cb..c692744a2 100644 --- a/bot/helper/mirror_leech_utils/gdrive_utils/download.py +++ b/bot/helper/mirror_leech_utils/gdrive_utils/download.py @@ -86,11 +86,23 @@ def _download_folder(self, folder_id, path, folder_name): mime_type = item.get("mimeType") if mime_type == self.G_DRIVE_DIR_MIME_TYPE: self._download_folder(file_id, path, filename) - elif not ospath.isfile( - f"{path}{filename}", - ) and not filename.strip().lower().endswith( - tuple(self.listener.excluded_extensions), + elif ( + ospath.isfile(f"{path}{filename}") + or ( + self.listener.included_extensions + and not filename.strip() + .lower() + .endswith(tuple(self.listener.included_extensions)) + ) + or ( + not self.listener.included_extensions + and filename.strip() + .lower() + .endswith(tuple(self.listener.excluded_extensions)) + ) ): + continue + else: self._download_file(file_id, path, filename, mime_type) if self.listener.is_cancelled: break diff --git a/bot/helper/mirror_leech_utils/gdrive_utils/search.py b/bot/helper/mirror_leech_utils/gdrive_utils/search.py index 3e10a99c0..b0866b490 100644 --- a/bot/helper/mirror_leech_utils/gdrive_utils/search.py +++ b/bot/helper/mirror_leech_utils/gdrive_utils/search.py @@ -116,7 +116,8 @@ def drive_list(self, file_name, target_id="", user_id=""): else: drives = zip(drives_names, drives_ids, index_urls, strict=False) if ( - not target_id.startswith("mtp:") and len(drives_ids) > 1 + target_id.startswith("mtp:") + or (not target_id.startswith("mtp:") and len(drives_ids) > 1) ) or target_id.startswith("tp:"): self.use_sa = False @@ -145,7 +146,7 @@ def drive_list(self, file_name, target_id="", user_id=""): msg += f"📁 {file.get('name')}
(folder)

" msg += f"Drive Link" if index_url: - url = f"{index_url}/findpath?id={file.get('id')}" + url = f"{index_url}findpath?id={file.get('id')}" msg += f' | Index Link' elif mime_type == "application/vnd.google-apps.shortcut": furl = self.G_DRIVE_DIR_BASE_DOWNLOAD_URL.format(file.get("id")) @@ -158,11 +159,11 @@ def drive_list(self, file_name, target_id="", user_id=""): msg += f"📄 {file.get('name')}
({get_readable_file_size(int(file.get('size', 0)))})

" msg += f"Drive Link" if index_url: - url = f"{index_url}/findpath?id={file.get('id')}" + url = f"{index_url}findpath?id={file.get('id')}" msg += f' | Index Link' if mime_type.startswith(("image", "video", "audio")): urlv = ( - f"{index_url}/findpath?id={file.get('id')}&view=true" + f"{index_url}findpath?id={file.get('id')}&view=true" ) msg += f' | View Link' msg += "

" diff --git a/bot/helper/mirror_leech_utils/gdrive_utils/upload.py b/bot/helper/mirror_leech_utils/gdrive_utils/upload.py index 5534f792c..c012703ea 100644 --- a/bot/helper/mirror_leech_utils/gdrive_utils/upload.py +++ b/bot/helper/mirror_leech_utils/gdrive_utils/upload.py @@ -13,6 +13,7 @@ wait_exponential, ) +from bot import intervals from bot.core.config_manager import Config from bot.helper.ext_utils.bot_utils import SetInterval, async_to_sync from bot.helper.ext_utils.files_utils import get_mime_type @@ -119,6 +120,11 @@ def _upload_dir(self, input_directory, dest_id): new_id = None for item in list_dirs: current_file_name = ospath.join(input_directory, item) + if not ospath.exists(current_file_name): + if intervals["stopAll"]: + return None + LOGGER.error(f"{current_file_name} not exists! Continue uploading!") + continue if ospath.isdir(current_file_name): current_dir_id = self.create_directory(item, dest_id) new_id = self._upload_dir( diff --git a/bot/helper/mirror_leech_utils/gofile_utils/upload.py b/bot/helper/mirror_leech_utils/gofile_utils/upload.py index 6ed074dcf..92a8942db 100644 --- a/bot/helper/mirror_leech_utils/gofile_utils/upload.py +++ b/bot/helper/mirror_leech_utils/gofile_utils/upload.py @@ -18,6 +18,7 @@ wait_exponential, ) +from bot import user_data from bot.core.config_manager import Config from bot.helper.ext_utils.bot_utils import SetInterval, sync_to_async @@ -53,8 +54,6 @@ def __init__(self, listener, path): self.update_interval = 3 # Get user-specific token or fall back to global config - from bot import user_data - user_dict = user_data.get(self.listener.user_id, {}) self.token = user_dict.get("GOFILE_TOKEN") or Config.GOFILE_API self.folder_id = ( @@ -111,9 +110,11 @@ async def __resp_handler(self, response): ) async def __getServer(self): - async with ClientSession() as session: - async with session.get(f"{self.api_url}servers") as resp: - return await self.__resp_handler(await resp.json()) + async with ( + ClientSession() as session, + session.get(f"{self.api_url}servers") as resp, + ): + return await self.__resp_handler(await resp.json()) async def __getAccount(self, check_account=False): if self.token is None: @@ -174,23 +175,25 @@ async def upload_aiohttp(self, url, file_path, req_file, data): filename=file_path, read_callback=self.__progress_callback ) as file: data[req_file] = file - async with ClientSession() as session: - async with session.post(url, data=data) as resp: - if resp.status == 200: - try: - return await resp.json() - except ContentTypeError: - return { - "status": "ok", - "data": {"downloadPage": "Uploaded"}, - } - except JSONDecodeError: - return { - "status": "ok", - "data": {"downloadPage": "Uploaded"}, - } - else: - raise Exception(f"HTTP {resp.status}: {await resp.text()}") + async with ( + ClientSession() as session, + session.post(url, data=data) as resp, + ): + if resp.status == 200: + try: + return await resp.json() + except ContentTypeError: + return { + "status": "ok", + "data": {"downloadPage": "Uploaded"}, + } + except JSONDecodeError: + return { + "status": "ok", + "data": {"downloadPage": "Uploaded"}, + } + else: + raise Exception(f"HTTP {resp.status}: {await resp.text()}") return None async def create_folder(self, parentFolderId, folderName): @@ -340,7 +343,7 @@ async def upload(self): if ( self.listener.is_cancelled and not self._is_errored ) or self._is_errored: - return + pass async def _upload_process(self): if not await self.is_goapi(self.token): diff --git a/bot/helper/mirror_leech_utils/rclone_utils/transfer.py b/bot/helper/mirror_leech_utils/rclone_utils/transfer.py index 01699ff7c..c71e4c7ba 100644 --- a/bot/helper/mirror_leech_utils/rclone_utils/transfer.py +++ b/bot/helper/mirror_leech_utils/rclone_utils/transfer.py @@ -1,5 +1,5 @@ import contextlib -from asyncio import create_subprocess_exec, gather, sleep, wait_for +from asyncio import create_subprocess_exec, gather, wait_for from asyncio.subprocess import PIPE from configparser import RawConfigParser from json import loads @@ -33,7 +33,6 @@ def __init__(self, listener): self._sa_index = 0 self._sa_number = 0 self._use_service_accounts = Config.USE_SERVICE_ACCOUNTS - self._rclone_select = False @property def transferred_size(self): @@ -79,7 +78,6 @@ async def _progress(self): self._speed, self._eta, ) = data[0] - await sleep(0.5) def _switch_service_account(self): if self._sa_index == self._sa_number - 1: @@ -473,11 +471,10 @@ def _get_updated_command( destination, method, ): + rclone_select = False if source.split(":")[-1].startswith("rclone_select"): source = f"{source.split(':')[0]}:" - self._rclone_select = True - else: - ext = "*.{" + ",".join(self._listener.excluded_extensions) + "}" + rclone_select = True cmd = [ "xone", method, @@ -495,9 +492,13 @@ def _get_updated_command( "1", "-M", ] - if self._rclone_select: + if rclone_select: cmd.extend(("--files-from", self._listener.link)) + elif self._listener.included_extensions: + ext = "*.{" + ",".join(self._listener.included_extensions) + "}" + cmd.extend(("--include", ext)) else: + ext = "*.{" + ",".join(self._listener.excluded_extensions) + "}" cmd.extend(("--exclude", ext)) if rcflags := self._listener.rc_flags: rcflags = rcflags.split("|") diff --git a/bot/helper/mirror_leech_utils/status_utils/nzb_status.py b/bot/helper/mirror_leech_utils/status_utils/nzb_status.py index 550c1673b..063737e4a 100644 --- a/bot/helper/mirror_leech_utils/status_utils/nzb_status.py +++ b/bot/helper/mirror_leech_utils/status_utils/nzb_status.py @@ -10,6 +10,8 @@ async def get_download(nzo_id, old_info=None): + if old_info is None: + old_info = {} try: queue = await sabnzbd_client.get_downloads(nzo_ids=nzo_id) if res := queue["queue"]["slots"]: @@ -56,26 +58,30 @@ def __init__(self, listener, gid, queued=False): self.queued = queued self.listener = listener self._gid = gid - self._info = None + self._info = {} self.tool = "sabnzbd" async def update(self): self._info = await get_download(self._gid, self._info) def progress(self): - return f"{self._info['percentage']}%" + return f"{self._info.get('percentage', '0')}%" def processed_raw(self): - return (float(self._info["mb"]) - float(self._info["mbleft"])) * 1048576 + return ( + float(self._info.get("mb", "0")) - float(self._info.get("mbleft", "0")) + ) * 1048576 def processed_bytes(self): return get_readable_file_size(self.processed_raw()) def speed_raw(self): - if self._info["mb"] == self._info["mbleft"]: + if self._info.get("mb", "0") == self._info.get("mbleft", "0"): return 0 try: - return int(float(self._info["mbleft"]) * 1048576) / self.eta_raw() + return ( + int(float(self._info.get("mbleft", "0")) * 1048576) / self.eta_raw() + ) except Exception: return 0 @@ -83,22 +89,22 @@ def speed(self): return f"{get_readable_file_size(self.speed_raw())}/s" def name(self): - return self._info["filename"] + return self._info.get("filename", "") def size(self): - return self._info["size"] + return self._info.get("size", 0) def eta_raw(self): - return int(time_to_seconds(self._info["timeleft"])) + return int(time_to_seconds(self._info.get("timeleft", "0"))) def eta(self): return get_readable_time(self.eta_raw()) async def status(self): await self.update() - if self._info["mb"] == self._info["mbleft"]: + if self._info.get("mb", "0") == self._info.get("mbleft", "0"): return MirrorStatus.STATUS_QUEUEDL - state = self._info["status"] + state = self._info.get("status") if state == "Paused" and self.queued: return MirrorStatus.STATUS_QUEUEDL if state in [ diff --git a/bot/helper/mirror_leech_utils/telegram_uploader.py b/bot/helper/mirror_leech_utils/telegram_uploader.py index cdb4dbf61..5d37ac383 100644 --- a/bot/helper/mirror_leech_utils/telegram_uploader.py +++ b/bot/helper/mirror_leech_utils/telegram_uploader.py @@ -30,8 +30,9 @@ wait_exponential, ) -from bot.core.aeon_client import TgClient +from bot import intervals from bot.core.config_manager import Config +from bot.core.telegram_manager import TgClient from bot.helper.aeon_utils.caption_gen import generate_caption from bot.helper.ext_utils.bot_utils import sync_to_async from bot.helper.ext_utils.files_utils import ( @@ -113,7 +114,6 @@ async def _msg_to_reply(self): self._sent_msg = await TgClient.user.send_message( chat_id=self._listener.up_dest, text=msg, - disable_web_page_preview=True, message_thread_id=self._listener.chat_thread_id, disable_notification=True, ) @@ -121,7 +121,6 @@ async def _msg_to_reply(self): self._sent_msg = await self._listener.client.send_message( chat_id=self._listener.up_dest, text=msg, - disable_web_page_preview=True, message_thread_id=self._listener.chat_thread_id, disable_notification=True, ) @@ -139,7 +138,6 @@ async def _msg_to_reply(self): self._sent_msg = await TgClient.user.send_message( chat_id=self._listener.message.chat.id, text="Deleted Cmd Message! Don't delete the cmd message again!", - disable_web_page_preview=True, disable_notification=True, ) else: @@ -249,6 +247,8 @@ async def upload(self): self._error = "" self._up_path = f_path = ospath.join(dirpath, file_) if not await aiopath.exists(self._up_path): + if intervals["stopAll"]: + return LOGGER.error(f"{self._up_path} not exists! Continue uploading!") continue try: @@ -339,7 +339,7 @@ async def upload(self): return if self._total_files == 0: await self._listener.on_upload_error( - "No files to upload. In case you have filled EXCLUDED_EXTENSIONS, then check if all files have those extensions or not.", + "No files to upload. In case you have filled EXCLUDED/INCLUDED EXTENSIONS, then check if all files have those extensions or not.", ) return if self._total_files <= self._corrupted: @@ -378,6 +378,8 @@ async def _upload_file(self, cap_mono, file, o_path, force_document=False): thumb_path = f"{self._path}/yt-dlp-thumb/{file_name}.jpg" if await aiopath.isfile(thumb_path): thumb = thumb_path + elif await aiopath.isfile(thumb_path.replace("/yt-dlp-thumb", "")): + thumb = thumb_path.replace("/yt-dlp-thumb", "") elif is_audio and not is_video: thumb = await get_audio_thumbnail(self._up_path) diff --git a/bot/helper/telegram_helper/message_utils.py b/bot/helper/telegram_helper/message_utils.py index 7320ac720..8a3669316 100644 --- a/bot/helper/telegram_helper/message_utils.py +++ b/bot/helper/telegram_helper/message_utils.py @@ -20,8 +20,8 @@ task_dict_lock, user_data, ) -from bot.core.aeon_client import TgClient from bot.core.config_manager import Config +from bot.core.telegram_manager import TgClient from bot.helper.ext_utils.bot_utils import SetInterval from bot.helper.ext_utils.exceptions import TgLinkException from bot.helper.ext_utils.status_utils import get_readable_message @@ -43,7 +43,6 @@ async def send_message( return await TgClient.bot.send_message( chat_id=message, text=text, - disable_web_page_preview=True, disable_notification=True, reply_markup=buttons, parse_mode=parse_mode, @@ -60,7 +59,6 @@ async def send_message( return await message.reply( text=text, quote=True, - disable_web_page_preview=True, disable_notification=True, reply_markup=buttons, parse_mode=parse_mode, @@ -100,7 +98,6 @@ async def edit_message( ) await message.edit( text=text, - disable_web_page_preview=True, reply_markup=buttons, # parse_mode=parse_mode, ) @@ -141,7 +138,6 @@ async def send_rss(text, chat_id, thread_id): return await app.send_message( chat_id=chat_id, text=text, - disable_web_page_preview=True, message_thread_id=thread_id, disable_notification=True, ) diff --git a/bot/modules/bot_settings.py b/bot/modules/bot_settings.py index d6caf1141..baf70c8f6 100644 --- a/bot/modules/bot_settings.py +++ b/bot/modules/bot_settings.py @@ -22,6 +22,7 @@ drives_ids, drives_names, excluded_extensions, + included_extensions, index_urls, intervals, jd_listener_lock, @@ -30,10 +31,10 @@ sudo_users, task_dict, ) -from bot.core.aeon_client import TgClient from bot.core.config_manager import Config from bot.core.jdownloader_booter import jdownloader from bot.core.startup import update_nzb_options, update_variables +from bot.core.telegram_manager import TgClient from bot.core.torrent_manager import TorrentManager from bot.helper.ext_utils.bot_utils import SetInterval, new_task from bot.helper.ext_utils.db_handler import database @@ -216,6 +217,12 @@ async def edit_variable(_, message, pre_message, key): for x in fx: x = x.lstrip(".") excluded_extensions.append(x.strip().lower()) + elif key == "INCLUDED_EXTENSIONS": + fx = value.split() + included_extensions.clear() + for x in fx: + x = x.lstrip(".") + included_extensions.append(x.strip().lower()) elif key == "GDRIVE_ID": if drives_names and drives_names[0] == "Main": drives_ids[0] = value @@ -223,9 +230,9 @@ async def edit_variable(_, message, pre_message, key): drives_ids.insert(0, value) elif key == "INDEX_URL": if drives_names and drives_names[0] == "Main": - index_urls[0] = value.strip("/") + index_urls[0] = value else: - index_urls.insert(0, value.strip("/")) + index_urls.insert(0, value) elif key == "AUTHORIZED_CHATS": aid = value.split() auth_chats.clear() @@ -400,7 +407,7 @@ async def update_private_file(_, message, pre_message): drives_ids.append(temp[1]) drives_names.append(temp[0].replace("_", " ")) if len(temp) > 2: - index_urls.append(temp[2]).strip("/") + index_urls.append(temp[2]) else: index_urls.append("") elif file_name in [".netrc", "netrc"]: @@ -466,7 +473,7 @@ async def edit_bot_settings(client, query): ) return await query.answer( - "Syncronization Started. JDownloader will get restarted. It takes up to 10 sec!", + "Synchronization Started. JDownloader will get restarted. It takes up to 10 sec!", show_alert=True, ) await sync_jdownloader() @@ -479,12 +486,24 @@ async def edit_bot_settings(client, query): await update_buttons(message, data[1]) elif data[1] == "resetvar": await query.answer() - value = "" + expected_type = type(getattr(Config, data[2])) + if expected_type is bool: + value = False + elif expected_type is int: + value = 0 + elif expected_type is str: + value = "" + elif expected_type is list: + value = [] + elif expected_type is dict: + value = {} if data[2] in DEFAULT_VALUES: value = DEFAULT_VALUES[data[2]] elif data[2] == "EXCLUDED_EXTENSIONS": excluded_extensions.clear() excluded_extensions.extend(["aria2", "!qB"]) + elif data[2] == "INCLUDED_EXTENSIONS": + included_extensions.clear() elif data[2] == "TORRENT_TIMEOUT": await TorrentManager.change_aria2_option("bt-stop-timeout", "0") await database.update_aria2("bt-stop-timeout", "0") @@ -542,7 +561,7 @@ async def edit_bot_settings(client, query): await database.update_nzb_config() elif data[1] == "syncnzb": await query.answer( - "Syncronization Started. It takes up to 2 sec!", + "Synchronization Started. It takes up to 2 sec!", show_alert=True, ) nzb_options.clear() diff --git a/bot/modules/cancel_task.py b/bot/modules/cancel_task.py index 1adc72ec5..42cb3404f 100644 --- a/bot/modules/cancel_task.py +++ b/bot/modules/cancel_task.py @@ -1,7 +1,7 @@ from asyncio import sleep from bot import multi_tags, task_dict, task_dict_lock, user_data -from bot.core.aeon_client import Config +from bot.core.telegram_manager import Config from bot.helper.ext_utils.bot_utils import new_task from bot.helper.ext_utils.status_utils import ( MirrorStatus, diff --git a/bot/modules/clone.py b/bot/modules/clone.py index 27b96c0cd..399159642 100644 --- a/bot/modules/clone.py +++ b/bot/modules/clone.py @@ -97,9 +97,9 @@ async def new_event(self): if not isinstance(is_bulk, bool): dargs = is_bulk.split(":") - bulk_start = dargs[0] or 0 + bulk_start = dargs[0] or "0" if len(dargs) == 2: - bulk_end = dargs[1] or 0 + bulk_end = dargs[1] or "0" is_bulk = True if is_bulk: diff --git a/bot/modules/exec.py b/bot/modules/exec.py index 971884623..ba3341f1e 100644 --- a/bot/modules/exec.py +++ b/bot/modules/exec.py @@ -8,7 +8,7 @@ from aiofiles import open as aiopen from bot import LOGGER -from bot.core.aeon_client import TgClient +from bot.core.telegram_manager import TgClient from bot.helper.ext_utils.bot_utils import new_task, sync_to_async from bot.helper.telegram_helper.message_utils import send_file, send_message diff --git a/bot/modules/file_selector.py b/bot/modules/file_selector.py index def7385df..a91646100 100644 --- a/bot/modules/file_selector.py +++ b/bot/modules/file_selector.py @@ -50,7 +50,7 @@ async def select(_, message): await send_message(message, "This task is not for you!") return if not iscoroutinefunction(task.status): - await send_message(message, "The task have finshed the download stage!") + await send_message(message, "The task have finished the download stage!") return if await task.status() not in [ MirrorStatus.STATUS_DOWNLOAD, diff --git a/bot/modules/force_start.py b/bot/modules/force_start.py index e416e7082..719258b03 100644 --- a/bot/modules/force_start.py +++ b/bot/modules/force_start.py @@ -37,7 +37,7 @@ async def remove_from_queue(_, message): elif len(msg) in {1, 2}: msg = f"""Reply to an active Command message which was used to start the download/upload. /{BotCommands.ForceStartCommand[0]} fd (to remove it from download queue) or fu (to remove it from upload queue) or nothing to start remove it from both download and upload queue. -Also send /{BotCommands.ForceStartCommand[0]} GID fu|fd or obly gid to force start by removeing the task rom queue! +Also send /{BotCommands.ForceStartCommand[0]} GID fu|fd or obly gid to force start by removing the task rom queue! Examples: /{BotCommands.ForceStartCommand[1]} GID fu (force upload) /{BotCommands.ForceStartCommand[1]} GID (force download and upload) diff --git a/bot/modules/mediainfo.py b/bot/modules/mediainfo.py index 88888768d..0b180b888 100644 --- a/bot/modules/mediainfo.py +++ b/bot/modules/mediainfo.py @@ -10,7 +10,7 @@ from aiofiles.os import remove as aioremove from bot import LOGGER -from bot.core.aeon_client import TgClient +from bot.core.telegram_manager import TgClient from bot.helper.aeon_utils.access_check import token_check from bot.helper.ext_utils.bot_utils import cmd_exec from bot.helper.ext_utils.telegraph_helper import telegraph diff --git a/bot/modules/mirror_leech.py b/bot/modules/mirror_leech.py index cecf76074..4baac8938 100644 --- a/bot/modules/mirror_leech.py +++ b/bot/modules/mirror_leech.py @@ -9,7 +9,7 @@ from truelink.types import FolderResult, LinkResult from bot import DOWNLOAD_DIR, LOGGER, bot_loop, task_dict_lock -from bot.core.aeon_client import TgClient +from bot.core.telegram_manager import TgClient from bot.helper.aeon_utils.access_check import error_check from bot.helper.ext_utils.bot_utils import ( COMMAND_USAGE, @@ -220,9 +220,9 @@ async def new_event(self): if not isinstance(is_bulk, bool): dargs = is_bulk.split(":") - bulk_start = dargs[0] or 0 + bulk_start = dargs[0] or "0" if len(dargs) == 2: - bulk_end = dargs[1] or 0 + bulk_end = dargs[1] or "0" is_bulk = True if not is_bulk: diff --git a/bot/modules/restart.py b/bot/modules/restart.py index f4ca8cecd..31f94676d 100644 --- a/bot/modules/restart.py +++ b/bot/modules/restart.py @@ -8,9 +8,9 @@ from aiofiles.os import remove from bot import LOGGER, intervals, sabnzbd_client, scheduler -from bot.core.aeon_client import TgClient from bot.core.config_manager import Config from bot.core.jdownloader_booter import jdownloader +from bot.core.telegram_manager import TgClient from bot.core.torrent_manager import TorrentManager from bot.helper.ext_utils.bot_utils import new_task from bot.helper.ext_utils.db_handler import database @@ -39,14 +39,12 @@ async def send_incomplete_task_message(cid, msg_id, msg): chat_id=cid, message_id=msg_id, text=msg, - disable_web_page_preview=True, ) await remove(".restartmsg") else: await TgClient.bot.send_message( chat_id=cid, text=msg, - disable_web_page_preview=True, disable_notification=True, ) except Exception as e: diff --git a/bot/modules/rss.py b/bot/modules/rss.py index 0a1acbd3f..48791b892 100644 --- a/bot/modules/rss.py +++ b/bot/modules/rss.py @@ -91,7 +91,7 @@ async def rss_sub(_, message, pre_event): if len(args) < 2: await send_message( message, - f"{item}. Wrong Input format. Read help message before adding new subcription!", + f"{item}. Wrong Input format. Read help message before adding new subscription!", ) continue title = args[0].strip() @@ -136,10 +136,7 @@ async def rss_sub(_, message, pre_event): stv = False try: async with AsyncClient( - headers=headers, - follow_redirects=True, - timeout=60, - verify=False, + headers=headers, follow_redirects=True, timeout=60, verify=False ) as client: res = await client.get(feed_link) html = res.text @@ -194,10 +191,10 @@ async def rss_sub(_, message, pre_event): "command": cmd, "sensitive": stv, "tag": tag, - }, + } } LOGGER.info( - f"Rss Feed Added: id: {user_id} - title: {title} - link: {feed_link} - c: {cmd} - inf: {inf} - exf: {exf} - stv: {stv}", + f"Rss Feed Added: id: {user_id} - title: {title} - link: {feed_link} - c: {cmd} - inf: {inf} - exf: {exf} - stv: {stv}" ) except (IndexError, AttributeError) as e: emsg = f"The link: {feed_link} doesn't seem to be a RSS feed or it's region-blocked!" @@ -291,7 +288,7 @@ async def rss_list(query, start, all_users=False): index = 0 for titles in rss_dict.values(): for index, (title, data) in enumerate( - list(titles.items())[start : 5 + start], + list(titles.items())[start : 5 + start] ): list_feed += f"\n\nTitle: {title}\n" list_feed += f"Feed Url: {data['link']}\n" @@ -320,9 +317,7 @@ async def rss_list(query, start, all_users=False): if keysCount > 5: for x in range(0, keysCount, 5): buttons.data_button( - f"{int(x / 5)}", - f"rss list {user_id} {x}", - position="footer", + f"{int(x / 5)}", f"rss list {user_id} {x}", position="footer" ) button = buttons.build_menu(2) if query.message.text.html == list_feed: @@ -338,7 +333,7 @@ async def rss_get(_, message, pre_event): if len(args) < 2: await send_message( message, - f"{args}. Wrong Input format. You should add number of the items you want to get. Read help message before adding new subcription!", + f"{args}. Wrong Input format. You should add number of the items you want to get. Read help message before adding new subscription!", ) await update_rss_menu(pre_event) return @@ -349,14 +344,10 @@ async def rss_get(_, message, pre_event): if data and count > 0: try: msg = await send_message( - message, - f"Getting the last {count} item(s) from {title}", + message, f"Getting the last {count} item(s) from {title}" ) async with AsyncClient( - headers=headers, - follow_redirects=True, - timeout=60, - verify=False, + headers=headers, follow_redirects=True, timeout=60, verify=False ) as client: res = await client.get(data["link"]) html = res.text @@ -380,8 +371,7 @@ async def rss_get(_, message, pre_event): except IndexError as e: LOGGER.error(str(e)) await edit_message( - msg, - "Parse depth exceeded. Try again with a lower value.", + msg, "Parse depth exceeded. Try again with a lower value." ) except Exception as e: LOGGER.error(str(e)) @@ -470,12 +460,11 @@ async def event_filter(_, __, event): return bool( user.id == user_id and event.chat.id == query.message.chat.id - and event.text, + and event.text ) handler = client.add_handler( - MessageHandler(pfunc, create(event_filter)), - group=-1, + MessageHandler(pfunc, create(event_filter)), group=-1 ) while handler_dict[user_id]: await sleep(0.5) @@ -492,8 +481,7 @@ async def rss_listener(client, query): data = query.data.split() if int(data[2]) != user_id and not await CustomFilters.sudo("", query): await query.answer( - text="You don't have permission to use these buttons!", - show_alert=True, + text="You don't have permission to use these buttons!", show_alert=True ) elif data[1] == "close": await query.answer() @@ -596,13 +584,13 @@ async def rss_listener(client, query): await update_rss_menu(query) elif data[1].endswith("pause"): async with rss_dict_lock: - for title in list(rss_dict[int(data[2])].keys()): - rss_dict[int(data[2])][title]["paused"] = True + for info in rss_dict[int(data[2])].values(): + info["paused"] = True await database.rss_update(int(data[2])) elif data[1].endswith("resume"): async with rss_dict_lock: - for title in list(rss_dict[int(data[2])].keys()): - rss_dict[int(data[2])][title]["paused"] = False + for info in rss_dict[int(data[2])].values(): + info["paused"] = False if scheduler.state == 2: scheduler.resume() await database.rss_update(int(data[2])) @@ -619,22 +607,23 @@ async def rss_listener(client, query): await update_rss_menu(query) elif data[1].endswith("pause"): async with rss_dict_lock: - for user in list(rss_dict.keys()): - for title in list(rss_dict[user].keys()): - rss_dict[int(data[2])][title]["paused"] = True + for user_feeds in rss_dict.values(): + for feed in user_feeds.values(): + feed["paused"] = True if scheduler.running: scheduler.pause() await database.rss_update_all() elif data[1].endswith("resume"): async with rss_dict_lock: - for user in list(rss_dict.keys()): - for title in list(rss_dict[user].keys()): - rss_dict[int(data[2])][title]["paused"] = False + for user_feeds in rss_dict.values(): + for feed in user_feeds.values(): + feed["paused"] = False if scheduler.state == 2: scheduler.resume() elif not scheduler.running: add_job() scheduler.start() + await update_rss_menu(query) await database.rss_update_all() elif data[1] == "deluser": if len(rss_dict) == 0: @@ -695,10 +684,10 @@ async def rss_monitor(): rss_chat_id = int(chat) for user, items in list(rss_dict.items()): for title, data in items.items(): + if data["paused"]: + continue + tries = 0 try: - if data["paused"]: - continue - tries = 0 while True: try: async with AsyncClient( @@ -716,13 +705,21 @@ async def rss_monitor(): raise continue rss_d = feed_parse(html) - try: - last_link = rss_d.entries[0]["links"][1]["href"] - except IndexError: - last_link = rss_d.entries[0]["link"] - finally: - all_paused = False - last_title = rss_d.entries[0]["title"] + if not rss_d.entries: + LOGGER.warning( + f"No entries found for > Feed Title: {title} - Feed Link: {data['link']}" + ) + continue + entry0 = rss_d.entries[0] + links = entry0.get("links", []) + if len(links) > 1: + last_link = links[1].get("href") + elif links: + last_link = links[0].get("href") + else: + last_link = entry0.get("link") + last_title = entry0.get("title") + all_paused = False if ( data["last_feed"] == last_link or data["last_title"] == last_title @@ -732,8 +729,8 @@ async def rss_monitor(): while True: try: await sleep(10) - except Exception: - raise RssShutdownException("Rss Monitor Stopped!") from None + except Exception as e: + raise RssShutdownException("Rss Monitor Stopped!") from e try: item_title = rss_d.entries[feed_count]["title"] try: @@ -756,7 +753,7 @@ async def rss_monitor(): size = 0 except IndexError: LOGGER.warning( - f"Reached Max index no. {feed_count} for this feed: {title}. Maybe you need to use less RSS_DELAY to not miss some torrents", + f"Reached Max index no. {feed_count} for this feed: {title}. Maybe you need to use less RSS_DELAY to not miss some torrents" ) break parse = True @@ -815,14 +812,11 @@ async def rss_monitor(): if user not in rss_dict or not rss_dict[user].get(title, False): continue rss_dict[user][title].update( - {"last_feed": last_link, "last_title": last_title}, + {"last_feed": last_link, "last_title": last_title} ) await database.rss_update(user) LOGGER.info(f"Feed Name: {title}") LOGGER.info(f"Last item: {last_link}") - except RssShutdownException as ex: - LOGGER.info(ex) - break except Exception as e: LOGGER.error(f"{e} - Feed Name: {title} - Feed Link: {data['link']}") continue diff --git a/bot/modules/services.py b/bot/modules/services.py index 84f0b092a..93f6a25a4 100644 --- a/bot/modules/services.py +++ b/bot/modules/services.py @@ -5,8 +5,8 @@ from aiofiles import open as aiopen from bot import LOGGER, user_data -from bot.core.aeon_client import TgClient from bot.core.config_manager import Config +from bot.core.telegram_manager import TgClient from bot.helper.ext_utils.bot_utils import new_task from bot.helper.ext_utils.db_handler import database from bot.helper.ext_utils.status_utils import get_readable_time diff --git a/bot/modules/speedtest.py b/bot/modules/speedtest.py index 7512850bd..19cfed9b8 100644 --- a/bot/modules/speedtest.py +++ b/bot/modules/speedtest.py @@ -1,7 +1,7 @@ from speedtest import Speedtest from bot import LOGGER -from bot.core.aeon_client import TgClient +from bot.core.telegram_manager import TgClient from bot.helper.ext_utils.bot_utils import new_task from bot.helper.ext_utils.status_utils import get_readable_file_size from bot.helper.telegram_helper.message_utils import ( diff --git a/bot/modules/stats.py b/bot/modules/stats.py index b75e3ebe2..32258776b 100644 --- a/bot/modules/stats.py +++ b/bot/modules/stats.py @@ -42,6 +42,10 @@ async def bot_stats(_, message): total, used, free, disk = disk_usage("/") swap = swap_memory() memory = virtual_memory() + per_cpu = cpu_percent(interval=1, percpu=True) + per_cpu_str = " | ".join( + [f"CPU{i + 1}: {round(p)}%" for i, p in enumerate(per_cpu)] + ) stats = f""" Commit Date: {commands["commit"]} @@ -54,7 +58,8 @@ async def bot_stats(_, message): Upload: {get_readable_file_size(net_io_counters().bytes_sent)} Download: {get_readable_file_size(net_io_counters().bytes_recv)} -CPU: {cpu_percent(interval=0.5)}% +CPU: {cpu_percent(interval=1)}% +CPU Cores: {per_cpu_str} RAM: {memory.percent}% DISK: {disk}% diff --git a/bot/modules/users_settings.py b/bot/modules/users_settings.py index 3adca4736..4389ca7ad 100644 --- a/bot/modules/users_settings.py +++ b/bot/modules/users_settings.py @@ -11,9 +11,15 @@ from pyrogram.filters import create from pyrogram.handlers import MessageHandler -from bot import auth_chats, excluded_extensions, sudo_users, user_data -from bot.core.aeon_client import TgClient +from bot import ( + auth_chats, + excluded_extensions, + included_extensions, + sudo_users, + user_data, +) from bot.core.config_manager import Config +from bot.core.telegram_manager import TgClient from bot.helper.ext_utils.bot_utils import ( get_size_bytes, new_task, @@ -289,7 +295,7 @@ async def get_user_settings(from_user, stype="main"): and Config.UPLOAD_PATHS ): upload_paths = Config.UPLOAD_PATHS - else: + if not upload_paths: upload_paths = "None" buttons.data_button("Upload Paths", f"userset {user_id} menu UPLOAD_PATHS") @@ -332,9 +338,23 @@ async def get_user_settings(from_user, stype="main"): else: ex_ex = "None" - ns_msg = "Added" if user_dict.get("NAME_SUBSTITUTE", False) else "None" buttons.data_button( - "Name Subtitute", + "Included Extensions", f"userset {user_id} menu INCLUDED_EXTENSIONS" + ) + if user_dict.get("INCLUDED_EXTENSIONS", False): + inc_ex = user_dict["INCLUDED_EXTENSIONS"] + elif "INCLUDED_EXTENSIONS" not in user_dict: + inc_ex = included_extensions + else: + inc_ex = "None" + if user_dict.get("NAME_SUBSTITUTE", False) or ( + "NAME_SUBSTITUTE" not in user_dict and Config.NAME_SUBSTITUTE + ): + ns_msg = "Added" + else: + ns_msg = "None" + buttons.data_button( + "Name Substitute", f"userset {user_id} menu NAME_SUBSTITUTE", ) if user_dict.get("NAME_PREFIX", False): @@ -392,6 +412,7 @@ async def get_user_settings(from_user, stype="main"): Name substitution is {ns_msg} Name prefix is {np_msg} Excluded Extensions is {ex_ex} +Included Extensions is {inc_ex} YT-DLP Options is {ytopt} FFMPEG Commands is {ffc} Metadata is {mdt} @@ -486,6 +507,12 @@ async def set_option(_, message, option): for x in fx: x = x.lstrip(".") value.append(x.strip().lower()) + elif option == "INCLUDED_EXTENSIONS": + fx = value.split() + value = [] + for x in fx: + x = x.lstrip(".") + value.append(x.strip().lower()) elif option in ["UPLOAD_PATHS", "FFMPEG_CMDS", "YT_DLP_OPTIONS"]: if value.startswith("{") and value.endswith("}"): try: @@ -579,7 +606,7 @@ async def ffmpeg_variables( if ffc: buttons = ButtonMaker() if key is None: - msg = "Choose which key you want to fill/edit varibales in it:" + msg = "Choose which key you want to fill/edit variables in it:" for k, v in list(ffc.items()): add = False for i in v: diff --git a/bot/modules/ytdlp.py b/bot/modules/ytdlp.py index 64e62ed1c..57051d873 100644 --- a/bot/modules/ytdlp.py +++ b/bot/modules/ytdlp.py @@ -244,7 +244,7 @@ async def audio_quality(self, format): buttons.data_button("Back", "ytq aq back") buttons.data_button("Cancel", "ytq aq cancel") subbuttons = buttons.build_menu(5) - msg = f"Choose Audio{i} Qaulity:\n0 is best and 10 is worst\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}" + msg = f"Choose Audio{i} Quality:\n0 is best and 10 is worst\nTimeout: {get_readable_time(self._timeout - (time() - self._time))}" await edit_message(self._reply_to, msg, subbuttons) diff --git a/config_sample.py b/config_sample.py index 2fd037a3d..99e05f810 100644 --- a/config_sample.py +++ b/config_sample.py @@ -21,12 +21,13 @@ EXCLUDED_EXTENSIONS = ( "" # Space separated file extensions to exclude (e.g., .log .exe) ) +INCLUDED_EXTENSIONS = "" INCOMPLETE_TASK_NOTIFIER = ( False # Notify for incomplete tasks on restart (requires DATABASE_URL) ) YT_DLP_OPTIONS = {} # Dictionary of yt-dlp options, e.g., {"format": "bestvideo+bestaudio/best"} USE_SERVICE_ACCOUNTS = False -NAME_SUBSTITUTE = "" # Replace/remove words: "source1/target1|source2/target2" +NAME_SUBSTITUTE = r"" # Replace/remove words: "source1/target1|source2/target2" FFMPEG_CMDS = {} # Predefined FFmpeg commands, e.g., {"preset_name": ["-vf", "scale=1280:-1"]} UPLOAD_PATHS = {} # Named upload paths, e.g., {"movies": "remote:movies/", "tv": "gdrive_id_tv_folder"} diff --git a/docs/CONFIGURATIONS.md b/docs/CONFIGURATIONS.md index d020f8b8c..1d63d11d8 100644 --- a/docs/CONFIGURATIONS.md +++ b/docs/CONFIGURATIONS.md @@ -19,6 +19,7 @@ | `SUDO_USERS` | `str` | User IDs with sudo permission. Separate by spaces. | | `UPLOAD_PATHS` | `dict` | Dict with upload paths. Example: `{"path 1": "remote:", "path 2": "gdrive id", ...}` | | `DEFAULT_UPLOAD` | `str` | `rc` for `RCLONE_PATH`, `gd` for `GDRIVE_ID`. Default: `rc`. [Read More](https://github.com/anasty17/mirror-leech-telegram-bot/tree/master#upload). | +| `INCLUDED_EXTENSIONS` | `str` | File extensions to be uploaded/cloned. `EXCLUDED_EXTENSIONS` will be ignore if you filled this ! Separate them by spaces. | | `EXCLUDED_EXTENSIONS` | `str` | File extensions to skip during processing. Separate by spaces. | | `INCOMPLETE_TASK_NOTIFIER`| `bool` | Notify after restart for incomplete tasks. Requires `DATABASE_URL` and the bot to be in a supergroup. Default: `False`. | | `FILELION_API` | `str` | API key from [FileLion](https://vidhide.com/?op=my_account). | diff --git a/myjd/myjdapi.py b/myjd/myjdapi.py index af7bdc8c2..0584818f3 100644 --- a/myjd/myjdapi.py +++ b/myjd/myjdapi.py @@ -685,7 +685,7 @@ async def solve(self, captcha_id, solution): class Jddevice: def __init__(self, jd): - """This functions initializates the device instance. + """This functions initializes the device instance. It uses the provided dictionary to create the device. :param device_dict: Device dictionary diff --git a/sabnzbd/SABnzbd.ini b/sabnzbd/SABnzbd.ini index 1c1408309..0bf183955 100644 --- a/sabnzbd/SABnzbd.ini +++ b/sabnzbd/SABnzbd.ini @@ -35,9 +35,9 @@ api_key = mltb nzb_key = "" socks5_proxy_url = "" permissions = "" -download_dir = /usr/src/app/downloads +download_dir = /app/downloads download_free = "" -complete_dir = /usr/src/app/downloads +complete_dir = /app/downloads complete_free = "" fulldisk_autoresume = 0 script_dir = "" @@ -284,4 +284,4 @@ nscript_prio_new_login = 0 nscript_prio_warning = 0 nscript_prio_error = 0 nscript_prio_queue_done = 0 -nscript_prio_other = 1 \ No newline at end of file +nscript_prio_other = 1