diff --git a/.gitignore b/.gitignore index a5f2e3b75..9f5c16d31 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -config.env +config.py *.pyc data* .vscode diff --git a/bot/__init__.py b/bot/__init__.py index 0e473b7a2..9096f2835 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -1,35 +1,19 @@ -import subprocess from asyncio import Lock, new_event_loop, set_event_loop -from datetime import datetime from logging import ( ERROR, INFO, + WARNING, FileHandler, - Formatter, - LogRecord, StreamHandler, basicConfig, - error, getLogger, - info, - warning, ) -from os import environ, getcwd, remove -from os import path as ospath -from shutil import rmtree from socket import setdefaulttimeout -from sys import exit from time import time - +import subprocess from apscheduler.schedulers.asyncio import AsyncIOScheduler -from aria2p import API +from aria2p import API as ariaAPI from aria2p import Client as ariaClient -from dotenv import dotenv_values, load_dotenv -from pymongo.mongo_client import MongoClient -from pymongo.server_api import ServerApi -from pyrogram import Client as TgClient -from pyrogram import enums -from pytz import timezone from qbittorrentapi import Client as QbClient from tzlocal import get_localzone from uvloop import install @@ -40,420 +24,68 @@ install() setdefaulttimeout(600) -getLogger("qbittorrentapi").setLevel(INFO) -getLogger("requests").setLevel(INFO) -getLogger("urllib3").setLevel(INFO) +getLogger("qbittorrentapi").setLevel(WARNING) +getLogger("requests").setLevel(WARNING) +getLogger("urllib3").setLevel(WARNING) getLogger("pyrogram").setLevel(ERROR) -getLogger("httpx").setLevel(ERROR) -getLogger("pymongo").setLevel(ERROR) +getLogger("httpx").setLevel(WARNING) +getLogger("pymongo").setLevel(WARNING) bot_start_time = time() bot_loop = new_event_loop() set_event_loop(bot_loop) - -class CustomFormatter(Formatter): - def formatTime( # noqa: N802 - self, - record: LogRecord, - datefmt: str | None, - ) -> str: - dt: datetime = datetime.fromtimestamp( - record.created, - tz=timezone("Asia/Dhaka"), - ) - return dt.strftime(datefmt) - - def format(self, record: LogRecord) -> str: - return super().format(record).replace(record.levelname, record.levelname[:1]) - - -formatter = CustomFormatter( - "[%(asctime)s] %(levelname)s - %(message)s [%(module)s:%(lineno)d]", - datefmt="%d-%b %I:%M:%S %p", +basicConfig( + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[FileHandler("log.txt"), StreamHandler()], + level=INFO, ) -file_handler = FileHandler("log.txt") -file_handler.setFormatter(formatter) - -stream_handler = StreamHandler() -stream_handler.setFormatter(formatter) - -basicConfig(handlers=[file_handler, stream_handler], level=INFO) - LOGGER = getLogger(__name__) -load_dotenv("config.env", override=True) - intervals = {"status": {}, "qb": "", "stopAll": False} qb_torrents = {} -drives_names = [] -drives_ids = [] -index_urls = [] -global_extension_filter = ["aria2", "!qB"] user_data = {} aria2_options = {} qbit_options = {} queued_dl = {} queued_up = {} +status_dict = {} +task_dict = {} +rss_dict = {} non_queued_dl = set() non_queued_up = set() multi_tags = set() -shorteners_list = [] - -try: - if bool(environ.get("_____REMOVE_THIS_LINE_____")): - error("The README.md file there to be read! Exiting now!") - bot_loop.stop() - exit(1) -except Exception: - pass - task_dict_lock = Lock() queue_dict_lock = Lock() qb_listener_lock = Lock() cpu_eater_lock = Lock() subprocess_lock = Lock() same_directory_lock = Lock() -status_dict = {} -task_dict = {} - -BOT_TOKEN = environ.get("BOT_TOKEN", "") -if len(BOT_TOKEN) == 0: - error("BOT_TOKEN variable is missing! Exiting now") - bot_loop.stop() - exit(1) - -BOT_ID = BOT_TOKEN.split(":", 1)[0] - -DATABASE_URL = environ.get("DATABASE_URL", "") -if len(DATABASE_URL) == 0: - DATABASE_URL = "" - -if DATABASE_URL: - try: - conn = MongoClient(DATABASE_URL, server_api=ServerApi("1")) - db = conn.luna - current_config = dict(dotenv_values("config.env")) - old_config = db.settings.deployConfig.find_one({"_id": BOT_ID}) - if old_config is None: - db.settings.deployConfig.replace_one( - {"_id": BOT_ID}, - current_config, - upsert=True, - ) - else: - del old_config["_id"] - if old_config and old_config != current_config: - db.settings.deployConfig.replace_one( - {"_id": BOT_ID}, - current_config, - upsert=True, - ) - elif config_dict := db.settings.config.find_one({"_id": BOT_ID}): - del config_dict["_id"] - for key, value in config_dict.items(): - environ[key] = str(value) - if pf_dict := db.settings.files.find_one({"_id": BOT_ID}): - del pf_dict["_id"] - for key, value in pf_dict.items(): - if value: - file_ = key.replace("__", ".") - with open(file_, "wb+") as f: - f.write(value) - if a2c_options := db.settings.aria2c.find_one({"_id": BOT_ID}): - del a2c_options["_id"] - aria2_options = a2c_options - if qbit_opt := db.settings.qbittorrent.find_one({"_id": BOT_ID}): - del qbit_opt["_id"] - qbit_options = qbit_opt - conn.close() - BOT_TOKEN = environ.get("BOT_TOKEN", "") - BOT_ID = BOT_TOKEN.split(":", 1)[0] - DATABASE_URL = environ.get("DATABASE_URL", "") - except Exception as e: - LOGGER.error(f"Database ERROR: {e}") -else: - config_dict = {} - - -OWNER_ID = environ.get("OWNER_ID", "") -if len(OWNER_ID) == 0: - error("OWNER_ID variable is missing! Exiting now") - bot_loop.stop() - exit(1) -else: - OWNER_ID = int(OWNER_ID) - -TELEGRAM_API = environ.get("TELEGRAM_API", "") -if len(TELEGRAM_API) == 0: - error("TELEGRAM_API variable is missing! Exiting now") - bot_loop.stop() - exit(1) -else: - TELEGRAM_API = int(TELEGRAM_API) - -TELEGRAM_HASH = environ.get("TELEGRAM_HASH", "") -if len(TELEGRAM_HASH) == 0: - error("TELEGRAM_HASH variable is missing! Exiting now") - bot_loop.stop() - exit(1) - -USER_SESSION_STRING = environ.get("USER_SESSION_STRING", "") -if len(USER_SESSION_STRING) != 0: - info("Creating client from USER_SESSION_STRING") - try: - user = TgClient( - "user", - TELEGRAM_API, - TELEGRAM_HASH, - session_string=USER_SESSION_STRING, - parse_mode=enums.ParseMode.HTML, - max_concurrent_transmissions=10, - ).start() - IS_PREMIUM_USER = user.me.is_premium - except Exception: - error("Failed to start client from USER_SESSION_STRING") - IS_PREMIUM_USER = False - user = "" -else: - IS_PREMIUM_USER = False - user = "" - -GDRIVE_ID = environ.get("GDRIVE_ID", "") -if len(GDRIVE_ID) == 0: - GDRIVE_ID = "" - -FSUB_IDS = environ.get("FSUB_IDS", "") -if len(FSUB_IDS) == 0: - FSUB_IDS = "" - -PAID_CHAT_ID = environ.get("PAID_CHAT_ID", "") -PAID_CHAT_ID = "" if len(PAID_CHAT_ID) == 0 else int(PAID_CHAT_ID) - -PAID_CHAT_LINK = environ.get("PAID_CHAT_LINK", "") -if len(PAID_CHAT_LINK) == 0: - PAID_CHAT_LINK = "" - -TOKEN_TIMEOUT = environ.get("TOKEN_TIMEOUT", "") -TOKEN_TIMEOUT = "" if len(TOKEN_TIMEOUT) == 0 else int(TOKEN_TIMEOUT) - -RCLONE_PATH = environ.get("RCLONE_PATH", "") -if len(RCLONE_PATH) == 0: - RCLONE_PATH = "" - -RCLONE_FLAGS = environ.get("RCLONE_FLAGS", "") -if len(RCLONE_FLAGS) == 0: - RCLONE_FLAGS = "" - -DEFAULT_UPLOAD = environ.get("DEFAULT_UPLOAD", "") -if DEFAULT_UPLOAD != "gd": - DEFAULT_UPLOAD = "rc" - -DOWNLOAD_DIR = "/usr/src/app/downloads/" - -AUTHORIZED_CHATS = environ.get("AUTHORIZED_CHATS", "") -if len(AUTHORIZED_CHATS) != 0: - aid = AUTHORIZED_CHATS.split() - for id_ in aid: - chat_id, *thread_ids = id_.split("|") - chat_id = int(chat_id.strip()) - if thread_ids: - thread_ids = [int(x.strip()) for x in thread_ids] - user_data[chat_id] = {"is_auth": True, "thread_ids": thread_ids} - else: - user_data[chat_id] = {"is_auth": True} - -SUDO_USERS = environ.get("SUDO_USERS", "") -if len(SUDO_USERS) != 0: - aid = SUDO_USERS.split() - for id_ in aid: - user_data[int(id_.strip())] = {"is_sudo": True} - -EXTENSION_FILTER = environ.get("EXTENSION_FILTER", "") -if len(EXTENSION_FILTER) > 0: - fx = EXTENSION_FILTER.split() - for x in fx: - x = x.lstrip(".") - global_extension_filter.append(x.strip().lower()) - -FILELION_API = environ.get("FILELION_API", "") -if len(FILELION_API) == 0: - FILELION_API = "" - -STREAMWISH_API = environ.get("STREAMWISH_API", "") -if len(STREAMWISH_API) == 0: - STREAMWISH_API = "" - -INDEX_URL = environ.get("INDEX_URL", "").rstrip("/") -if len(INDEX_URL) == 0: - INDEX_URL = "" - -LEECH_FILENAME_PREFIX = environ.get("LEECH_FILENAME_PREFIX", "") -if len(LEECH_FILENAME_PREFIX) == 0: - LEECH_FILENAME_PREFIX = "" - -MAX_SPLIT_SIZE = 4194304000 if IS_PREMIUM_USER else 2097152000 - -LEECH_SPLIT_SIZE = environ.get("LEECH_SPLIT_SIZE", "") -if ( - len(LEECH_SPLIT_SIZE) == 0 - or int(LEECH_SPLIT_SIZE) > MAX_SPLIT_SIZE - or LEECH_SPLIT_SIZE == "2097152000" -): - LEECH_SPLIT_SIZE = MAX_SPLIT_SIZE -else: - LEECH_SPLIT_SIZE = int(LEECH_SPLIT_SIZE) - -YT_DLP_OPTIONS = environ.get("YT_DLP_OPTIONS", "") -if len(YT_DLP_OPTIONS) == 0: - YT_DLP_OPTIONS = "" - -LEECH_DUMP_CHAT = environ.get("LEECH_DUMP_CHAT", "") -LEECH_DUMP_CHAT = "" if len(LEECH_DUMP_CHAT) == 0 else LEECH_DUMP_CHAT - -MEGA_EMAIL = environ.get("MEGA_EMAIL", "") -MEGA_PASSWORD = environ.get("MEGA_PASSWORD", "") -if len(MEGA_EMAIL) == 0 or len(MEGA_PASSWORD) == 0: - MEGA_EMAIL = "" - MEGA_PASSWORD = "" - -CMD_SUFFIX = environ.get("CMD_SUFFIX", "") - -TORRENT_TIMEOUT = environ.get("TORRENT_TIMEOUT", "") -TORRENT_TIMEOUT = "" if len(TORRENT_TIMEOUT) == 0 else int(TORRENT_TIMEOUT) - -QUEUE_ALL = environ.get("QUEUE_ALL", "") -QUEUE_ALL = "" if len(QUEUE_ALL) == 0 else int(QUEUE_ALL) - -QUEUE_DOWNLOAD = environ.get("QUEUE_DOWNLOAD", "") -QUEUE_DOWNLOAD = "" if len(QUEUE_DOWNLOAD) == 0 else int(QUEUE_DOWNLOAD) - -QUEUE_UPLOAD = environ.get("QUEUE_UPLOAD", "") -QUEUE_UPLOAD = "" if len(QUEUE_UPLOAD) == 0 else int(QUEUE_UPLOAD) - -STOP_DUPLICATE = environ.get("STOP_DUPLICATE", "") -STOP_DUPLICATE = STOP_DUPLICATE.lower() == "true" - -IS_TEAM_DRIVE = environ.get("IS_TEAM_DRIVE", "") -IS_TEAM_DRIVE = IS_TEAM_DRIVE.lower() == "true" - -USE_SERVICE_ACCOUNTS = environ.get("USE_SERVICE_ACCOUNTS", "") -USE_SERVICE_ACCOUNTS = USE_SERVICE_ACCOUNTS.lower() == "true" - -AS_DOCUMENT = environ.get("AS_DOCUMENT", "") -AS_DOCUMENT = AS_DOCUMENT.lower() == "true" - -USER_TRANSMISSION = environ.get("USER_TRANSMISSION", "") -USER_TRANSMISSION = USER_TRANSMISSION.lower() == "true" and IS_PREMIUM_USER - -BASE_URL = environ.get("BASE_URL", "").rstrip("/") -if len(BASE_URL) == 0: - warning("BASE_URL not provided!") - BASE_URL = "" - -UPSTREAM_REPO = environ.get("UPSTREAM_REPO", "") -if len(UPSTREAM_REPO) == 0: - UPSTREAM_REPO = "" - -UPSTREAM_BRANCH = environ.get("UPSTREAM_BRANCH", "") -if len(UPSTREAM_BRANCH) == 0: - UPSTREAM_BRANCH = "master" - -NAME_SUBSTITUTE = environ.get("NAME_SUBSTITUTE", "") -NAME_SUBSTITUTE = "" if len(NAME_SUBSTITUTE) == 0 else NAME_SUBSTITUTE - -MIXED_LEECH = environ.get("MIXED_LEECH", "") -MIXED_LEECH = MIXED_LEECH.lower() == "true" and IS_PREMIUM_USER - -THUMBNAIL_LAYOUT = environ.get("THUMBNAIL_LAYOUT", "") -THUMBNAIL_LAYOUT = "" if len(THUMBNAIL_LAYOUT) == 0 else THUMBNAIL_LAYOUT - -FFMPEG_CMDS = environ.get("FFMPEG_CMDS", "") -try: - FFMPEG_CMDS = [] if len(FFMPEG_CMDS) == 0 else eval(FFMPEG_CMDS) -except Exception: - error(f"Wrong FFMPEG_CMDS format: {FFMPEG_CMDS}") - FFMPEG_CMDS = [] +extension_filter = ["aria2", "!qB"] +drives_names = [] +drives_ids = [] +index_urls = [] +shorteners_list = {} -config_dict = { - "AS_DOCUMENT": AS_DOCUMENT, - "AUTHORIZED_CHATS": AUTHORIZED_CHATS, - "BASE_URL": BASE_URL, - "BOT_TOKEN": BOT_TOKEN, - "CMD_SUFFIX": CMD_SUFFIX, - "DATABASE_URL": DATABASE_URL, - "DEFAULT_UPLOAD": DEFAULT_UPLOAD, - "EXTENSION_FILTER": EXTENSION_FILTER, - "FFMPEG_CMDS": FFMPEG_CMDS, - "FILELION_API": FILELION_API, - "FSUB_IDS": FSUB_IDS, - "GDRIVE_ID": GDRIVE_ID, - "INDEX_URL": INDEX_URL, - "IS_TEAM_DRIVE": IS_TEAM_DRIVE, - "LEECH_DUMP_CHAT": LEECH_DUMP_CHAT, - "LEECH_FILENAME_PREFIX": LEECH_FILENAME_PREFIX, - "LEECH_SPLIT_SIZE": LEECH_SPLIT_SIZE, - "MEGA_EMAIL": MEGA_EMAIL, - "MEGA_PASSWORD": MEGA_PASSWORD, - "MIXED_LEECH": MIXED_LEECH, - "NAME_SUBSTITUTE": NAME_SUBSTITUTE, - "OWNER_ID": OWNER_ID, - "PAID_CHAT_ID": PAID_CHAT_ID, - "PAID_CHAT_LINK": PAID_CHAT_LINK, - "QUEUE_ALL": QUEUE_ALL, - "QUEUE_DOWNLOAD": QUEUE_DOWNLOAD, - "QUEUE_UPLOAD": QUEUE_UPLOAD, - "RCLONE_FLAGS": RCLONE_FLAGS, - "RCLONE_PATH": RCLONE_PATH, - "STOP_DUPLICATE": STOP_DUPLICATE, - "STREAMWISH_API": STREAMWISH_API, - "SUDO_USERS": SUDO_USERS, - "TELEGRAM_API": TELEGRAM_API, - "TELEGRAM_HASH": TELEGRAM_HASH, - "THUMBNAIL_LAYOUT": THUMBNAIL_LAYOUT, - "TORRENT_TIMEOUT": TORRENT_TIMEOUT, - "TOKEN_TIMEOUT": TOKEN_TIMEOUT, - "USER_TRANSMISSION": USER_TRANSMISSION, - "UPSTREAM_REPO": UPSTREAM_REPO, - "UPSTREAM_BRANCH": UPSTREAM_BRANCH, - "USER_SESSION_STRING": USER_SESSION_STRING, - "USE_SERVICE_ACCOUNTS": USE_SERVICE_ACCOUNTS, - "YT_DLP_OPTIONS": YT_DLP_OPTIONS, -} +aria2 = ariaAPI(ariaClient(host="http://localhost", port=6800, secret="")) -if GDRIVE_ID: - drives_names.append("Main") - drives_ids.append(GDRIVE_ID) - index_urls.append(INDEX_URL) +subprocess.run(["xnox", "-d", f"--profile={getcwd()}"], check=False) -if ospath.exists("list_drives.txt"): - with open("list_drives.txt", "r+") as f: - lines = f.readlines() - for line in lines: - temp = line.strip().split() - drives_ids.append(temp[1]) - drives_names.append(temp[0].replace("_", " ")) - if len(temp) > 2: - index_urls.append(temp[2]) - else: - index_urls.append("") -PORT = environ.get("BASE_URL_PORT") or environ.get("PORT") -subprocess.Popen( - f"gunicorn web.wserver:app --bind 0.0.0.0:{PORT} --worker-class gevent", - shell=True, +xnox_client = QbClient( + host="localhost", + port=8090, + VERIFY_WEBUI_CERTIFICATE=False, + REQUESTS_ARGS={"timeout": (30, 60)}, + HTTPADAPTER_ARGS={ + "pool_maxsize": 500, + "max_retries": 10, + "pool_block": True, + }, ) -subprocess.run(["xnox", "-d", f"--profile={getcwd()}"], check=False) - -if not ospath.exists(".netrc"): - with open(".netrc", "w"): - pass -subprocess.run(["chmod", "600", ".netrc"], check=False) -subprocess.run(["cp", ".netrc", "/root/.netrc"], check=False) - trackers = ( subprocess.check_output( "curl -Ns https://raw.githubusercontent.com/XIU2/TrackersListCollection/master/all.txt https://ngosang.github.io/trackerslist/trackers_all_http.txt https://newtrackon.com/api/all https://raw.githubusercontent.com/hezhijie0327/Trackerslist/main/trackerslist_tracker.txt | awk '$0' | tr '\n\n' ','", @@ -479,86 +111,5 @@ def format(self, record: LogRecord) -> str: shorteners_list.append({"domain": temp[0], "api_key": temp[1]}) -if ospath.exists("accounts.zip"): - if ospath.exists("accounts"): - rmtree("accounts") - subprocess.run( - ["7z", "x", "-o.", "-aoa", "accounts.zip", "accounts/*.json"], - check=False, - ) - subprocess.run(["chmod", "-R", "777", "accounts"], check=False) - remove("accounts.zip") -if not ospath.exists("accounts"): - config_dict["USE_SERVICE_ACCOUNTS"] = False - - -alive = subprocess.Popen(["python3", "alive.py"]) - - -xnox_client = QbClient( - host="localhost", - port=8090, - VERIFY_WEBUI_CERTIFICATE=False, - REQUESTS_ARGS={"timeout": (30, 60)}, - HTTPADAPTER_ARGS={ - "pool_maxsize": 500, - "max_retries": 10, - "pool_block": True, - }, -) - - -aria2c_global = [ - "bt-max-open-files", - "download-result", - "keep-unfinished-download-result", - "log", - "log-level", - "max-concurrent-downloads", - "max-download-result", - "max-overall-download-limit", - "save-session", - "max-overall-upload-limit", - "optimize-concurrent-downloads", - "save-cookies", - "server-stat-of", -] - -aria2 = API(ariaClient(host="http://localhost", port=6800, secret="")) - -if not aria2_options: - aria2_options = aria2.client.get_global_option() -else: - a2c_glo = {op: aria2_options[op] for op in aria2c_global if op in aria2_options} - aria2.set_global_options(a2c_glo) - - -def get_qb_options(): - global qbit_options - if not qbit_options: - qbit_options = dict(xnox_client.app_preferences()) - del qbit_options["listen_port"] - for k in list(qbit_options.keys()): - if k.startswith("rss"): - del qbit_options[k] - xnox_client.app_set_preferences({"web_ui_password": "mltbmltb"}) - else: - qbit_options["web_ui_password"] = "mltbmltb" - qb_opt = {**qbit_options} - xnox_client.app_set_preferences(qb_opt) - - -get_qb_options() - -info("Creating client from BOT_TOKEN") -bot = TgClient( - "bot", - TELEGRAM_API, - TELEGRAM_HASH, - bot_token=BOT_TOKEN, - parse_mode=enums.ParseMode.HTML, - max_concurrent_transmissions=10, -).start() -bot_name = bot.me.username scheduler = AsyncIOScheduler(timezone=str(get_localzone()), event_loop=bot_loop) diff --git a/bot/__main__.py b/bot/__main__.py index fe43a9ebe..ff745141d 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,394 +1,56 @@ -# ruff: noqa: F401 -import contextlib -from asyncio import create_subprocess_exec, gather -from html import escape -from os import execl as osexecl +from asyncio import gather from signal import SIGINT, signal -from sys import executable -from time import time -from uuid import uuid4 -from aiofiles import open as aiopen -from aiofiles.os import path as aiopath -from aiofiles.os import remove -from psutil import ( - boot_time, - cpu_count, - cpu_percent, - disk_usage, - net_io_counters, - swap_memory, - virtual_memory, +from .core.config_manager import Config + +from . import LOGGER, bot_loop +from .core.handlers import add_handlers +from .core.aeon_client import TgClient +from .core.startup import ( + load_configurations, + load_settings, + save_settings, + update_aria2_options, + update_qb_options, + update_variables, ) -from pyrogram.filters import command, regex -from pyrogram.handlers import CallbackQueryHandler, MessageHandler - -from bot import ( - LOGGER, - bot, - bot_name, - bot_start_time, - config_dict, - intervals, - scheduler, - user_data, -) - -from .helper.ext_utils.bot_utils import ( - cmd_exec, - create_help_buttons, - new_task, - sync_to_async, -) -from .helper.ext_utils.db_handler import Database +from .helper.ext_utils.bot_utils import create_help_buttons, sync_to_async from .helper.ext_utils.files_utils import clean_all, exit_clean_up -from .helper.ext_utils.status_utils import get_readable_file_size, get_readable_time from .helper.ext_utils.telegraph_helper import telegraph from .helper.listeners.aria2_listener import start_aria2_listener -from .helper.telegram_helper.bot_commands import BotCommands -from .helper.telegram_helper.button_build import ButtonMaker -from .helper.telegram_helper.filters import CustomFilters -from .helper.telegram_helper.message_utils import ( - delete_message, - edit_message, - five_minute_del, - send_file, - send_message, -) +from .helper.mirror_leech_utils.rclone_utils.serve import rclone_serve_booter from .modules import ( - authorize, - bot_settings, - broadcast, - cancel_task, - clone, - exec, - file_selector, - force_start, - gd_count, - gd_delete, - gd_search, - help, - mediainfo, - mirror_leech, - shell, - speedtest, - status, - users_settings, - ytdlp, + get_packages_version, + initiate_search_tools, + restart_notification, ) -@new_task -async def stats(_, message): - if await aiopath.exists(".git"): - last_commit = await cmd_exec( - "git log -1 --date=short --pretty=format:'%cd From %cr'", - True, - ) - last_commit = last_commit[0] - else: - last_commit = "No UPSTREAM_REPO" - total, used, free, disk = disk_usage("/") - swap = swap_memory() - memory = virtual_memory() - stats = ( - f"Commit Date: {last_commit}\n\n" - f"Bot Uptime: {get_readable_time(time() - bot_start_time)}\n" - f"OS Uptime: {get_readable_time(time() - boot_time())}\n\n" - f"Total Disk Space: {get_readable_file_size(total)}\n" - f"Used: {get_readable_file_size(used)} | Free: {get_readable_file_size(free)}\n\n" - f"Upload: {get_readable_file_size(net_io_counters().bytes_sent)}\n" - f"Download: {get_readable_file_size(net_io_counters().bytes_recv)}\n\n" - f"CPU: {cpu_percent(interval=0.5)}%\n" - f"RAM: {memory.percent}%\n" - f"DISK: {disk}%\n\n" - f"Physical Cores: {cpu_count(logical=False)}\n" - f"Total Cores: {cpu_count(logical=True)}\n\n" - f"SWAP: {get_readable_file_size(swap.total)} | Used: {swap.percent}%\n" - f"Memory Total: {get_readable_file_size(memory.total)}\n" - f"Memory Free: {get_readable_file_size(memory.available)}\n" - f"Memory Used: {get_readable_file_size(memory.used)}\n" - ) - await send_message(message, stats) - - -@new_task -async def start(client, message): - if len(message.command) > 1 and message.command[1] == "private": - await delete_message(message) - elif len(message.command) > 1 and len(message.command[1]) == 36: - userid = message.from_user.id - input_token = message.command[1] - stored_token = await Database.get_user_token(userid) - if stored_token is None: - return await send_message( - message, - "This token is not for you!\n\nPlease generate your own.", - ) - if input_token != stored_token: - return await send_message( - message, - "Invalid token.\n\nPlease generate a new one.", - ) - if userid not in user_data: - return await send_message( - message, - "This token is not yours!\n\nKindly generate your own.", - ) - data = user_data[userid] - if "token" not in data or data["token"] != input_token: - return await send_message( - message, - "This token has already been used!\n\nPlease get a new one.", - ) - token = str(uuid4()) - token_time = time() - data["token"] = token - data["time"] = token_time - user_data[userid].update(data) - await Database.update_user_tdata(userid, token, token_time) - msg = "Your token has been successfully generated!\n\n" - msg += f'It will be valid for {get_readable_time(int(config_dict["TOKEN_TIMEOUT"]), True)}' - return await send_message(message, msg) - elif await CustomFilters.authorized(client, message): - help_command = f"/{BotCommands.HelpCommand}" - start_string = f"This bot can mirror all your links|files|torrents to Google Drive or any rclone cloud or to telegram.\nType {help_command} to get a list of available commands" - await send_message(message, start_string) - else: - await send_message(message, "You are not a authorized user!") - await Database.update_pm_users(message.from_user.id) - return None - - -@new_task -async def restart(_, message): - intervals["stopAll"] = True - restart_message = await send_message(message, "Restarting...") - if scheduler.running: - scheduler.shutdown(wait=False) - if qb := intervals["qb"]: - qb.cancel() - if st := intervals["status"]: - for intvl in list(st.values()): - intvl.cancel() - await sync_to_async(clean_all) - proc1 = await create_subprocess_exec( - "pkill", - "-9", - "-f", - "gunicorn|xria|xnox|xtra|xone", - ) - proc2 = await create_subprocess_exec("python3", "update.py") - await gather(proc1.wait(), proc2.wait()) - async with aiopen(".restartmsg", "w") as f: - await f.write(f"{restart_message.chat.id}\n{restart_message.id}\n") - osexecl(executable, executable, "-m", "bot") - - -@new_task -async def ping(_, message): - start_time = int(round(time() * 1000)) - reply = await send_message(message, "Starting Ping") - end_time = int(round(time() * 1000)) - await edit_message(reply, f"{end_time - start_time} ms") - - -@new_task -async def log(_, message): - buttons = ButtonMaker() - buttons.data_button("Log display", f"aeon {message.from_user.id} logdisplay") - reply_message = await send_file( - message, - "log.txt", - buttons=buttons.build_menu(1), - ) - await delete_message(message) - await five_minute_del(reply_message) - - -help_string = f""" -NOTE: Try each command without any argument to see more detalis. -/{BotCommands.MirrorCommand[0]} or /{BotCommands.MirrorCommand[1]}: Start mirroring to cloud. -/{BotCommands.YtdlCommand[0]} or /{BotCommands.YtdlCommand[1]}: Mirror yt-dlp supported link. -/{BotCommands.LeechCommand[0]} or /{BotCommands.LeechCommand[1]}: Start leeching to Telegram. -/{BotCommands.YtdlLeechCommand[0]} or /{BotCommands.YtdlLeechCommand[1]}: Leech yt-dlp supported link. -/{BotCommands.CloneCommand} [drive_url]: Copy file/folder to Google Drive. -/{BotCommands.CountCommand} [drive_url]: Count file/folder of Google Drive. -/{BotCommands.DeleteCommand} [drive_url]: Delete file/folder from Google Drive (Only Owner & Sudo). -/{BotCommands.UserSetCommand[0]} or /{BotCommands.UserSetCommand[1]} [query]: Users settings. -/{BotCommands.BotSetCommand[0]} or /{BotCommands.BotSetCommand[1]} [query]: Bot settings. -/{BotCommands.SelectCommand}: Select files from torrents by gid or reply. -/{BotCommands.ForceStartCommand[0]} or /{BotCommands.ForceStartCommand[1]} [gid]: Force start task by gid or reply. -/{BotCommands.CancelAllCommand} [query]: Cancel all [status] tasks. -/{BotCommands.ListCommand} [query]: Search in Google Drive(s). -/{BotCommands.StatusCommand}: Shows a status of all the downloads. -/{BotCommands.StatsCommand}: Show stats of the machine where the bot is hosted in. -/{BotCommands.PingCommand}: Check how long it takes to Ping the Bot (Only Owner & Sudo). -/{BotCommands.AuthorizeCommand}: Authorize a chat or a user to use the bot (Only Owner & Sudo). -/{BotCommands.UnAuthorizeCommand}: Unauthorize a chat or a user to use the bot (Only Owner & Sudo). -/{BotCommands.UsersCommand}: show users settings (Only Owner & Sudo). -/{BotCommands.AddSudoCommand}: Add sudo user (Only Owner). -/{BotCommands.RmSudoCommand}: Remove sudo users (Only Owner). -/{BotCommands.RestartCommand}: Restart and update the bot (Only Owner & Sudo). -/{BotCommands.LogCommand}: Get a log file of the bot. Handy for getting crash reports (Only Owner & Sudo). -/{BotCommands.ShellCommand}: Run shell commands (Only Owner). -/{BotCommands.AExecCommand}: Exec async functions (Only Owner). -/{BotCommands.ExecCommand}: Exec sync functions (Only Owner). -/{BotCommands.ClearLocalsCommand}: Clear {BotCommands.AExecCommand} or {BotCommands.ExecCommand} locals (Only Owner). -""" - - -@new_task -async def bot_help(_, message): - await send_message(message, help_string) - - -async def restart_notification(): - if await aiopath.isfile(".restartmsg"): - cmd = r"""remote_url=$(git config --get remote.origin.url) && - if echo "$remote_url" | grep -qE "github\.com[:/](.*)/(.*?)(\.git)?$"; then - last_commit=$(git log -1 --pretty=format:'%h') && - commit_link="https://github.com/AeonOrg/Aeon-MLTB/commit/$last_commit" && - echo $commit_link; - else - echo "Failed to extract repository name and owner name from the remote URL."; - fi""" - - result = await cmd_exec(cmd, True) - - commit_link = result[0] - - async with aiopen(".restartmsg") as f: - content = await f.read() - chat_id, msg_id = map(int, content.splitlines()) - - try: - await bot.edit_message_text( - chat_id=chat_id, - message_id=msg_id, - text=f'Restarted Successfully!', - ) - except Exception as e: - print(f"Failed to edit message: {e}") - await remove(".restartmsg") - - -@new_task -async def aeon_callback(_, query): - message = query.message - user_id = query.from_user.id - data = query.data.split() - if user_id != int(data[1]): - return await query.answer(text="This message not your's!", show_alert=True) - if data[2] == "logdisplay": - await query.answer() - async with aiopen("log.txt") as f: - logFileLines = (await f.read()).splitlines() - - def parseline(line): - try: - return line.split("] ", 1)[1] - except IndexError: - return line - - ind, Loglines = 1, "" - try: - while len(Loglines) <= 3500: - Loglines = parseline(logFileLines[-ind]) + "\n" + Loglines - if ind == len(logFileLines): - break - ind += 1 - startLine = "
" - endLine = "" - btn = ButtonMaker() - btn.data_button("Close", f"aeon {user_id} close") - reply_message = await send_message( - message, - startLine + escape(Loglines) + endLine, - btn.build_menu(1), - ) - await query.edit_message_reply_markup(None) - await delete_message(message) - await five_minute_del(reply_message) - except Exception as err: - LOGGER.error(f"TG Log Display : {err!s}") - elif data[2] == "private": - await query.answer(url=f"https://t.me/{bot_name}?start=private") - return None - else: - await query.answer() - await delete_message(message) - return None - +Config.load() async def main(): - if config_dict["DATABASE_URL"]: - await Database.db_load() + await load_settings() + await gather(TgClient.start_bot(), TgClient.start_user()) + await gather(load_configurations(), update_variables()) + await gather( + sync_to_async(update_qb_options), + sync_to_async(update_aria2_options), + ) await gather( + save_settings(), sync_to_async(clean_all), + initiate_search_tools(), + get_packages_version(), restart_notification(), telegraph.create_account(), + rclone_serve_booter(), sync_to_async(start_aria2_listener, wait=False), ) create_help_buttons() - - bot.add_handler( - MessageHandler( - start, - filters=command( - BotCommands.StartCommand, - ), - ), - ) - bot.add_handler( - MessageHandler( - log, - filters=command( - BotCommands.LogCommand, - ) - & CustomFilters.sudo, - ), - ) - bot.add_handler( - MessageHandler( - restart, - filters=command( - BotCommands.RestartCommand, - ) - & CustomFilters.sudo, - ), - ) - bot.add_handler( - MessageHandler( - ping, - filters=command( - BotCommands.PingCommand, - ) - & CustomFilters.authorized, - ), - ) - bot.add_handler( - MessageHandler( - bot_help, - filters=command( - BotCommands.HelpCommand, - ) - & CustomFilters.authorized, - ), - ) - bot.add_handler( - MessageHandler( - stats, - filters=command( - BotCommands.StatsCommand, - ) - & CustomFilters.authorized, - ), - ) - bot.add_handler(CallbackQueryHandler(aeon_callback, filters=regex(r"^aeon"))) + add_handlers() LOGGER.info("Bot Started!") signal(SIGINT, exit_clean_up) -bot.loop.run_until_complete(main()) -bot.loop.run_forever() +bot_loop.run_until_complete(main()) +bot_loop.run_forever() diff --git a/bot/core/__init__.py b/bot/core/__init__.py new file mode 100644 index 000000000..d3f5a12fa --- /dev/null +++ b/bot/core/__init__.py @@ -0,0 +1 @@ + diff --git a/bot/core/aeon_client.py b/bot/core/aeon_client.py new file mode 100644 index 000000000..af042436b --- /dev/null +++ b/bot/core/aeon_client.py @@ -0,0 +1,73 @@ +from asyncio import Lock + +from pyrogram import Client, enums + +from bot import LOGGER + +from .config_manager import Config + + +class TgClient: + _lock = Lock() + bot = None + user = None + NAME = "" + ID = 0 + IS_PREMIUM_USER = False + MAX_SPLIT_SIZE = 2097152000 + + @classmethod + async def start_bot(cls): + LOGGER.info("Creating client from BOT_TOKEN") + cls.bot = Client( + "bot", + Config.TELEGRAM_API, + Config.TELEGRAM_HASH, + bot_token=Config.BOT_TOKEN, + parse_mode=enums.ParseMode.HTML, + sleep_threshold=60, + max_concurrent_transmissions=10, + ) + await cls.bot.start() + cls.NAME = cls.bot.me.username + cls.ID = Config.BOT_TOKEN.split(":", 1)[0] + + @classmethod + async def start_user(cls): + if Config.USER_SESSION_STRING: + LOGGER.info("Creating client from USER_SESSION_STRING") + try: + cls.user = Client( + "user", + Config.TELEGRAM_API, + Config.TELEGRAM_HASH, + session_string=Config.USER_SESSION_STRING, + parse_mode=enums.ParseMode.HTML, + sleep_threshold=60, + max_concurrent_transmissions=10, + ) + await cls.user.start() + cls.IS_PREMIUM_USER = cls.user.me.is_premium + if cls.IS_PREMIUM_USER: + cls.MAX_SPLIT_SIZE = 4194304000 + except Exception as e: + LOGGER.error(f"Failed to start client from USER_SESSION_STRING. {e}") + cls.IS_PREMIUM_USER = False + cls.user = None + + @classmethod + async def stop(cls): + async with cls._lock: + if cls.bot: + await cls.bot.stop() + if cls.user: + await cls.user.stop() + LOGGER.info("Client stopped") + + @classmethod + async def reload(cls): + async with cls._lock: + await cls.bot.restart() + if cls.user: + await cls.user.restart() + LOGGER.info("Client restarted") diff --git a/bot/core/config_manager.py b/bot/core/config_manager.py new file mode 100644 index 000000000..5abd013cf --- /dev/null +++ b/bot/core/config_manager.py @@ -0,0 +1,122 @@ +from importlib import import_module + + +class Config: + AS_DOCUMENT = False + AUTHORIZED_CHATS = "" + BASE_URL = "" + BASE_URL_PORT = 80 + BOT_TOKEN = "" + CMD_SUFFIX = "" + DATABASE_URL = "" + DEFAULT_UPLOAD = "rc" + DOWNLOAD_DIR = "/usr/src/app/downloads/" + EQUAL_SPLITS = False + EXTENSION_FILTER = "" + FFMPEG_CMDS = [] + FILELION_API = "" + GDRIVE_ID = "" + INCOMPLETE_TASK_NOTIFIER = False + INDEX_URL = "" + IS_TEAM_DRIVE = False + LEECH_DUMP_CHAT = "" + LEECH_FILENAME_PREFIX = "" + LEECH_SPLIT_SIZE = 2097152000 + MEDIA_GROUP = False + MIXED_LEECH = False + NAME_SUBSTITUTE = "" + OWNER_ID = 0 + QUEUE_ALL = 0 + QUEUE_DOWNLOAD = 0 + QUEUE_UPLOAD = 0 + RCLONE_FLAGS = "" + RCLONE_PATH = "" + RCLONE_SERVE_URL = "" + RCLONE_SERVE_USER = "" + RCLONE_SERVE_PASS = "" + RCLONE_SERVE_PORT = 8080 + RSS_CHAT = "" + RSS_DELAY = 600 + SEARCH_API_LINK = "" + SEARCH_LIMIT = 0 + SEARCH_PLUGINS = [] + STATUS_LIMIT = 10 + STATUS_UPDATE_INTERVAL = 15 + STOP_DUPLICATE = False + STREAMWISH_API = "" + SUDO_USERS = "" + TELEGRAM_API = 0 + TELEGRAM_HASH = "" + THUMBNAIL_LAYOUT = "" + TORRENT_TIMEOUT = 0 + USER_TRANSMISSION = False + UPSTREAM_REPO = "" + UPSTREAM_BRANCH = "master" + USENET_SERVERS = [] + USER_SESSION_STRING = "" + USE_SERVICE_ACCOUNTS = False + WEB_PINCODE = False + YT_DLP_OPTIONS = "" + + @classmethod + def get(cls, key): + if hasattr(cls, key): + return getattr(cls, key) + raise KeyError(f"{key} is not a valid configuration key.") + + @classmethod + def set(cls, key, value): + if hasattr(cls, key): + setattr(cls, key, value) + else: + raise KeyError(f"{key} is not a valid configuration key.") + + @classmethod + def get_all(cls): + return { + key: getattr(cls, key) + for key in cls.__dict__ + if not key.startswith("__") and not callable(getattr(cls, key)) + } + + @classmethod + def load(cls): + settings = import_module("config") + for attr in dir(settings): + if hasattr(cls, attr): + value = getattr(settings, attr) + if isinstance(value, str): + value = value.strip() + if not value: + continue + if attr == "DEFAULT_UPLOAD" and value != "rc": + value = "gd" + elif attr == "DOWNLOAD_DIR" and not value.endswith("/"): + value = f"{value}/" + setattr(cls, attr, value) + for key in ["BOT_TOKEN", "OWNER_ID", "TELEGRAM_API", "TELEGRAM_HASH"]: + value = getattr(cls, key) + if isinstance(value, str): + value = value.strip() + if not value: + raise ValueError(f"{key} variable is missing!") + + @classmethod + def load_dict(cls, config_dict): + for key, value in config_dict.items(): + if hasattr(cls, key): + if key == "DEFAULT_UPLOAD" and value != "rc": + value = "gd" + elif key == "DOWNLOAD_DIR": + if not value.endswith("/"): + value = f"{value}/" + elif key == "USENET_SERVERS": + if not value[0].get("host"): + continue + setattr(cls, key, value) + for key in ["BOT_TOKEN", "OWNER_ID", "TELEGRAM_API", "TELEGRAM_HASH"]: + value = getattr(cls, key) + if isinstance(value, str): + value = value.strip() + if not value: + raise ValueError(f"{key} variable is missing!") diff --git a/bot/core/handlers.py b/bot/core/handlers.py new file mode 100644 index 000000000..0e72a9d13 --- /dev/null +++ b/bot/core/handlers.py @@ -0,0 +1,290 @@ +from pyrogram.filters import command, regex +from pyrogram.handlers import ( + CallbackQueryHandler, + EditedMessageHandler, + MessageHandler, +) + +from bot.helper.telegram_helper.bot_commands import BotCommands +from bot.helper.telegram_helper.filters import CustomFilters +from bot.modules import * + +from .aeon_client import TgClient + + +def add_handlers(): + TgClient.bot.add_handler( + MessageHandler( + authorize, + filters=command(BotCommands.AuthorizeCommand, case_sensitive=True) + & CustomFilters.sudo, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + unauthorize, + filters=command(BotCommands.UnAuthorizeCommand, case_sensitive=True) + & CustomFilters.sudo, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + add_sudo, + filters=command(BotCommands.AddSudoCommand, case_sensitive=True) + & CustomFilters.sudo, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + remove_sudo, + filters=command(BotCommands.RmSudoCommand, case_sensitive=True) + & CustomFilters.sudo, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + send_bot_settings, + filters=command(BotCommands.BotSetCommand, case_sensitive=True) + & CustomFilters.sudo, + ), + ) + TgClient.bot.add_handler( + CallbackQueryHandler( + edit_bot_settings, + filters=regex("^botset") & CustomFilters.sudo, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + cancel, + filters=command(BotCommands.CancelTaskCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + cancel_all_buttons, + filters=command(BotCommands.CancelAllCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + CallbackQueryHandler(cancel_all_update, filters=regex("^canall")), + ) + TgClient.bot.add_handler( + CallbackQueryHandler(cancel_multi, filters=regex("^stopm")), + ) + TgClient.bot.add_handler( + MessageHandler( + clone_node, + filters=command(BotCommands.CloneCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + aioexecute, + filters=command(BotCommands.AExecCommand, case_sensitive=True) + & CustomFilters.owner, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + execute, + filters=command(BotCommands.ExecCommand, case_sensitive=True) + & CustomFilters.owner, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + clear, + filters=command(BotCommands.ClearLocalsCommand, case_sensitive=True) + & CustomFilters.owner, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + select, + filters=command(BotCommands.SelectCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + CallbackQueryHandler(confirm_selection, filters=regex("^sel")), + ) + TgClient.bot.add_handler( + MessageHandler( + remove_from_queue, + filters=command(BotCommands.ForceStartCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + count_node, + filters=command(BotCommands.CountCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + delete_file, + filters=command(BotCommands.DeleteCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + gdrive_search, + filters=command(BotCommands.ListCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + CallbackQueryHandler(select_type, filters=regex("^list_types")), + ) + TgClient.bot.add_handler(CallbackQueryHandler(arg_usage, filters=regex("^help"))) + TgClient.bot.add_handler( + MessageHandler( + mirror, + filters=command(BotCommands.MirrorCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + qb_mirror, + filters=command(BotCommands.QbMirrorCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + leech, + filters=command(BotCommands.LeechCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + qb_leech, + filters=command(BotCommands.QbLeechCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + get_rss_menu, + filters=command(BotCommands.RssCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + CallbackQueryHandler(rss_listener, filters=regex("^rss")) + ) + TgClient.bot.add_handler( + MessageHandler( + run_shell, + filters=command(BotCommands.ShellCommand, case_sensitive=True) + & CustomFilters.owner, + ), + ) + TgClient.bot.add_handler( + EditedMessageHandler( + run_shell, + filters=command(BotCommands.ShellCommand, case_sensitive=True) + & CustomFilters.owner, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + start, + filters=command(BotCommands.StartCommand, case_sensitive=True), + ), + ) + TgClient.bot.add_handler( + MessageHandler( + log, + filters=command(BotCommands.LogCommand, case_sensitive=True) + & CustomFilters.sudo, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + restart_bot, + filters=command(BotCommands.RestartCommand, case_sensitive=True) + & CustomFilters.sudo, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + ping, + filters=command(BotCommands.PingCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + bot_help, + filters=command(BotCommands.HelpCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + bot_stats, + filters=command(BotCommands.StatsCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + task_status, + filters=command(BotCommands.StatusCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + CallbackQueryHandler(status_pages, filters=regex("^status")), + ) + TgClient.bot.add_handler( + MessageHandler( + torrent_search, + filters=command(BotCommands.SearchCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + CallbackQueryHandler(torrent_search_update, filters=regex("^torser")), + ) + TgClient.bot.add_handler( + MessageHandler( + get_users_settings, + filters=command(BotCommands.UsersCommand, case_sensitive=True) + & CustomFilters.sudo, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + send_user_settings, + filters=command(BotCommands.UserSetCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + CallbackQueryHandler(edit_user_settings, filters=regex("^userset")), + ) + TgClient.bot.add_handler( + MessageHandler( + ytdl, + filters=command(BotCommands.YtdlCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) + TgClient.bot.add_handler( + MessageHandler( + ytdl_leech, + filters=command(BotCommands.YtdlLeechCommand, case_sensitive=True) + & CustomFilters.authorized, + ), + ) diff --git a/bot/core/startup.py b/bot/core/startup.py new file mode 100644 index 000000000..72a517fad --- /dev/null +++ b/bot/core/startup.py @@ -0,0 +1,243 @@ +from asyncio import create_subprocess_exec, create_subprocess_shell + +import subprocess + +from aiofiles import open as aiopen +from aiofiles.os import makedirs, remove +from aiofiles.os import path as aiopath +from aioshutil import rmtree +from os import environ, getcwd + +from bot import ( + LOGGER, + aria2, + aria2_options, + drives_ids, + drives_names, + extension_filter, + index_urls, + qbit_options, + xnox_client, + rss_dict, + user_data, +) +from bot.helper.ext_utils.db_handler import database + +from .config_manager import Config +from .aeon_client import TgClient + + +def update_qb_options(): + if not qbit_options: + qbit_options.update(dict(xnox_client.app_preferences())) + del qbit_options["listen_port"] + for k in list(qbit_options.keys()): + if k.startswith("rss"): + del qbit_options[k] + qbit_options["web_ui_password"] = "mltbmltb" + xnox_client.app_set_preferences({"web_ui_password": "mltbmltb"}) + else: + xnox_client.app_set_preferences(qbit_options) + + +def update_aria2_options(): + if not aria2_options: + aria2_options.update(aria2.client.get_global_option()) + else: + aria2.set_global_options(aria2_options) + + +async def load_settings(): + if not Config.DATABASE_URL: + return + await database.connect() + if database.db is not None: + BOT_ID = Config.BOT_TOKEN.split(":", 1)[0] + config_file = Config.get_all() + old_config = await database.db.settings.deployConfig.find_one( + {"_id": BOT_ID} + ) + if old_config is None: + database.db.settings.deployConfig.replace_one( + {"_id": BOT_ID}, + config_file, + upsert=True, + ) + else: + del old_config["_id"] + if old_config and old_config != config_file: + await database.db.settings.deployConfig.replace_one( + {"_id": BOT_ID}, + config_file, + upsert=True, + ) + else: + config_dict = await database.db.settings.config.find_one( + {"_id": BOT_ID}, + {"_id": 0}, + ) + if config_dict: + Config.load_dict(config_dict) + + if pf_dict := await database.db.settings.files.find_one( + {"_id": BOT_ID}, + {"_id": 0}, + ): + for key, value in pf_dict.items(): + if value: + file_ = key.replace("__", ".") + async with aiopen(file_, "wb+") as f: + await f.write(value) + + if a2c_options := await database.db.settings.aria2c.find_one( + {"_id": BOT_ID}, + {"_id": 0}, + ): + aria2_options.update(a2c_options) + + if qbit_opt := await database.db.settings.qbittorrent.find_one( + {"_id": BOT_ID}, + {"_id": 0}, + ): + qbit_options.update(qbit_opt) + + if await database.db.users.find_one(): + rows = database.db.users.find({}) + async for row in rows: + uid = row["_id"] + del row["_id"] + thumb_path = f"Thumbnails/{uid}.jpg" + rclone_config_path = f"rclone/{uid}.conf" + token_path = f"tokens/{uid}.pickle" + if row.get("thumb"): + if not await aiopath.exists("Thumbnails"): + await makedirs("Thumbnails") + async with aiopen(thumb_path, "wb+") as f: + await f.write(row["thumb"]) + row["thumb"] = thumb_path + if row.get("rclone_config"): + if not await aiopath.exists("rclone"): + await makedirs("rclone") + async with aiopen(rclone_config_path, "wb+") as f: + await f.write(row["rclone_config"]) + row["rclone_config"] = rclone_config_path + if row.get("token_pickle"): + if not await aiopath.exists("tokens"): + await makedirs("tokens") + async with aiopen(token_path, "wb+") as f: + await f.write(row["token_pickle"]) + row["token_pickle"] = token_path + user_data[uid] = row + LOGGER.info("Users data has been imported from Database") + + if await database.db.rss[BOT_ID].find_one(): + rows = database.db.rss[BOT_ID].find({}) + async for row in rows: + user_id = row["_id"] + del row["_id"] + rss_dict[user_id] = row + LOGGER.info("Rss data has been imported from Database.") + + +async def save_settings(): + if database.db is None: + return + config_dict = Config.get_all() + await database.db.settings.config.replace_one( + {"_id": TgClient.ID}, + config_dict, + upsert=True, + ) + if await database.db.settings.aria2c.find_one({"_id": TgClient.ID}) is None: + await database.db.settings.aria2c.update_one( + {"_id": TgClient.ID}, + {"$set": aria2_options}, + upsert=True, + ) + if await database.db.settings.qbittorrent.find_one({"_id": TgClient.ID}) is None: + await database.save_qbit_settings() + + +async def update_variables(): + if ( + Config.LEECH_SPLIT_SIZE > TgClient.MAX_SPLIT_SIZE + or Config.LEECH_SPLIT_SIZE == 2097152000 + or not Config.LEECH_SPLIT_SIZE + ): + Config.LEECH_SPLIT_SIZE = TgClient.MAX_SPLIT_SIZE + + Config.MIXED_LEECH = bool(Config.MIXED_LEECH and TgClient.IS_PREMIUM_USER) + Config.USER_TRANSMISSION = bool( + Config.USER_TRANSMISSION and TgClient.IS_PREMIUM_USER, + ) + + if Config.AUTHORIZED_CHATS: + aid = Config.AUTHORIZED_CHATS.split() + for id_ in aid: + chat_id, *thread_ids = id_.split("|") + chat_id = int(chat_id.strip()) + if thread_ids: + thread_ids = [int(x.strip()) for x in thread_ids] + user_data[chat_id] = {"is_auth": True, "thread_ids": thread_ids} + else: + user_data[chat_id] = {"is_auth": True} + + if Config.SUDO_USERS: + aid = Config.SUDO_USERS.split() + for id_ in aid: + user_data[int(id_.strip())] = {"is_sudo": True} + + if Config.EXTENSION_FILTER: + fx = Config.EXTENSION_FILTER.split() + for x in fx: + x = x.lstrip(".") + extension_filter.append(x.strip().lower()) + + if Config.GDRIVE_ID: + drives_names.append("Main") + drives_ids.append(Config.GDRIVE_ID) + index_urls.append(Config.INDEX_URL) + + if await aiopath.exists("list_drives.txt"): + async with aiopen("list_drives.txt", "r+") as f: + lines = f.readlines() + for line in lines: + temp = line.strip().split() + drives_ids.append(temp[1]) + drives_names.append(temp[0].replace("_", " ")) + if len(temp) > 2: + index_urls.append(temp[2]) + else: + index_urls.append("") + + if not await aiopath.exists("accounts"): + Config.USE_SERVICE_ACCOUNTS = False + + +async def load_configurations(): + if not await aiopath.exists(".netrc"): + async with aiopen(".netrc", "w"): + pass + subprocess.run(["chmod", "600", ".netrc"], check=False) + subprocess.run(["cp", ".netrc", "/root/.netrc"], check=False) + + PORT = environ.get("BASE_URL_PORT") or environ.get("PORT") + await create_subprocess_shell( + f"gunicorn web.wserver:app --bind 0.0.0.0:{PORT} --worker-class gevent", + ) + + if await aiopath.exists("accounts.zip"): + if await aiopath.exists("accounts"): + await rmtree("accounts") + await ( + await create_subprocess_exec( + "7z", + "x", + "-o.", + "-aoa", + "accounts.zip", + "accounts/*.json", + ) + ).wait() + await (await create_subprocess_exec("chmod", "-R", "777", "accounts")).wait() + await remove("accounts.zip") diff --git a/bot/helper/common.py b/bot/helper/common.py index f6007f60f..d85a3401a 100644 --- a/bot/helper/common.py +++ b/bot/helper/common.py @@ -4,7 +4,7 @@ from os import path as ospath from os import walk from re import IGNORECASE, sub -from secrets import token_hex +from secrets import token_urlsafe from aiofiles.os import makedirs, remove from aiofiles.os import path as aiopath @@ -12,21 +12,18 @@ from pyrogram.enums import ChatAction from bot import ( - DOWNLOAD_DIR, - IS_PREMIUM_USER, LOGGER, - MAX_SPLIT_SIZE, - config_dict, cpu_eater_lock, - global_extension_filter, + extension_filter, intervals, multi_tags, subprocess_lock, task_dict, task_dict_lock, - user, user_data, ) +from bot.core.config_manager import Config +from bot.core.aeon_client import TgClient from .ext_utils.bot_utils import get_size_bytes, new_task, sync_to_async from .ext_utils.bulk_links import extract_bulk_links @@ -59,6 +56,7 @@ from .mirror_leech_utils.rclone_utils.list import RcloneList from .mirror_leech_utils.status_utils.ffmpeg_status import FFmpegStatus from .mirror_leech_utils.status_utils.sevenz_status import SevenZStatus +from .telegram_helper.bot_commands import BotCommands from .telegram_helper.message_utils import ( get_tg_link_message, send_message, @@ -72,7 +70,7 @@ def __init__(self): self.user = self.message.from_user or self.message.sender_chat self.user_id = self.user.id self.user_dict = user_data.get(self.user_id, {}) - self.dir = f"{DOWNLOAD_DIR}{self.mid}" + self.dir = f"{Config.DOWNLOAD_DIR}{self.mid}" self.link = "" self.up_dest = "" self.rc_flags = "" @@ -90,6 +88,7 @@ def __init__(self): self.is_qbit = False self.is_clone = False self.is_ytdlp = False + self.equal_splits = False self.user_transmission = False self.mixed_leech = False self.extract = False @@ -123,7 +122,7 @@ def get_token_path(self, dest): if dest.startswith("mtp:"): return f"tokens/{self.user_id}.pickle" if dest.startswith("sa:") or ( - config_dict["USE_SERVICE_ACCOUNTS"] and not dest.startswith("tp:") + Config.USE_SERVICE_ACCOUNTS and not dest.startswith("tp:") ): return "accounts" return "token.pickle" @@ -155,34 +154,30 @@ async def before_start(self): self.name_sub = ( self.name_sub or self.user_dict.get("name_sub", False) - or ( - config_dict["NAME_SUBSTITUTE"] - if "name_sub" not in self.user_dict - else "" - ) + or (Config.NAME_SUBSTITUTE if "name_sub" not in self.user_dict else "") ) if self.name_sub: self.name_sub = [x.split("/") for x in self.name_sub.split(" | ")] self.seed = False self.extension_filter = self.user_dict.get("excluded_extensions") or ( - global_extension_filter + extension_filter if "excluded_extensions" not in self.user_dict else ["aria2", "!qB"] ) if self.link not in ["rcl", "gdl"]: - if is_rclone_path(self.link): - if not self.link.startswith("mrcc:") and self.user_dict.get( - "user_tokens", - False, - ): - self.link = f"mrcc:{self.link}" - await self.is_token_exists(self.link, "dl") - elif is_gdrive_link(self.link): - if not self.link.startswith( - ("mtp:", "tp:", "sa:"), - ) and self.user_dict.get("user_tokens", False): - self.link = f"mtp:{self.link}" - await self.is_token_exists(self.link, "dl") + if is_rclone_path(self.link): + if not self.link.startswith("mrcc:") and self.user_dict.get( + "user_tokens", + False, + ): + self.link = f"mrcc:{self.link}" + await self.is_token_exists(self.link, "dl") + elif is_gdrive_link(self.link): + if not self.link.startswith( + ("mtp:", "tp:", "sa:"), + ) and self.user_dict.get("user_tokens", False): + self.link = f"mtp:{self.link}" + await self.is_token_exists(self.link, "dl") elif self.link == "rcl": if not self.is_ytdlp: self.link = await RcloneList(self).get_rclone_path("rcd") @@ -193,10 +188,10 @@ async def before_start(self): if not is_gdrive_id(self.link): raise ValueError(self.link) - self.user_transmission = IS_PREMIUM_USER and ( + self.user_transmission = TgClient.IS_PREMIUM_USER and ( self.user_dict.get("user_transmission") or ( - config_dict["USER_TRANSMISSION"] + Config.USER_TRANSMISSION and "user_transmission" not in self.user_dict ) ) @@ -211,34 +206,26 @@ async def before_start(self): self.ffmpeg_cmds = ( self.ffmpeg_cmds or self.user_dict.get("ffmpeg_cmds", None) - or ( - config_dict["FFMPEG_CMDS"] - if "ffmpeg_cmds" not in self.user_dict - else None - ) + or (Config.FFMPEG_CMDS if "ffmpeg_cmds" not in self.user_dict else None) ) if self.ffmpeg_cmds: self.seed = False if not self.is_leech: self.stop_duplicate = self.user_dict.get("stop_duplicate") or ( - "stop_duplicate" not in self.user_dict - and config_dict["STOP_DUPLICATE"] + "stop_duplicate" not in self.user_dict and Config.STOP_DUPLICATE ) default_upload = ( - self.user_dict.get("default_upload", "") - or config_dict["DEFAULT_UPLOAD"] + self.user_dict.get("default_upload", "") or Config.DEFAULT_UPLOAD ) if (not self.up_dest and default_upload == "rc") or self.up_dest == "rc": self.up_dest = ( - self.user_dict.get("rclone_path") or config_dict["RCLONE_PATH"] + self.user_dict.get("rclone_path") or Config.RCLONE_PATH ) elif ( not self.up_dest and default_upload == "gd" ) or self.up_dest == "gd": - self.up_dest = ( - self.user_dict.get("gdrive_id") or config_dict["GDRIVE_ID"] - ) + self.up_dest = self.user_dict.get("gdrive_id") or Config.GDRIVE_ID if not self.up_dest: raise ValueError("No Upload Destination!") if is_gdrive_id(self.up_dest): @@ -301,14 +288,11 @@ async def before_start(self): self.up_dest = ( self.up_dest or self.user_dict.get("leech_dest") - or config_dict["LEECH_DUMP_CHAT"] + or Config.LEECH_DUMP_CHAT ) - self.mixed_leech = IS_PREMIUM_USER and ( + self.mixed_leech = TgClient.IS_PREMIUM_USER and ( self.user_dict.get("mixed_leech") - or ( - config_dict["MIXED_LEECH"] - and "mixed_leech" not in self.user_dict - ) + or (Config.MIXED_LEECH and "mixed_leech" not in self.user_dict) ) if self.up_dest: if not isinstance(self.up_dest, int): @@ -318,9 +302,9 @@ async def before_start(self): self.mixed_leech = False elif self.up_dest.startswith("u:"): self.up_dest = self.up_dest.replace("u:", "", 1) - self.user_transmission = IS_PREMIUM_USER + self.user_transmission = TgClient.IS_PREMIUM_USER elif self.up_dest.startswith("m:"): - self.user_transmission = IS_PREMIUM_USER + self.user_transmission = TgClient.IS_PREMIUM_USER self.mixed_leech = self.user_transmission if "|" in self.up_dest: self.up_dest, self.chat_thread_id = [ @@ -333,33 +317,58 @@ async def before_start(self): self.up_dest = self.user_id if self.user_transmission: - chat = await user.get_chat(self.up_dest) - uploader_id = user.me.id - else: - chat = await self.client.get_chat(self.up_dest) - uploader_id = self.client.me.id + try: + chat = await TgClient.user.get_chat(self.up_dest) + except: + chat = None + if chat is None: + self.user_transmission = False + self.mixed_leech = False + else: + uploader_id = TgClient.user.me.id + if chat.type.name not in ["SUPERGROUP", "CHANNEL", "GROUP"]: + self.user_transmission = False + self.mixed_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.mixed_leech = False - if chat.type.name in ["SUPERGROUP", "CHANNEL"]: - member = await chat.get_member(uploader_id) - if ( - not member.privileges.can_manage_chat - or not member.privileges.can_delete_messages - ): - raise ValueError( - "You don't have enough privileges in this chat!", - ) - elif self.user_transmission: - raise ValueError( - "Custom Leech Destination only allowed for super-group or channel when UserTransmission enalbed!\nDisable UserTransmission so bot can send files to user!", - ) - else: + if not self.user_transmission or self.mixed_leech: try: - await self.client.send_chat_action( - self.up_dest, - ChatAction.TYPING, - ) - except Exception: - raise ValueError("Start the bot and try again!") from None + chat = await self.client.get_chat(self.up_dest) + except: + chat = None + if chat is None: + if self.user_transmission: + self.mixed_leech = False + else: + raise ValueError("Chat not found!") + else: + uploader_id = self.client.me.id + if chat.type.name in ["SUPERGROUP", "CHANNEL", "GROUP"]: + 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.mixed_leech = False + else: + try: + await self.client.send_chat_action( + self.up_dest, + ChatAction.TYPING, + ) + except: + raise ValueError("Start the bot and try again!") elif ( self.user_transmission or self.mixed_leech ) and not self.is_super_chat: @@ -373,10 +382,13 @@ async def before_start(self): self.split_size = ( self.split_size or self.user_dict.get("split_size") - or config_dict["LEECH_SPLIT_SIZE"] + or Config.LEECH_SPLIT_SIZE + ) + self.equal_splits = self.user_dict.get("equal_splits") or ( + Config.EQUAL_SPLITS and "equal_splits" not in self.user_dict ) self.max_split_size = ( - MAX_SPLIT_SIZE if self.user_transmission else 2097152000 + TgClient.MAX_SPLIT_SIZE if self.user_transmission else 2097152000 ) self.split_size = min(self.split_size, self.max_split_size) @@ -386,10 +398,7 @@ async def before_start(self): if self.as_med else ( self.user_dict.get("as_doc", False) - or ( - config_dict["AS_DOCUMENT"] - and "as_doc" not in self.user_dict - ) + or (Config.AS_DOCUMENT and "as_doc" not in self.user_dict) ) ) @@ -397,7 +406,7 @@ async def before_start(self): self.thumbnail_layout or self.user_dict.get("thumb_layout", False) or ( - config_dict["THUMBNAIL_LAYOUT"] + Config.THUMBNAIL_LAYOUT if "thumb_layout" not in self.user_dict else "" ) @@ -434,7 +443,7 @@ async def get_tag(self, text: list): async def run_multi(self, input_list, obj): await sleep(7) if not self.multi_tag and self.multi > 1: - self.multi_tag = token_hex(2) + self.multi_tag = token_urlsafe(3) multi_tags.add(self.multi_tag) elif self.multi <= 1: if self.multi_tag in multi_tags: @@ -455,7 +464,7 @@ async def run_multi(self, input_list, obj): msg.append(f"{self.bulk[0]} -i {self.multi - 1} {self.options}") msgts = " ".join(msg) if self.multi > 2: - msgts += f"\nCancel Multi:
/stop_{self.multi_tag}
"
+ msgts += f"\nCancel Multi: /{BotCommands.CancelTaskCommand[1]} {self.multi_tag}
"
nextmsg = await send_message(self.message, msgts)
else:
msg = [s.strip() for s in input_list]
@@ -467,7 +476,7 @@ async def run_multi(self, input_list, obj):
)
msgts = " ".join(msg)
if self.multi > 2:
- msgts += f"\nCancel Multi: /stop_{self.multi_tag}
"
+ msgts += f"\nCancel Multi: /{BotCommands.CancelTaskCommand[1]} {self.multi_tag}
"
nextmsg = await send_message(nextmsg, msgts)
nextmsg = await self.client.get_messages(
chat_id=self.message.chat.id,
@@ -505,9 +514,9 @@ async def init_bulk(self, input_list, bulk_start, bulk_end, obj):
b_msg.append(f"{self.bulk[0]} -i {len(self.bulk)} {self.options}")
msg = " ".join(b_msg)
if len(self.bulk) > 2:
- self.multi_tag = token_hex(2)
+ self.multi_tag = token_urlsafe(3)
multi_tags.add(self.multi_tag)
- msg += f"\nCancel Multi: /stop_{self.multi_tag}
"
+ msg += f"\nCancel Multi: /{BotCommands.CancelTaskCommand[1]} {self.multi_tag}
"
nextmsg = await send_message(self.message, msg)
nextmsg = await self.client.get_messages(
chat_id=self.message.chat.id,
@@ -527,7 +536,7 @@ async def init_bulk(self, input_list, bulk_start, bulk_end, obj):
self.multi_tag,
self.options,
).new_event()
- except Exception:
+ except:
await send_message(
self.message,
"Reply to text file or to telegram message that have links seperated by new line!",
@@ -536,9 +545,7 @@ async def init_bulk(self, input_list, bulk_start, bulk_end, obj):
async def decompress_zst(self, dl_path, is_dir=False):
if is_dir:
for dirpath, _, files in await sync_to_async(
- walk,
- dl_path,
- topdown=False,
+ walk, dl_path, topdown=False
):
for file_ in files:
if file_.endswith(".zst"):
@@ -559,7 +566,7 @@ async def decompress_zst(self, dl_path, is_dir=False):
if code != 0:
try:
stderr = stderr.decode().strip()
- except Exception:
+ except:
stderr = "Unable to decode the error!"
LOGGER.error(
f"{stderr}. Unable to extract zst file!. Path: {f_path}",
@@ -581,10 +588,10 @@ async def decompress_zst(self, dl_path, is_dir=False):
if code != 0:
try:
stderr = stderr.decode().strip()
- except Exception:
+ except:
stderr = "Unable to decode the error!"
LOGGER.error(
- f"{stderr}. Unable to extract zst file!. Path: {dl_path}",
+ f"{stderr}. Unable to extract zst file!. Path: {dl_path}"
)
elif not self.seed:
await remove(dl_path)
@@ -646,7 +653,7 @@ async def proceed_extract(self, dl_path, gid):
if code != 0:
try:
stderr = stderr.decode().strip()
- except Exception:
+ except:
stderr = "Unable to decode the error!"
LOGGER.error(
f"{stderr}. Unable to extract archive splits!. Path: {f_path}",
@@ -661,7 +668,7 @@ async def proceed_extract(self, dl_path, gid):
del_path = ospath.join(dirpath, file_)
try:
await remove(del_path)
- except Exception:
+ except:
self.is_cancelled = True
return up_path
dl_path = await self.decompress_zst(dl_path)
@@ -696,12 +703,12 @@ async def proceed_extract(self, dl_path, gid):
if not self.seed:
try:
await remove(dl_path)
- except Exception:
+ except:
self.is_cancelled = True
return up_path
try:
stderr = stderr.decode().strip()
- except Exception:
+ except:
stderr = "Unable to decode the error!"
LOGGER.error(
f"{stderr}. Unable to extract archive! Uploading anyway. Path: {dl_path}",
@@ -719,15 +726,19 @@ async def proceed_compress(self, dl_path, gid, o_files, ft_delete):
pswd = self.compress if isinstance(self.compress, str) else ""
if self.seed and not self.new_dir:
self.new_dir = f"{self.dir}10000"
- up_path = f"{self.new_dir}/{self.name}.7z"
+ up_path = f"{self.new_dir}/{self.name}.zip"
delete = False
else:
- up_path = f"{dl_path}.7z"
+ up_path = f"{dl_path}.zip"
delete = True
async with task_dict_lock:
task_dict[self.mid] = SevenZStatus(self, gid, "Zip")
size = await get_path_size(dl_path)
- split_size = self.split_size
+ if self.equal_splits:
+ parts = -(-size // self.split_size)
+ split_size = (size // parts) + (size % parts)
+ else:
+ split_size = self.split_size
cmd = [
"7z",
f"-v{split_size}b",
@@ -780,7 +791,7 @@ async def proceed_compress(self, dl_path, gid, o_files, ft_delete):
self.new_dir = ""
try:
stderr = stderr.decode().strip()
- except Exception:
+ except:
stderr = "Unable to decode the error!"
LOGGER.error(f"{stderr}. Unable to zip this path: {dl_path}")
return dl_path
@@ -817,13 +828,13 @@ async def proceed_split(self, up_dir, m_size, o_files, gid):
else:
try:
await remove(f_path)
- except Exception:
+ except:
return
continue
if not self.seed or self.new_dir:
try:
await remove(f_path)
- except Exception:
+ except:
return
else:
m_size.append(f_size)
@@ -879,9 +890,7 @@ async def generate_sample_video(self, dl_path, gid, unwanted_files, ft_delete):
return new_folder
else:
for dirpath, _, files in await sync_to_async(
- walk,
- dl_path,
- topdown=False,
+ walk, dl_path, topdown=False
):
for file_ in files:
f_path = ospath.join(dirpath, file_)
@@ -1010,9 +1019,7 @@ async def proceed_convert(m_path):
return output_file
else:
for dirpath, _, files in await sync_to_async(
- walk,
- dl_path,
- topdown=False,
+ walk, dl_path, topdown=False
):
for file_ in files:
if self.is_cancelled:
@@ -1061,9 +1068,7 @@ async def generate_screenshots(self, dl_path):
else:
LOGGER.info(f"Creating Screenshot for: {dl_path}")
for dirpath, _, files in await sync_to_async(
- walk,
- dl_path,
- topdown=False,
+ walk, dl_path, topdown=False
):
for file_ in files:
f_path = ospath.join(dirpath, file_)
@@ -1089,10 +1094,7 @@ async def substitute(self, dl_path):
res = ""
try:
name = sub(
- rf"{pattern}",
- res,
- name,
- flags=IGNORECASE if sen else 0,
+ rf"{pattern}", res, name, flags=IGNORECASE if sen else 0
)
except Exception as e:
LOGGER.error(
@@ -1141,8 +1143,12 @@ async def substitute(self, dl_path):
async def proceed_ffmpeg(self, dl_path, gid):
checked = False
- for ffmpeg_cmd in self.ffmpeg_cmds:
- cmd = ["ffmpeg", "-hide_banner", "-loglevel", "error", *ffmpeg_cmd]
+ cmds = [
+ [part.strip() for part in item.split() if part.strip()]
+ for item in self.ffmpeg_cmds
+ ]
+ for ffmpeg_cmd in cmds:
+ cmd = ["xtra", "-hide_banner", "-loglevel", "error", *ffmpeg_cmd]
if "-del" in cmd:
cmd.remove("-del")
delete_files = True
@@ -1207,9 +1213,7 @@ async def proceed_ffmpeg(self, dl_path, gid):
checked = True
async with task_dict_lock:
task_dict[self.mid] = FFmpegStatus(
- self,
- gid,
- "FFmpeg",
+ self, gid, "FFmpeg"
)
await cpu_eater_lock.acquire()
LOGGER.info(f"Running ffmpeg cmd for: {f_path}")
diff --git a/bot/helper/ext_utils/bot_utils.py b/bot/helper/ext_utils/bot_utils.py
index 729b0a557..a3401b0b3 100644
--- a/bot/helper/ext_utils/bot_utils.py
+++ b/bot/helper/ext_utils/bot_utils.py
@@ -10,7 +10,8 @@
from httpx import AsyncClient
-from bot import bot_loop, config_dict, user_data
+from bot import bot_loop, user_data
+from bot.core.config_manager import Config
from bot.helper.telegram_helper.button_build import ButtonMaker
from .help_messages import (
@@ -59,9 +60,14 @@ def bt_selection_buttons(id_):
gid = id_[:12] if len(id_) > 25 else id_
pin = "".join([n for n in id_ if n.isdigit()][:4])
buttons = ButtonMaker()
- BASE_URL = config_dict["BASE_URL"]
- buttons.url_button("Select Files", f"{BASE_URL}/app/files?gid={id_}")
- buttons.data_button("Pincode", f"sel pin {gid} {pin}")
+ if Config.WEB_PINCODE:
+ buttons.url_button("Select Files", f"{Config.BASE_URL}/app/files?gid={id_}")
+ buttons.data_button("Pincode", f"sel pin {gid} {pin}")
+ else:
+ buttons.url_button(
+ "Select Files",
+ f"{Config.BASE_URL}/app/files?gid={id_}&pin={pin}",
+ )
buttons.data_button("Done Selecting", f"sel done {gid} {id_}")
buttons.data_button("Cancel", f"sel cancel {gid}")
return buttons.build_menu(2)
diff --git a/bot/helper/ext_utils/db_handler.py b/bot/helper/ext_utils/db_handler.py
index c67f2c017..2f884790b 100644
--- a/bot/helper/ext_utils/db_handler.py
+++ b/bot/helper/ext_utils/db_handler.py
@@ -1,123 +1,64 @@
+from importlib import import_module
+
from aiofiles import open as aiopen
-from aiofiles.os import makedirs
from aiofiles.os import path as aiopath
-from dotenv import dotenv_values
-
-# from motor.motor_asyncio import AsyncIOMotorClient
-from pymongo import AsyncMongoClient
+from motor.motor_asyncio import AsyncIOMotorClient
from pymongo.errors import PyMongoError
from pymongo.server_api import ServerApi
-from bot import (
- BOT_ID,
- LOGGER,
- aria2_options,
- config_dict,
- qbit_options,
- user_data,
-)
+from bot import LOGGER, qbit_options, rss_dict, user_data
+from bot.core.config_manager import Config
+from bot.core.aeon_client import TgClient
class DbManager:
def __init__(self):
- self._return = False
- self._db = None
+ self._return = True
self._conn = None
+ self.db = None
async def connect(self):
try:
- if config_dict["DATABASE_URL"]:
- if self._conn is not None:
- await self._conn.close()
- self._conn = AsyncMongoClient(
- config_dict["DATABASE_URL"],
- server_api=ServerApi("1"),
- )
- self._db = self._conn.luna
- self._return = False
- else:
- self._return = True
+ if self._conn is not None:
+ await self._conn.close()
+ self._conn = AsyncIOMotorClient(
+ Config.DATABASE_URL,
+ server_api=ServerApi("1"),
+ )
+ self.db = self._conn.luna
+ self._return = False
except PyMongoError as e:
LOGGER.error(f"Error in DB connection: {e}")
+ self.db = None
self._return = True
+ self._conn = None
async def disconnect(self):
+ self._return = True
if self._conn is not None:
await self._conn.close()
self._conn = None
- self._return = True
-
- async def db_load(self):
- if self._db is None:
- await self.connect()
- if self._return:
- return
- # Save bot settings
- try:
- await self._db.settings.config.replace_one(
- {"_id": BOT_ID},
- config_dict,
- upsert=True,
- )
- except Exception as e:
- LOGGER.error(f"DataBase Collection Error: {e}")
- return
- # Save Aria2c options
- if await self._db.settings.aria2c.find_one({"_id": BOT_ID}) is None:
- await self._db.settings.aria2c.update_one(
- {"_id": BOT_ID},
- {"$set": aria2_options},
- upsert=True,
- )
- # Save qbittorrent options
- if await self._db.settings.qbittorrent.find_one({"_id": BOT_ID}) is None:
- await self.save_qbit_settings()
- # User Data
- if await self._db.users.find_one():
- rows = self._db.users.find({})
- # return a dict ==> {_id, is_sudo, is_auth, as_doc, thumb, yt_opt, split_size, rclone, rclone_path, token_pickle, gdrive_id, leech_dest, lperfix, lprefix, excluded_extensions, user_transmission, index_url, default_upload}
- async for row in rows:
- uid = row["_id"]
- del row["_id"]
- thumb_path = f"Thumbnails/{uid}.jpg"
- rclone_config_path = f"rclone/{uid}.conf"
- token_path = f"tokens/{uid}.pickle"
- if row.get("thumb"):
- if not await aiopath.exists("Thumbnails"):
- await makedirs("Thumbnails")
- async with aiopen(thumb_path, "wb+") as f:
- await f.write(row["thumb"])
- row["thumb"] = thumb_path
- if row.get("rclone_config"):
- if not await aiopath.exists("rclone"):
- await makedirs("rclone")
- async with aiopen(rclone_config_path, "wb+") as f:
- await f.write(row["rclone_config"])
- row["rclone_config"] = rclone_config_path
- if row.get("token_pickle"):
- if not await aiopath.exists("tokens"):
- await makedirs("tokens")
- async with aiopen(token_path, "wb+") as f:
- await f.write(row["token_pickle"])
- row["token_pickle"] = token_path
- user_data[uid] = row
- LOGGER.info("Users data has been imported from Database")
async def update_deploy_config(self):
if self._return:
return
- current_config = dict(dotenv_values("config.env"))
- await self._db.settings.deployConfig.replace_one(
- {"_id": BOT_ID},
- current_config,
+ settings = import_module("config")
+ config_file = {
+ key: value.strip() if isinstance(value, str) else value
+ for key, value in vars(settings).items()
+ if not key.startswith("__")
+ }
+ await self.db.settings.deployConfig.replace_one(
+ {"_id": TgClient.ID},
+ config_file,
upsert=True,
)
async def update_config(self, dict_):
if self._return:
return
- await self._db.settings.config.update_one(
- {"_id": BOT_ID},
+ await self.db.settings.config.update_one(
+ {"_id": TgClient.ID},
{"$set": dict_},
upsert=True,
)
@@ -125,8 +66,8 @@ async def update_config(self, dict_):
async def update_aria2(self, key, value):
if self._return:
return
- await self._db.settings.aria2c.update_one(
- {"_id": BOT_ID},
+ await self.db.settings.aria2c.update_one(
+ {"_id": TgClient.ID},
{"$set": {key: value}},
upsert=True,
)
@@ -134,8 +75,8 @@ async def update_aria2(self, key, value):
async def update_qbittorrent(self, key, value):
if self._return:
return
- await self._db.settings.qbittorrent.update_one(
- {"_id": BOT_ID},
+ await self.db.settings.qbittorrent.update_one(
+ {"_id": TgClient.ID},
{"$set": {key: value}},
upsert=True,
)
@@ -143,8 +84,8 @@ async def update_qbittorrent(self, key, value):
async def save_qbit_settings(self):
if self._return:
return
- await self._db.settings.qbittorrent.replace_one(
- {"_id": BOT_ID},
+ await self.db.settings.qbittorrent.replace_one(
+ {"_id": TgClient.ID},
qbit_options,
upsert=True,
)
@@ -158,12 +99,12 @@ async def update_private_file(self, path):
else:
pf_bin = ""
path = path.replace(".", "__")
- await self._db.settings.files.update_one(
- {"_id": BOT_ID},
+ await self.db.settings.files.update_one(
+ {"_id": TgClient.ID},
{"$set": {path: pf_bin}},
upsert=True,
)
- if path == "config.env":
+ if path == "config.py":
await self.update_deploy_config()
async def update_user_data(self, user_id):
@@ -176,11 +117,7 @@ async def update_user_data(self, user_id):
del data["rclone_config"]
if data.get("token_pickle"):
del data["token_pickle"]
- if data.get("token"):
- del data["token"]
- if data.get("time"):
- del data["time"]
- await self._db.users.replace_one({"_id": user_id}, data, upsert=True)
+ await self.db.users.replace_one({"_id": user_id}, data, upsert=True)
async def update_user_doc(self, user_id, key, path=""):
if self._return:
@@ -190,77 +127,69 @@ async def update_user_doc(self, user_id, key, path=""):
doc_bin = await doc.read()
else:
doc_bin = ""
- await self._db.users.update_one(
+ await self.db.users.update_one(
{"_id": user_id},
{"$set": {key: doc_bin}},
upsert=True,
)
- async def trunc_table(self, name):
- if self._return:
- return
- await self._db[name][BOT_ID].drop()
-
- async def get_pm_uids(self):
- if self._return:
- return None
- return [doc["_id"] async for doc in self._db.pm_users[BOT_ID].find({})]
-
- async def update_pm_users(self, user_id):
+ async def rss_update_all(self):
if self._return:
return
- if not bool(await self._db.pm_users[BOT_ID].find_one({"_id": user_id})):
- await self._db.pm_users[BOT_ID].insert_one({"_id": user_id})
- LOGGER.info(f"New PM User Added : {user_id}")
-
- async def rm_pm_user(self, user_id):
- if self._return:
- return
- await self._db.pm_users[BOT_ID].delete_one({"_id": user_id})
+ for user_id in list(rss_dict.keys()):
+ await self.db.rss[TgClient.ID].replace_one(
+ {"_id": user_id},
+ rss_dict[user_id],
+ upsert=True,
+ )
- async def update_user_tdata(self, user_id, token, time):
+ async def rss_update(self, user_id):
if self._return:
return
- await self._db.access_token.update_one(
+ await self.db.rss[TgClient.ID].replace_one(
{"_id": user_id},
- {"$set": {"token": token, "time": time}},
+ rss_dict[user_id],
upsert=True,
)
- async def update_user_token(self, user_id, token):
+ async def rss_delete(self, user_id):
if self._return:
return
- await self._db.access_token.update_one(
- {"_id": user_id},
- {"$set": {"token": token}},
- upsert=True,
- )
+ await self.db.rss[TgClient.ID].delete_one({"_id": user_id})
- async def get_token_expiry(self, user_id):
+ async def add_incomplete_task(self, cid, link, tag):
if self._return:
- return None
- user_data = await self._db.access_token.find_one({"_id": user_id})
- if user_data:
- return user_data.get("time")
- return None
+ return
+ await self.db.tasks[TgClient.ID].insert_one(
+ {"_id": link, "cid": cid, "tag": tag},
+ )
- async def delete_user_token(self, user_id):
+ async def rm_complete_task(self, link):
if self._return:
return
- await self._db.access_token.delete_one({"_id": user_id})
+ await self.db.tasks[TgClient.ID].delete_one({"_id": link})
- async def get_user_token(self, user_id):
+ async def get_incomplete_tasks(self):
+ notifier_dict = {}
if self._return:
- return None
- user_data = await self._db.access_token.find_one({"_id": user_id})
- if user_data:
- return user_data.get("token")
- return None
+ return notifier_dict
+ if await self.db.tasks[TgClient.ID].find_one():
+ rows = self.db.tasks[TgClient.ID].find({})
+ async for row in rows:
+ if row["cid"] in list(notifier_dict.keys()):
+ if row["tag"] in list(notifier_dict[row["cid"]]):
+ notifier_dict[row["cid"]][row["tag"]].append(row["_id"])
+ else:
+ notifier_dict[row["cid"]][row["tag"]] = [row["_id"]]
+ else:
+ notifier_dict[row["cid"]] = {row["tag"]: [row["_id"]]}
+ await self.db.tasks[TgClient.ID].drop()
+ return notifier_dict
- async def delete_all_access_tokens(self):
+ async def trunc_table(self, name):
if self._return:
return
- await self._db.access_token.delete_many({})
+ await self.db[name][TgClient.ID].drop()
-Database = DbManager()
+database = DbManager()
diff --git a/bot/helper/ext_utils/exceptions.py b/bot/helper/ext_utils/exceptions.py
index 384be80aa..b46ba321f 100644
--- a/bot/helper/ext_utils/exceptions.py
+++ b/bot/helper/ext_utils/exceptions.py
@@ -7,5 +7,9 @@ class NotSupportedExtractionArchive(Exception):
"""The archive format use is trying to extract is not supported"""
+class RssShutdownException(Exception):
+ """This exception should be raised when shutdown is called to stop the montior"""
+
+
class TgLinkException(Exception):
"""No Access granted for this chat"""
diff --git a/bot/helper/ext_utils/files_utils.py b/bot/helper/ext_utils/files_utils.py
index 7e0265114..fc3146b06 100644
--- a/bot/helper/ext_utils/files_utils.py
+++ b/bot/helper/ext_utils/files_utils.py
@@ -12,7 +12,8 @@
from aioshutil import rmtree as aiormtree
from magic import Magic
-from bot import DOWNLOAD_DIR, LOGGER, aria2, xnox_client
+from bot import LOGGER, aria2, xnox_client
+from bot.core.config_manager import Config
from .bot_utils import cmd_exec, sync_to_async
from .exceptions import NotSupportedExtractionArchive
@@ -102,10 +103,10 @@ def clean_all():
xnox_client.torrents_delete(torrent_hashes="all")
try:
LOGGER.info("Cleaning Download Directory")
- rmtree(DOWNLOAD_DIR, ignore_errors=True)
- except Exception:
+ rmtree(Config.DOWNLOAD_DIR, ignore_errors=True)
+ except:
pass
- makedirs(DOWNLOAD_DIR, exist_ok=True)
+ makedirs(Config.DOWNLOAD_DIR, exist_ok=True)
def exit_clean_up(_, __):
@@ -173,8 +174,7 @@ async def count_files_and_folders(path, extension_filter, unwanted_files=None):
def get_base_name(orig_path):
extension = next(
- (ext for ext in ARCH_EXT if orig_path.lower().endswith(ext)),
- "",
+ (ext for ext in ARCH_EXT if orig_path.lower().endswith(ext)), ""
)
if extension != "":
return re_split(f"{extension}$", orig_path, maxsplit=1, flags=IGNORECASE)[0]
diff --git a/bot/helper/ext_utils/help_messages.py b/bot/helper/ext_utils/help_messages.py
index cc5fc27b3..76ad85874 100644
--- a/bot/helper/ext_utils/help_messages.py
+++ b/bot/helper/ext_utils/help_messages.py
@@ -1,32 +1,4 @@
-nsfw_keywords = [
- "porn",
- "onlyfans",
- "nsfw",
- "Brazzers",
- "adult",
- "xnxx",
- "xvideos",
- "nsfwcherry",
- "hardcore",
- "Pornhub",
- "xvideos2",
- "youporn",
- "pornrip",
- "playboy",
- "hentai",
- "erotica",
- "blowjob",
- "redtube",
- "stripchat",
- "camgirl",
- "nude",
- "fetish",
- "cuckold",
- "orgy",
- "horny",
- "swingers",
- "ullu",
-]
+from bot.helper.telegram_helper.bot_commands import BotCommands
mirror = """Send link along with command line or
@@ -269,13 +241,12 @@
Notes:
1. Add -del
to the list(s) which you want from the bot to delete the original files after command run complete!
2. Seed will get disbaled while using this option
-3. It must be list of list(s) event of one list added like [["-i", "file.mkv", "-c", "copy", "-c:s", "srt", "file.mkv", "-del"]]
-Examples: [["-i", "file.mkv", "-c", "copy", "-c:s", "srt", "file.mkv", "-del"], ["-i", "file.video", "-c", "copy", "-c:s", "srt", "file"], ["-i", "file.m4a", "-c:a", "libmp3lame", "-q:a", "2", "file.mp3"], ["-i", "file.audio", "-c:a", "libmp3lame", "-q:a", "2", "file.mp3"]]
-Here I will explain how to use file.* which is reference to files you want to work on.
-1. First cmd: the input is file.mkv so this cmd will work only on mkv videos and the output is file.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 file.video so this cmd will work on all videos and the output is only file so the extenstion is same as input files.
-3. Third cmd: the input in file.m4a so this cmd will work only on m4a audios and the output is file.mp3 so the output extension is mp3.
-4. Fourth cmd: the input is file.audio so this cmd will work on all audios and the output is file.mp3 so the output extension is mp3."""
+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"]
+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.
+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."""
YT_HELP_DICT = {
"main": yt,
@@ -339,6 +310,29 @@
"Rclone": rclone_cl,
}
+RSS_HELP_MESSAGE = """
+Use this format to add feed url:
+Title1 link (required)
+Title2 link -c cmd -inf xx -exf xx
+Title3 link -c cmd -d ratio:time -z password
+
+-c command -up mrcc:remote:path/subdir -rcf --buffer-size:8M|key|key:value
+-inf For included words filter.
+-exf For excluded words filter.
+-stv true or false (sensitive filter)
+
+Example: Title https://www.rss-url.com -inf 1080 or 720 or 144p|mkv or mp4|hevc -exf flv or web|xxx
+This filter will parse links that its titles contain `(1080 or 720 or 144p) and (mkv or mp4) and hevc` and doesn't contain (flv or web) and xxx words. You can add whatever you want.
+
+Another example: -inf 1080 or 720p|.web. or .webrip.|hvec or x264. This will parse titles that contain ( 1080 or 720p) and (.web. or .webrip.) and (hvec or x264). I have added space before and after 1080 to avoid wrong matching. If this `10805695` number in title it will match 1080 if added 1080 without spaces after it.
+
+Filter Notes:
+1. | means and.
+2. Add `or` between similar keys, you can add it between qualities or between extensions, so don't add filter like this f: 1080|mp4 or 720|web because this will parse 1080 and (mp4 or 720) and web ... not (1080 and mp4) or (720 and web).
+3. You can add `or` and `|` as much as you want.
+4. Take a look at the title if it has a static special character after or before the qualities or extensions or whatever and use them in the filter to avoid wrong match.
+Timeout: 60 sec.
+"""
PASSWORD_ERROR_MESSAGE = """
This link requires a password!
@@ -346,3 +340,40 @@
Example: link::my password
"""
+
+
+help_string = f"""
+NOTE: Try each command without any argument to see more detalis.
+/{BotCommands.MirrorCommand[0]} or /{BotCommands.MirrorCommand[1]}: Start mirroring to cloud.
+/{BotCommands.QbMirrorCommand[0]} or /{BotCommands.QbMirrorCommand[1]}: Start Mirroring to cloud using qBittorrent.
+/{BotCommands.YtdlCommand[0]} or /{BotCommands.YtdlCommand[1]}: Mirror yt-dlp supported link.
+/{BotCommands.LeechCommand[0]} or /{BotCommands.LeechCommand[1]}: Start leeching to Telegram.
+/{BotCommands.QbLeechCommand[0]} or /{BotCommands.QbLeechCommand[1]}: Start leeching using qBittorrent.
+/{BotCommands.YtdlLeechCommand[0]} or /{BotCommands.YtdlLeechCommand[1]}: Leech yt-dlp supported link.
+/{BotCommands.CloneCommand} [drive_url]: Copy file/folder to Google Drive.
+/{BotCommands.CountCommand} [drive_url]: Count file/folder of Google Drive.
+/{BotCommands.DeleteCommand} [drive_url]: Delete file/folder from Google Drive (Only Owner & Sudo).
+/{BotCommands.UserSetCommand[0]} or /{BotCommands.UserSetCommand[1]} [query]: Users settings.
+/{BotCommands.BotSetCommand[0]} or /{BotCommands.BotSetCommand[1]} [query]: Bot settings.
+/{BotCommands.SelectCommand}: Select files from torrents or nzb by gid or reply.
+/{BotCommands.CancelTaskCommand[0]} or /{BotCommands.CancelTaskCommand[1]} [gid]: Cancel task by gid or reply.
+/{BotCommands.ForceStartCommand[0]} or /{BotCommands.ForceStartCommand[1]} [gid]: Force start task by gid or reply.
+/{BotCommands.CancelAllCommand} [query]: Cancel all [status] tasks.
+/{BotCommands.ListCommand} [query]: Search in Google Drive(s).
+/{BotCommands.SearchCommand} [query]: Search for torrents with API.
+/{BotCommands.StatusCommand}: Shows a status of all the downloads.
+/{BotCommands.StatsCommand}: Show stats of the machine where the bot is hosted in.
+/{BotCommands.PingCommand}: Check how long it takes to Ping the Bot (Only Owner & Sudo).
+/{BotCommands.AuthorizeCommand}: Authorize a chat or a user to use the bot (Only Owner & Sudo).
+/{BotCommands.UnAuthorizeCommand}: Unauthorize a chat or a user to use the bot (Only Owner & Sudo).
+/{BotCommands.UsersCommand}: show users settings (Only Owner & Sudo).
+/{BotCommands.AddSudoCommand}: Add sudo user (Only Owner).
+/{BotCommands.RmSudoCommand}: Remove sudo users (Only Owner).
+/{BotCommands.RestartCommand}: Restart and update the bot (Only Owner & Sudo).
+/{BotCommands.LogCommand}: Get a log file of the bot. Handy for getting crash reports (Only Owner & Sudo).
+/{BotCommands.ShellCommand}: Run shell commands (Only Owner).
+/{BotCommands.AExecCommand}: Exec async functions (Only Owner).
+/{BotCommands.ExecCommand}: Exec sync functions (Only Owner).
+/{BotCommands.ClearLocalsCommand}: Clear {BotCommands.AExecCommand} or {BotCommands.ExecCommand} locals (Only Owner).
+/{BotCommands.RssCommand}: RSS Menu.
+"""
diff --git a/bot/helper/ext_utils/media_utils.py b/bot/helper/ext_utils/media_utils.py
index bcf29a162..ab36ffb13 100644
--- a/bot/helper/ext_utils/media_utils.py
+++ b/bot/helper/ext_utils/media_utils.py
@@ -12,7 +12,8 @@
from aioshutil import rmtree
from PIL import Image
-from bot import DOWNLOAD_DIR, LOGGER, subprocess_lock
+from bot import LOGGER, subprocess_lock
+from bot.core.config_manager import Config
from .bot_utils import cmd_exec, sync_to_async
from .files_utils import ARCH_EXT, get_mime_type
@@ -126,7 +127,7 @@ async def convert_audio(listener, audio_file, ext):
async def create_thumb(msg, _id=""):
if not _id:
_id = msg.id
- path = f"{DOWNLOAD_DIR}Thumbnails"
+ path = f"{Config.DOWNLOAD_DIR}Thumbnails"
else:
path = "Thumbnails"
await makedirs(path, exist_ok=True)
@@ -153,7 +154,7 @@ async def is_multi_streams(path):
)
except Exception as e:
LOGGER.error(
- f"Get Video Streams: {e}. Mostly File not found! - File: {path}",
+ f"Get Video Streams: {e}. Mostly File not found! - File: {path}"
)
return False
if result[0] and result[2] == 0:
@@ -229,12 +230,12 @@ async def get_document_type(path):
is_video = True
except Exception as e:
LOGGER.error(
- f"Get Document Type: {e}. Mostly File not found! - File: {path}",
+ f"Get Document Type: {e}. Mostly File not found! - File: {path}"
)
if mime_type.startswith("audio"):
return False, True, False
if not mime_type.startswith("video") and not mime_type.endswith(
- "octet-stream",
+ "octet-stream"
):
return is_video, is_audio, is_image
if mime_type.startswith("video"):
@@ -259,7 +260,7 @@ async def take_ss(video_file, ss_nb) -> bool:
if duration != 0:
dirpath, name = video_file.rsplit("/", 1)
name, _ = ospath.splitext(name)
- dirpath = f"{dirpath}/{name}_sshots"
+ dirpath = f"{dirpath}/{name}_ss"
await makedirs(dirpath, exist_ok=True)
interval = duration // (ss_nb + 1)
cap_time = interval
@@ -305,7 +306,7 @@ async def take_ss(video_file, ss_nb) -> bool:
async def get_audio_thumbnail(audio_file):
- output_dir = f"{DOWNLOAD_DIR}Thumbnails"
+ output_dir = f"{Config.DOWNLOAD_DIR}Thumbnails"
await makedirs(output_dir, exist_ok=True)
output = ospath.join(output_dir, f"{time()}.jpg")
cmd = [
@@ -332,7 +333,7 @@ async def get_audio_thumbnail(audio_file):
async def get_video_thumbnail(video_file, duration):
- output_dir = f"{DOWNLOAD_DIR}Thumbnails"
+ output_dir = f"{Config.DOWNLOAD_DIR}Thumbnails"
await makedirs(output_dir, exist_ok=True)
output = ospath.join(output_dir, f"{time()}.jpg")
if duration is None:
@@ -380,7 +381,7 @@ async def get_multiple_frames_thumbnail(video_file, layout, keep_screenshots):
dirpath = await take_ss(video_file, ss_nb)
if not dirpath:
return None
- output_dir = f"{DOWNLOAD_DIR}Thumbnails"
+ output_dir = f"{Config.DOWNLOAD_DIR}Thumbnails"
await makedirs(output_dir, exist_ok=True)
output = ospath.join(output_dir, f"{time()}.jpg")
cmd = [
@@ -431,21 +432,21 @@ async def split_file(
listener,
start_time=0,
i=1,
+ inLoop=False,
multi_streams=True,
):
if listener.seed and not listener.new_dir:
dirpath = f"{dirpath}/splited_files"
await makedirs(dirpath, exist_ok=True)
-
parts = -(-size // listener.split_size)
-
+ if listener.equal_splits and not inLoop:
+ split_size = (size // parts) + (size % parts)
if not listener.as_doc and (await get_document_type(path))[0]:
if multi_streams:
multi_streams = await is_multi_streams(path)
duration = (await get_media_info(path))[0]
base_name, extension = ospath.splitext(file_)
split_size -= 5000000
-
while i <= parts or start_time < duration - 4:
out_path = f"{dirpath}/{base_name}.part{i:03}{extension}"
cmd = [
@@ -476,13 +477,11 @@ async def split_file(
del cmd[10]
if listener.is_cancelled:
return False
-
async with subprocess_lock:
listener.subproc = await create_subprocess_exec(*cmd, stderr=PIPE)
_, stderr = await listener.subproc.communicate()
if listener.is_cancelled:
return False
-
code = listener.subproc.returncode
if code == -9:
listener.is_cancelled = True
@@ -492,10 +491,8 @@ async def split_file(
stderr = stderr.decode().strip()
except Exception:
stderr = "Unable to decode the error!"
-
with contextlib.suppress(Exception):
await remove(out_path)
-
if multi_streams:
LOGGER.warning(
f"{stderr}. Retrying without map, -map 0 not working in all situations. Path: {path}",
@@ -509,13 +506,13 @@ async def split_file(
listener,
start_time,
i,
+ True,
False,
)
LOGGER.warning(
f"{stderr}. Unable to split this video, if it's size less than {listener.max_split_size} will be uploaded as it is. Path: {path}",
)
return False
-
out_size = await aiopath.getsize(out_path)
if out_size > listener.max_split_size:
dif = out_size - listener.max_split_size
@@ -530,9 +527,9 @@ async def split_file(
listener,
start_time,
i,
+ True,
multi_streams,
)
-
lpd = (await get_media_info(out_path))[0]
if lpd == 0:
LOGGER.error(
@@ -541,13 +538,12 @@ async def split_file(
break
if duration == lpd:
LOGGER.warning(
- f"This file has been split with default stream and audio, so you will only see one part with less size from the original one because it doesn't have all streams and audios. This happens mostly with MKV videos. Path: {path}",
+ 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: {path}",
)
break
if lpd <= 3:
await remove(out_path)
break
-
start_time += lpd - 3
i += 1
else:
@@ -555,7 +551,6 @@ async def split_file(
async with subprocess_lock:
if listener.is_cancelled:
return False
-
listener.subproc = await create_subprocess_exec(
"split",
"--numeric-suffixes=1",
@@ -566,10 +561,8 @@ async def split_file(
stderr=PIPE,
)
_, stderr = await listener.subproc.communicate()
-
if listener.is_cancelled:
return False
-
code = listener.subproc.returncode
if code == -9:
listener.is_cancelled = True
@@ -579,7 +572,6 @@ async def split_file(
stderr = stderr.decode().strip()
except Exception:
stderr = "Unable to decode the error!"
-
LOGGER.error(f"{stderr}. Split Document: {path}")
return True
@@ -658,12 +650,106 @@ async def create_sample_video(listener, video_file, sample_duration, part_durati
await remove(output_file)
return False
+ """finished_segments = []
+ await makedirs(f"{dir}/segments/", exist_ok=True)
+ ext = name.rsplit(".", 1)[-1]
+ for index, (start_time, end_time) in enumerate(segments, start=1):
+ output_seg = f"{dir}/segments/segment{index}.{ext}"
+ cmd = [
+ "xtra",
+ "-hide_banner",
+ "-loglevel",
+ "error",
+ "-i",
+ video_file,
+ "-ss",
+ f"{start_time}",
+ "-to",
+ f"{end_time}",
+ "-c",
+ "copy",
+ output_seg,
+ ]
+ if listener.is_cancelled:
+ return False
+ listener.subproc = await create_subprocess_exec(*cmd, stderr=PIPE)
+ _, stderr = await listener.subproc.communicate()
+ if listener.is_cancelled:
+ return False
+ code = listener.subproc.returncode
+ if code == -9:
+ listener.is_cancelled = True
+ return False
+ elif code != 0:
+ try:
+ stderr = stderr.decode().strip()
+ except:
+ stderr = "Unable to decode the error!"
+ LOGGER.error(
+ f"{stderr}. Something went wrong while splitting file for sample video, mostly file is corrupted. Path: {video_file}"
+ )
+ if await aiopath.exists(output_file):
+ await remove(output_file)
+ return False
+ else:
+ finished_segments.append(f"file '{output_seg}'")
+
+ segments_file = f"{dir}/segments.txt"
+
+ async with aiopen(segments_file, "w+") as f:
+ await f.write("\n".join(finished_segments))
+
+ cmd = [
+ "xtra",
+ "-hide_banner",
+ "-loglevel",
+ "error",
+ "-f",
+ "concat",
+ "-safe",
+ "0",
+ "-i",
+ segments_file,
+ "-c:v",
+ "libx264",
+ "-c:a",
+ "aac",
+ "-threads",
+ f"{cpu_count() // 2}",
+ output_file,
+ ]
+ if listener.is_cancelled:
+ return False
+ listener.subproc = await create_subprocess_exec(*cmd, stderr=PIPE)
+ _, stderr = await listener.subproc.communicate()
+ if listener.is_cancelled:
+ return False
+ code = listener.subproc.returncode
+ if code == -9:
+ listener.is_cancelled = True
+ return False
+ elif code != 0:
+ try:
+ stderr = stderr.decode().strip()
+ except:
+ stderr = "Unable to decode the error!"
+ LOGGER.error(
+ f"{stderr}. Something went wrong while creating sample video, mostly file is corrupted. Path: {video_file}"
+ )
+ if await aiopath.exists(output_file):
+ await remove(output_file)
+ await gather(remove(segments_file), rmtree(f"{dir}/segments"))
+ return False
+ await gather(remove(segments_file), rmtree(f"{dir}/segments"))
+ return output_file"""
+ return None
+
async def run_ffmpeg_cmd(listener, ffmpeg, path):
base_name, ext = ospath.splitext(path)
dir, base_name = base_name.rsplit("/", 1)
output_file = ffmpeg[-1]
- if output_file != "file" and output_file.startswith("mltb"):
+ if output_file != "mltb" and output_file.startswith("mltb"):
oext = ospath.splitext(output_file)[-1]
if ext == oext:
base_name = f"ffmpeg.{base_name}"
diff --git a/bot/helper/ext_utils/status_utils.py b/bot/helper/ext_utils/status_utils.py
index ee4169081..7398e7ead 100644
--- a/bot/helper/ext_utils/status_utils.py
+++ b/bot/helper/ext_utils/status_utils.py
@@ -3,15 +3,10 @@
from html import escape
from time import time
-from psutil import disk_usage
-
-from bot import (
- DOWNLOAD_DIR,
- bot_start_time,
- status_dict,
- task_dict,
- task_dict_lock,
-)
+from psutil import cpu_percent, disk_usage, virtual_memory
+
+from bot import bot_start_time, status_dict, task_dict, task_dict_lock
+from bot.core.config_manager import Config
from bot.helper.telegram_helper.button_build import ButtonMaker
from .bot_utils import sync_to_async
@@ -179,14 +174,13 @@ def get_progress_bar_string(pct):
return p_str
-"""
async def get_readable_message(sid, is_user, page_no=1, status="All", page_step=1):
msg = ""
button = None
tasks = await sync_to_async(get_specific_tasks, status, sid if is_user else None)
- STATUS_LIMIT = 4
+ STATUS_LIMIT = Config.STATUS_LIMIT
tasks_no = len(tasks)
pages = (max(tasks_no, 1) + STATUS_LIMIT - 1) // STATUS_LIMIT
if page_no > pages:
@@ -210,10 +204,10 @@ async def get_readable_message(sid, is_user, page_no=1, status="All", page_step=
if tstatus not in [
MirrorStatus.STATUS_SPLIT,
MirrorStatus.STATUS_SEED,
+ MirrorStatus.STATUS_SAMVID,
MirrorStatus.STATUS_CONVERT,
MirrorStatus.STATUS_FFMPEG,
MirrorStatus.STATUS_QUEUEUP,
- MirrorStatus.STATUS_SAMVID,
]:
progress = (
await task.progress()
@@ -234,7 +228,7 @@ async def get_readable_message(sid, is_user, page_no=1, status="All", page_step=
msg += f" | Time: {task.seeding_time()}"
else:
msg += f"\nSize: {task.size()}"
- msg += f"\nGid: /stop_{task.gid()}
\n\n"
+ msg += f"\nGid: {task.gid()}
\n\n"
if len(msg) == 0:
if status == "All":
@@ -256,84 +250,6 @@ async def get_readable_message(sid, is_user, page_no=1, status="All", page_step=
buttons.data_button(label, f"status {sid} st {status_value}")
buttons.data_button("âťď¸", f"status {sid} ref", position="header")
button = buttons.build_menu(8)
- msg += f"CPU: {cpu_percent()}% | FREE: {get_readable_file_size(disk_usage(DOWNLOAD_DIR).free)}"
+ msg += f"CPU: {cpu_percent()}% | FREE: {get_readable_file_size(disk_usage(Config.DOWNLOAD_DIR).free)}"
msg += f"\nRAM: {virtual_memory().percent}% | UPTIME: {get_readable_time(time() - bot_start_time)}"
return msg, button
-"""
-
-
-def source(self):
- return (
- sender_chat.title
- if (sender_chat := self.message.sender_chat)
- else self.message.from_user.username or self.message.from_user.id
- )
-
-
-async def get_readable_message(sid, is_user, page_no=1, status="All"):
- msg = ""
- button = None
-
- tasks = await sync_to_async(get_specific_tasks, status, sid if is_user else None)
-
- STATUS_LIMIT = 4
- tasks_no = len(tasks)
- pages = (max(tasks_no, 1) + STATUS_LIMIT - 1) // STATUS_LIMIT
- if page_no > pages:
- page_no = (page_no - 1) % pages + 1
- status_dict[sid]["page_no"] = page_no
- elif page_no < 1:
- page_no = pages - (abs(page_no) % pages)
- status_dict[sid]["page_no"] = page_no
- start_position = (page_no - 1) * STATUS_LIMIT
-
- for index, task in enumerate(
- tasks[start_position : STATUS_LIMIT + start_position],
- start=1,
- ):
- tstatus = await sync_to_async(task.status) if status == "All" else status
- msg += f"{index + start_position}. {tstatus}:\n"
- msg += f"{escape(f'{task.name()}')}"
- if tstatus not in [
- MirrorStatus.STATUS_SPLIT,
- MirrorStatus.STATUS_SEED,
- MirrorStatus.STATUS_CONVERT,
- MirrorStatus.STATUS_FFMPEG,
- MirrorStatus.STATUS_QUEUEUP,
- MirrorStatus.STATUS_SAMVID,
- ]:
- progress = (
- await task.progress()
- if iscoroutinefunction(task.progress)
- else task.progress()
- )
- msg += f"\n{get_progress_bar_string(progress)} {progress}"
- msg += f"\n{task.processed_bytes()} of {task.size()}"
- msg += f"\nSpeed: {task.speed()}\nEstimated: {task.eta()}"
- if hasattr(task, "seeders_num"):
- with contextlib.suppress(Exception):
- msg += f"\nSeeders: {task.seeders_num()} Leechers: {task.leechers_num()}"
- elif tstatus == MirrorStatus.STATUS_SEEDING:
- msg += f"\nSize: {task.size()}"
- msg += f"\nSpeed: {task.seed_speed()}"
- msg += f"\nUploaded: {task.uploaded_bytes()}"
- msg += f"\nRatio: {task.ratio()}"
- else:
- msg += f"\nSize: {task.size()}"
- msg += f"\nElapsed: {get_readable_time(time() - task.listener.message.date.timestamp())}"
- msg += f"\nBy: {source(task.listener)}"
- msg += f"\n/stop_{task.gid()[:7]}\n\n"
-
- if len(msg) == 0:
- if status == "All":
- return None, None
- msg = f"No Active {status} Tasks!\n\n"
- if tasks_no > STATUS_LIMIT:
- buttons = ButtonMaker()
- msg += f"\nPage: {page_no}/{pages} | Tasks: {tasks_no}"
- buttons.data_button("Prev", f"status {sid} pre", position="header")
- buttons.data_button("Next", f"status {sid} nex", position="header")
- button = buttons.build_menu(2)
- msg += f"\nFree disk: {get_readable_file_size(disk_usage(DOWNLOAD_DIR).free)}"
- msg += f"\nBot uptime: {get_readable_time(time() - bot_start_time)}"
- return msg, button
diff --git a/bot/helper/ext_utils/task_manager.py b/bot/helper/ext_utils/task_manager.py
index d9566ce46..2d67b19b5 100644
--- a/bot/helper/ext_utils/task_manager.py
+++ b/bot/helper/ext_utils/task_manager.py
@@ -2,13 +2,13 @@
from bot import (
LOGGER,
- config_dict,
non_queued_dl,
non_queued_up,
queue_dict_lock,
queued_dl,
queued_up,
)
+from bot.core.config_manager import Config
from bot.helper.mirror_leech_utils.gdrive_utils.search import GoogleDriveSearch
from .bot_utils import get_telegraph_list, sync_to_async
@@ -55,12 +55,8 @@ async def stop_duplicate_check(listener):
async def check_running_tasks(listener, state="dl"):
- all_limit = config_dict["QUEUE_ALL"]
- state_limit = (
- config_dict["QUEUE_DOWNLOAD"]
- if state == "dl"
- else config_dict["QUEUE_UPLOAD"]
- )
+ all_limit = Config.QUEUE_ALL
+ state_limit = Config.QUEUE_DOWNLOAD if state == "dl" else Config.QUEUE_UPLOAD
event = None
is_over_limit = False
async with queue_dict_lock:
@@ -108,9 +104,9 @@ async def start_up_from_queued(mid: int):
async def start_from_queued():
- if all_limit := config_dict["QUEUE_ALL"]:
- dl_limit = config_dict["QUEUE_DOWNLOAD"]
- up_limit = config_dict["QUEUE_UPLOAD"]
+ if all_limit := Config.QUEUE_ALL:
+ dl_limit = Config.QUEUE_DOWNLOAD
+ up_limit = Config.QUEUE_UPLOAD
async with queue_dict_lock:
dl = len(non_queued_dl)
up = len(non_queued_up)
@@ -130,7 +126,7 @@ async def start_from_queued():
break
return
- if up_limit := config_dict["QUEUE_UPLOAD"]:
+ if up_limit := Config.QUEUE_UPLOAD:
async with queue_dict_lock:
up = len(non_queued_up)
if queued_up and up < up_limit:
@@ -145,7 +141,7 @@ async def start_from_queued():
for mid in list(queued_up.keys()):
await start_up_from_queued(mid)
- if dl_limit := config_dict["QUEUE_DOWNLOAD"]:
+ if dl_limit := Config.QUEUE_DOWNLOAD:
async with queue_dict_lock:
dl = len(non_queued_dl)
if queued_dl and dl < dl_limit:
diff --git a/bot/helper/listeners/aria2_listener.py b/bot/helper/listeners/aria2_listener.py
index 92abade0c..a1c834462 100644
--- a/bot/helper/listeners/aria2_listener.py
+++ b/bot/helper/listeners/aria2_listener.py
@@ -5,7 +5,8 @@
from aiofiles.os import path as aiopath
from aiofiles.os import remove
-from bot import LOGGER, aria2, config_dict, intervals, task_dict, task_dict_lock
+from bot import LOGGER, aria2, intervals, task_dict, task_dict_lock
+from bot.core.config_manager import Config
from bot.helper.ext_utils.bot_utils import (
bt_selection_buttons,
loop_thread,
@@ -70,7 +71,7 @@ async def _on_download_complete(api, gid):
LOGGER.info(f"Gid changed from {gid} to {new_gid}")
if task := await get_task_by_gid(new_gid):
task.listener.is_torrent = True
- if config_dict["BASE_URL"] and task.listener.select:
+ if Config.BASE_URL and task.listener.select:
if not task.queued:
await sync_to_async(api.client.force_pause, new_gid)
SBUTTONS = bt_selection_buttons(new_gid)
@@ -150,10 +151,7 @@ async def _on_bt_download_complete(api, gid):
async with task_dict_lock:
if task.listener.mid not in task_dict:
await sync_to_async(
- api.remove,
- [download],
- force=True,
- files=True,
+ api.remove, [download], force=True, files=True
)
return
task_dict[task.listener.mid] = Aria2Status(task.listener, gid, True)
diff --git a/bot/helper/listeners/qbit_listener.py b/bot/helper/listeners/qbit_listener.py
index 49c1992e8..b8c0bad92 100644
--- a/bot/helper/listeners/qbit_listener.py
+++ b/bot/helper/listeners/qbit_listener.py
@@ -7,14 +7,14 @@
from bot import (
LOGGER,
- config_dict,
intervals,
qb_listener_lock,
qb_torrents,
+ xnox_client,
task_dict,
task_dict_lock,
- xnox_client,
)
+from bot.core.config_manager import Config
from bot.helper.ext_utils.bot_utils import new_task, sync_to_async
from bot.helper.ext_utils.files_utils import clean_unwanted
from bot.helper.ext_utils.status_utils import get_readable_time, get_task_by_gid
@@ -58,16 +58,14 @@ async def _on_seed_finish(tor):
@new_task
async def _stop_duplicate(tor):
- if (
- task := await get_task_by_gid(tor.hash[:12])
- ) and task.listener.stop_duplicate:
- task.listener.name = tor.content_path.rsplit("/", 1)[-1].rsplit(
- ".!qB",
- 1,
- )[0]
- msg, button = await stop_duplicate_check(task.listener)
- if msg:
- _on_download_error(msg, tor, button)
+ if task := await get_task_by_gid(tor.hash[:12]):
+ if task.listener.stop_duplicate:
+ task.listener.name = tor.content_path.rsplit("/", 1)[-1].rsplit(
+ ".!qB", 1
+ )[0]
+ msg, button = await stop_duplicate_check(task.listener)
+ if msg:
+ _on_download_error(msg, tor, button)
@new_task
@@ -76,7 +74,10 @@ async def _on_download_complete(tor):
tag = tor.tags
if task := await get_task_by_gid(ext_hash[:12]):
if not task.listener.seed:
- await sync_to_async(xnox_client.torrents_stop, torrent_hashes=ext_hash)
+ await sync_to_async(
+ xnox_client.torrents_stop,
+ torrent_hashes=ext_hash,
+ )
if task.listener.select:
await clean_unwanted(task.listener.dir)
path = tor.content_path.rsplit("/", 1)[0]
@@ -132,11 +133,11 @@ async def _qb_listener():
continue
state = tor_info.state
if state == "metaDL":
- TORRENT_TIMEOUT = config_dict["TORRENT_TIMEOUT"]
qb_torrents[tag]["stalled_time"] = time()
if (
- TORRENT_TIMEOUT
- and time() - tor_info.added_on >= TORRENT_TIMEOUT
+ Config.TORRENT_TIMEOUT
+ and time() - qb_torrents[tag]["start_time"]
+ >= Config.TORRENT_TIMEOUT
):
await _on_download_error("Dead Torrent!", tor_info)
else:
@@ -150,7 +151,6 @@ async def _qb_listener():
qb_torrents[tag]["stop_dup_check"] = True
await _stop_duplicate(tor_info)
elif state == "stalledDL":
- TORRENT_TIMEOUT = config_dict["TORRENT_TIMEOUT"]
if (
not qb_torrents[tag]["rechecked"]
and 0.99989999999999999 < tor_info.progress < 1
@@ -165,9 +165,9 @@ async def _qb_listener():
)
qb_torrents[tag]["rechecked"] = True
elif (
- TORRENT_TIMEOUT
+ Config.TORRENT_TIMEOUT
and time() - qb_torrents[tag]["stalled_time"]
- >= TORRENT_TIMEOUT
+ >= Config.TORRENT_TIMEOUT
):
await _on_download_error("Dead Torrent!", tor_info)
else:
@@ -208,6 +208,7 @@ async def _qb_listener():
async def on_download_start(tag):
async with qb_listener_lock:
qb_torrents[tag] = {
+ "start_time": time(),
"stalled_time": time(),
"stop_dup_check": False,
"rechecked": False,
diff --git a/bot/helper/listeners/task_listener.py b/bot/helper/listeners/task_listener.py
index ef953dd8e..0048eaf7e 100644
--- a/bot/helper/listeners/task_listener.py
+++ b/bot/helper/listeners/task_listener.py
@@ -7,10 +7,8 @@
from requests import utils as rutils
from bot import (
- DOWNLOAD_DIR,
LOGGER,
aria2,
- config_dict,
intervals,
non_queued_dl,
non_queued_up,
@@ -21,8 +19,10 @@
task_dict,
task_dict_lock,
)
+from bot.core.config_manager import Config
from bot.helper.common import TaskConfig
from bot.helper.ext_utils.bot_utils import sync_to_async
+from bot.helper.ext_utils.db_handler import database
from bot.helper.ext_utils.files_utils import (
clean_download,
clean_target,
@@ -74,7 +74,16 @@ async def remove_from_same_dir(self):
self.same_dir[self.folder_name]["total"] -= 1
async def on_download_start(self):
- pass
+ if (
+ self.is_super_chat
+ and Config.INCOMPLETE_TASK_NOTIFIER
+ and Config.DATABASE_URL
+ ):
+ await database.add_incomplete_task(
+ self.message.chat.id,
+ self.message.link,
+ self.tag,
+ )
async def on_download_complete(self):
await sleep(2)
@@ -100,14 +109,12 @@ async def on_download_complete(self):
self.same_dir[self.folder_name]["total"] -= 1
spath = f"{self.dir}{self.folder_name}"
des_id = next(
- iter(self.same_dir[self.folder_name]["tasks"]),
- )
- des_path = (
- f"{DOWNLOAD_DIR}{des_id}{self.folder_name}"
+ iter(self.same_dir[self.folder_name]["tasks"])
)
+ des_path = f"{Config.DOWNLOAD_DIR}{des_id}{self.folder_name}"
await makedirs(des_path, exist_ok=True)
LOGGER.info(
- f"Moving files from {self.mid} to {des_id}",
+ f"Moving files from {self.mid} to {des_id}"
)
for item in await listdir(spath):
if item.endswith((".aria2", ".!qB")):
@@ -159,7 +166,7 @@ async def on_download_complete(self):
up_path = f"{self.dir}/{self.name}"
self.size = await get_path_size(up_path)
- if not config_dict["QUEUE_ALL"]:
+ if not Config.QUEUE_ALL:
async with queue_dict_lock:
if self.mid in non_queued_dl:
non_queued_dl.remove(self.mid)
@@ -238,10 +245,7 @@ async def on_download_complete(self):
if self.is_leech and not self.compress:
await self.proceed_split(
- up_dir,
- unwanted_files_size,
- unwanted_files,
- gid,
+ up_dir, unwanted_files_size, unwanted_files, gid
)
if self.is_cancelled:
return
@@ -298,6 +302,12 @@ async def on_upload_complete(
rclone_path="",
dir_id="",
):
+ if (
+ self.is_super_chat
+ and Config.INCOMPLETE_TASK_NOTIFIER
+ and Config.DATABASE_URL
+ ):
+ await database.rm_complete_task(self.message.link)
msg = f"Name: {escape(self.name)}
\n\nSize: {get_readable_file_size(self.size)}"
LOGGER.info(f"Task Done: {self.name}")
if self.is_leech:
@@ -322,21 +332,27 @@ async def on_upload_complete(
if mime_type == "Folder":
msg += f"\nSubFolders: {folders}"
msg += f"\nFiles: {files}"
- if link or (rclone_path and not self.private_link):
+ if link or (
+ rclone_path and Config.RCLONE_SERVE_URL and not self.private_link
+ ):
buttons = ButtonMaker()
if link:
buttons.url_button("âď¸ Cloud Link", link)
else:
msg += f"\n\nPath: {rclone_path}
"
- if rclone_path and not self.private_link:
+ if rclone_path and Config.RCLONE_SERVE_URL and not self.private_link:
remote, path = rclone_path.split(":", 1)
- rutils.quote(f"{path}")
+ url_path = rutils.quote(f"{path}")
+ share_url = f"{Config.RCLONE_SERVE_URL}/{remote}/{url_path}"
+ if mime_type == "Folder":
+ share_url += "/"
+ buttons.url_button("đ Rclone Link", share_url)
if not rclone_path and dir_id:
INDEX_URL = ""
if self.private_link:
INDEX_URL = self.user_dict.get("index_url", "") or ""
- elif config_dict["INDEX_URL"]:
- INDEX_URL = config_dict["INDEX_URL"]
+ elif Config.INDEX_URL:
+ INDEX_URL = Config.INDEX_URL
if INDEX_URL:
share_url = f"{INDEX_URL}findpath?id={dir_id}"
buttons.url_button("⥠Index Link", share_url)
@@ -386,6 +402,13 @@ async def on_download_error(self, error, button=None):
else:
await update_status_message(self.message.chat.id)
+ if (
+ self.is_super_chat
+ and Config.INCOMPLETE_TASK_NOTIFIER
+ and Config.DATABASE_URL
+ ):
+ await database.rm_complete_task(self.message.link)
+
async with queue_dict_lock:
if self.mid in queued_dl:
queued_dl[self.mid].set()
@@ -417,6 +440,13 @@ async def on_upload_error(self, error):
else:
await update_status_message(self.message.chat.id)
+ if (
+ self.is_super_chat
+ and Config.INCOMPLETE_TASK_NOTIFIER
+ and Config.DATABASE_URL
+ ):
+ await database.rm_complete_task(self.message.link)
+
async with queue_dict_lock:
if self.mid in queued_dl:
queued_dl[self.mid].set()
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 4f924cdea..01467de54 100644
--- a/bot/helper/mirror_leech_utils/download_utils/aria2_download.py
+++ b/bot/helper/mirror_leech_utils/download_utils/aria2_download.py
@@ -1,15 +1,8 @@
from aiofiles.os import path as aiopath
from aiofiles.os import remove
-from bot import (
- LOGGER,
- aria2,
- aria2_options,
- aria2c_global,
- config_dict,
- task_dict,
- task_dict_lock,
-)
+from bot import LOGGER, aria2, task_dict, task_dict_lock
+from bot.core.config_manager import Config
from bot.helper.ext_utils.bot_utils import bt_selection_buttons, sync_to_async
from bot.helper.ext_utils.task_manager import check_running_tasks
from bot.helper.mirror_leech_utils.status_utils.aria2_status import Aria2Status
@@ -20,9 +13,7 @@
async def add_aria2c_download(listener, dpath, header, ratio, seed_time):
- a2c_opt = {**aria2_options}
- [a2c_opt.pop(k) for k in aria2c_global if k in aria2_options]
- a2c_opt["dir"] = dpath
+ a2c_opt = {"dir": dpath}
if listener.name:
a2c_opt["out"] = listener.name
if header:
@@ -31,7 +22,7 @@ async def add_aria2c_download(listener, dpath, header, ratio, seed_time):
a2c_opt["seed-ratio"] = ratio
if seed_time:
a2c_opt["seed-time"] = seed_time
- if TORRENT_TIMEOUT := config_dict["TORRENT_TIMEOUT"]:
+ if TORRENT_TIMEOUT := Config.TORRENT_TIMEOUT:
a2c_opt["bt-stop-timeout"] = f"{TORRENT_TIMEOUT}"
add_to_queue, event = await check_running_tasks(listener)
@@ -70,7 +61,7 @@ async def add_aria2c_download(listener, dpath, header, ratio, seed_time):
if (
not add_to_queue
- and (not listener.select or not config_dict["BASE_URL"])
+ and (not listener.select or not Config.BASE_URL)
and listener.multi <= 1
):
await send_status_message(listener.message)
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 2b7eaf26b..5e3804fb2 100644
--- a/bot/helper/mirror_leech_utils/download_utils/direct_downloader.py
+++ b/bot/helper/mirror_leech_utils/download_utils/direct_downloader.py
@@ -1,12 +1,6 @@
-from secrets import token_hex
+from secrets import token_urlsafe
-from bot import (
- LOGGER,
- aria2_options,
- aria2c_global,
- task_dict,
- task_dict_lock,
-)
+from bot import LOGGER, task_dict, task_dict_lock
from bot.helper.ext_utils.bot_utils import sync_to_async
from bot.helper.ext_utils.task_manager import (
check_running_tasks,
@@ -34,7 +28,7 @@ async def add_direct_download(listener, path):
await listener.on_download_error(msg, button)
return
- gid = token_hex(4)
+ gid = token_urlsafe(10)
add_to_queue, event = await check_running_tasks(listener)
if add_to_queue:
LOGGER.info(f"Added to Queue/Download: {listener.name}")
@@ -47,12 +41,9 @@ async def add_direct_download(listener, path):
if listener.is_cancelled:
return
- a2c_opt = {**aria2_options}
- [a2c_opt.pop(k) for k in aria2c_global if k in aria2_options]
+ a2c_opt = {"follow-torrent": "false", "follow-metalink": "false"}
if header := details.get("header"):
a2c_opt["header"] = header
- a2c_opt["follow-torrent"] = "false"
- a2c_opt["follow-metalink"] = "false"
directListener = DirectListener(path, listener, a2c_opt)
async with task_dict_lock:
diff --git a/bot/helper/mirror_leech_utils/download_utils/direct_link_generator.py b/bot/helper/mirror_leech_utils/download_utils/direct_link_generator.py
index aacaec78b..5e4dad613 100644
--- a/bot/helper/mirror_leech_utils/download_utils/direct_link_generator.py
+++ b/bot/helper/mirror_leech_utils/download_utils/direct_link_generator.py
@@ -1,25 +1,24 @@
-# ruff: noqa
-from os import path as ospath
-from re import match, search, findall
-from json import loads
-from time import sleep
-from uuid import uuid4
from base64 import b64decode
from hashlib import sha256
-from urllib.parse import parse_qs, urlparse
from http.cookiejar import MozillaCookieJar
+from json import loads
+from os import path as ospath
+from re import findall, match, search
+from time import sleep
+from urllib.parse import parse_qs, urlparse
+from uuid import uuid4
-from requests import Session, RequestException, get, post
-from lxml.etree import HTML
from cloudscraper import create_scraper
+from lxml.etree import HTML
+from requests import RequestException, Session, get, post
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
-from bot import config_dict
+from bot.core.config_manager import Config
from bot.helper.ext_utils.exceptions import DirectDownloadLinkException
+from bot.helper.ext_utils.help_messages import PASSWORD_ERROR_MESSAGE
from bot.helper.ext_utils.links_utils import is_share_link
from bot.helper.ext_utils.status_utils import speed_string_to_bytes
-from bot.helper.ext_utils.help_messages import PASSWORD_ERROR_MESSAGE
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0"
@@ -169,7 +168,7 @@ def direct_link_generator(link):
"telbx.net",
]
):
- return linkbox(link)
+ return linkBox(link)
if is_share_link(link):
if "gdtot" in domain:
return gdtot(link)
@@ -227,7 +226,8 @@ def mediafire(url, session=None):
else:
_password = ""
if final_link := findall(
- r"https?:\/\/download\d+\.mediafire\.com\/\S+\/\S+\/\S+", url
+ r"https?:\/\/download\d+\.mediafire\.com\/\S+\/\S+\/\S+",
+ url,
):
return final_link[0]
@@ -257,7 +257,7 @@ def _repair_download(url, session):
if not _password:
session.close()
raise DirectDownloadLinkException(
- f"ERROR: {PASSWORD_ERROR_MESSAGE}".format(url)
+ f"ERROR: {PASSWORD_ERROR_MESSAGE}".format(url),
)
try:
html = HTML(session.post(url, data={"downloadp": _password}).text)
@@ -273,7 +273,7 @@ def _repair_download(url, session):
if repair_link := html.xpath("//a[@class='retry']/@href"):
return _repair_download(repair_link[0], session)
raise DirectDownloadLinkException(
- "ERROR: No links found in this page Try Again"
+ "ERROR: No links found in this page Try Again",
)
if final_link[0].startswith("//"):
final_url = f"https://{final_link[0][2:]}"
@@ -311,7 +311,7 @@ def yandex_disk(url: str) -> str:
return get(api.format(link)).json()["href"]
except KeyError as e:
raise DirectDownloadLinkException(
- "ERROR: File not found/Download limit reached"
+ "ERROR: File not found/Download limit reached",
) from e
@@ -345,7 +345,7 @@ def hxfile(url):
url,
data={"op": "download2", "id": file_code},
cookies=cookies,
- ).text
+ ).text,
)
except Exception as e:
raise DirectDownloadLinkException(
@@ -417,7 +417,7 @@ def pixeldrain(url):
if resp["success"]:
return dl_link
raise DirectDownloadLinkException(
- f"ERROR: Cant't download due {resp['message']}."
+ f"ERROR: Cant't download due {resp['message']}.",
)
@@ -430,7 +430,7 @@ def streamtape(url):
except Exception as e:
raise DirectDownloadLinkException(f"ERROR: {e.__class__.__name__}") from e
script = html.xpath(
- "//script[contains(text(),'ideoooolink')]/text()"
+ "//script[contains(text(),'ideoooolink')]/text()",
) or html.xpath("//script[contains(text(),'ideoolink')]/text()")
if not script:
raise DirectDownloadLinkException("ERROR: requeries script not found")
@@ -479,31 +479,31 @@ def fichier(link):
raise DirectDownloadLinkException(f"ERROR: {e.__class__.__name__}") from e
if req.status_code == 404:
raise DirectDownloadLinkException(
- "ERROR: File not found/The link you entered is wrong!"
+ "ERROR: File not found/The link you entered is wrong!",
)
html = HTML(req.text)
if dl_url := html.xpath('//a[@class="ok btn-general btn-orange"]/@href'):
return dl_url[0]
if not (ct_warn := html.xpath('//div[@class="ct_warn"]')):
raise DirectDownloadLinkException(
- "ERROR: Error trying to generate Direct Link from 1fichier!"
+ "ERROR: Error trying to generate Direct Link from 1fichier!",
)
if len(ct_warn) == 3:
str_2 = ct_warn[-1].text
if "you must wait" in str_2.lower():
if numbers := [int(word) for word in str_2.split() if word.isdigit()]:
raise DirectDownloadLinkException(
- f"ERROR: 1fichier is on a limit. Please wait {numbers[0]} minute."
+ f"ERROR: 1fichier is on a limit. Please wait {numbers[0]} minute.",
)
raise DirectDownloadLinkException(
- "ERROR: 1fichier is on a limit. Please wait a few minutes/hour."
+ "ERROR: 1fichier is on a limit. Please wait a few minutes/hour.",
)
if "protect access" in str_2.lower():
raise DirectDownloadLinkException(
- f"ERROR:\n{PASSWORD_ERROR_MESSAGE.format(link)}"
+ f"ERROR:\n{PASSWORD_ERROR_MESSAGE.format(link)}",
)
raise DirectDownloadLinkException(
- "ERROR: Failed to generate Direct Link from 1fichier!"
+ "ERROR: Failed to generate Direct Link from 1fichier!",
)
if len(ct_warn) == 4:
str_1 = ct_warn[-2].text
@@ -511,17 +511,17 @@ def fichier(link):
if "you must wait" in str_1.lower():
if numbers := [int(word) for word in str_1.split() if word.isdigit()]:
raise DirectDownloadLinkException(
- f"ERROR: 1fichier is on a limit. Please wait {numbers[0]} minute."
+ f"ERROR: 1fichier is on a limit. Please wait {numbers[0]} minute.",
)
raise DirectDownloadLinkException(
- "ERROR: 1fichier is on a limit. Please wait a few minutes/hour."
+ "ERROR: 1fichier is on a limit. Please wait a few minutes/hour.",
)
if "bad password" in str_3.lower():
raise DirectDownloadLinkException(
- "ERROR: The password you entered is wrong!"
+ "ERROR: The password you entered is wrong!",
)
raise DirectDownloadLinkException(
- "ERROR: Error trying to generate Direct Link from 1fichier!"
+ "ERROR: Error trying to generate Direct Link from 1fichier!",
)
@@ -532,11 +532,11 @@ def solidfiles(url):
with create_scraper() as session:
try:
headers = {
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36"
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36",
}
pageSource = session.get(url, headers=headers).text
mainOptions = str(
- search(r"viewerOptions\'\,\ (.*?)\)\;", pageSource).group(1)
+ search(r"viewerOptions\'\,\ (.*?)\)\;", pageSource).group(1),
)
return loads(mainOptions)["downloadUrl"]
except Exception as e:
@@ -568,11 +568,11 @@ def krakenfiles(url):
_json = session.post(post_url, data=data).json()
except Exception as e:
raise DirectDownloadLinkException(
- f"ERROR: {e.__class__.__name__} While send post request"
+ f"ERROR: {e.__class__.__name__} While send post request",
) from e
if _json["status"] != "ok":
raise DirectDownloadLinkException(
- "ERROR: Unable to find download after post request"
+ "ERROR: Unable to find download after post request",
)
return _json["url"]
@@ -649,7 +649,7 @@ def terabox(url, video_quality="HD Video", save_dir="HD_Video"):
"url": zlink,
"filename": title,
"path": ospath.join(title, save_dir),
- }
+ },
)
details["title"] = title
@@ -703,14 +703,15 @@ def gdtot(url):
except Exception as e:
raise DirectDownloadLinkException(f"ERROR: {e.__class__.__name__}") from e
token_url = HTML(res.text).xpath(
- "//a[contains(@class,'inline-flex items-center justify-center')]/@href"
+ "//a[contains(@class,'inline-flex items-center justify-center')]/@href",
)
if not token_url:
try:
url = cget("GET", url).url
p_url = urlparse(url)
res = cget(
- "GET", f"{p_url.scheme}://{p_url.hostname}/ddl/{url.split('/')[-1]}"
+ "GET",
+ f"{p_url.scheme}://{p_url.hostname}/ddl/{url.split('/')[-1]}",
)
except Exception as e:
raise DirectDownloadLinkException(
@@ -721,14 +722,14 @@ def gdtot(url):
) and "drive.google.com" in drive_link[0]:
return drive_link[0]
raise DirectDownloadLinkException(
- "ERROR: Drive Link not found, Try in your broswer"
+ "ERROR: Drive Link not found, Try in your broswer",
)
token_url = token_url[0]
try:
token_page = cget("GET", token_url)
except Exception as e:
raise DirectDownloadLinkException(
- f"ERROR: {e.__class__.__name__} with {token_url}"
+ f"ERROR: {e.__class__.__name__} with {token_url}",
) from e
path = findall(r'\("(.*?)"\)', token_page.text)
if not path:
@@ -745,7 +746,7 @@ def sharer_scraper(url):
url = cget("GET", url).url
raw = urlparse(url)
header = {
- "useragent": "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.548.0 Safari/534.10"
+ "useragent": "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/7.0.548.0 Safari/534.10",
}
res = cget("GET", url, headers=header)
except Exception as e:
@@ -756,7 +757,7 @@ def sharer_scraper(url):
key = key[0]
if not HTML(res.text).xpath("//button[@id='drc']"):
raise DirectDownloadLinkException(
- "ERROR: This link don't have direct download button"
+ "ERROR: This link don't have direct download button",
)
boundary = uuid4()
headers = {
@@ -779,7 +780,7 @@ def sharer_scraper(url):
raise DirectDownloadLinkException(f"ERROR: {e.__class__.__name__}") from e
if "url" not in res:
raise DirectDownloadLinkException(
- "ERROR: Drive Link not found, Try in your broswer"
+ "ERROR: Drive Link not found, Try in your broswer",
)
if "drive.google.com" in res["url"]:
return res["url"]
@@ -792,7 +793,7 @@ def sharer_scraper(url):
) and "drive.google.com" in drive_link[0]:
return drive_link[0]
raise DirectDownloadLinkException(
- "ERROR: Drive Link not found, Try in your broswer"
+ "ERROR: Drive Link not found, Try in your broswer",
)
@@ -829,7 +830,7 @@ def akmfiles(url):
session.post(
url,
data={"op": "download2", "id": url.split("/")[-1]},
- ).text
+ ).text,
)
except Exception as e:
raise DirectDownloadLinkException(
@@ -866,16 +867,16 @@ def shrdsk(url):
raise DirectDownloadLinkException("ERROR: cannot find direct link in headers")
-def linkbox(url: str):
+def linkBox(url: str):
parsed_url = urlparse(url)
try:
shareToken = parsed_url.path.split("/")[-1]
- except Exception:
+ except:
raise DirectDownloadLinkException("ERROR: invalid URL")
details = {"contents": [], "title": "", "total_size": 0}
- def __single_item(session, itemId):
+ def __singleItem(session, itemId):
try:
_json = session.get(
"https://www.linkbox.to/api/file/detail",
@@ -933,8 +934,8 @@ def __fetch_links(session, _id=0, folderPath=""):
raise DirectDownloadLinkException("ERROR: data not found")
try:
if data["shareType"] == "singleItem":
- return __single_item(session, data["itemId"])
- except Exception:
+ return __singleItem(session, data["itemId"])
+ except:
pass
if not details["title"]:
details["title"] = data["dirName"]
@@ -955,7 +956,7 @@ def __fetch_links(session, _id=0, folderPath=""):
folderPath = details["title"]
filename = content["name"]
if (sub_type := content.get("sub_type")) and not filename.endswith(
- sub_type
+ sub_type,
):
filename += f".{sub_type}"
item = {
@@ -1024,13 +1025,13 @@ def __fetch_links(session, _id, folderPath=""):
raise DirectDownloadLinkException(f"ERROR: {e.__class__.__name__}")
if _json["status"] in "error-passwordRequired":
raise DirectDownloadLinkException(
- f"ERROR:\n{PASSWORD_ERROR_MESSAGE.format(url)}"
+ f"ERROR:\n{PASSWORD_ERROR_MESSAGE.format(url)}",
)
if _json["status"] in "error-passwordWrong":
raise DirectDownloadLinkException("ERROR: This password is wrong !")
if _json["status"] in "error-notFound":
raise DirectDownloadLinkException(
- "ERROR: File not found on gofile's server"
+ "ERROR: File not found on gofile's server",
)
if _json["status"] in "error-notPublic":
raise DirectDownloadLinkException("ERROR: This folder is not public")
@@ -1092,7 +1093,7 @@ def mediafireFolder(url):
raw = url.split("/", 4)[-1]
folderkey = raw.split("/", 1)[0]
folderkey = folderkey.split(",")
- except Exception:
+ except:
raise DirectDownloadLinkException("ERROR: Could not parse ")
if len(folderkey) == 1:
folderkey = folderkey[0]
@@ -1100,7 +1101,7 @@ def mediafireFolder(url):
session = create_scraper()
adapter = HTTPAdapter(
- max_retries=Retry(total=10, read=10, connect=10, backoff_factor=0.3)
+ max_retries=Retry(total=10, read=10, connect=10, backoff_factor=0.3),
)
session.mount("http://", adapter)
session.mount("https://", adapter)
@@ -1125,7 +1126,7 @@ def __get_info(folderkey):
).json()
except Exception as e:
raise DirectDownloadLinkException(
- f"ERROR: {e.__class__.__name__} While getting info"
+ f"ERROR: {e.__class__.__name__} While getting info",
)
_res = _json["response"]
if "folder_infos" in _res:
@@ -1154,21 +1155,21 @@ def __repair_download(url):
html = HTML(session.get(url).text)
if new_link := html.xpath('//a[@id="continue-btn"]/@href'):
return __scraper(f"https://mediafire.com/{new_link[0]}")
- except Exception:
+ except:
return None
try:
html = HTML(session.get(url).text)
- except Exception:
+ except:
return None
if html.xpath("//div[@class='passwordPrompt']"):
if not _password:
raise DirectDownloadLinkException(
- f"ERROR: {PASSWORD_ERROR_MESSAGE}".format(url)
+ f"ERROR: {PASSWORD_ERROR_MESSAGE}".format(url),
)
try:
html = HTML(session.post(url, data={"downloadp": _password}).text)
- except Exception:
+ except:
return None
if html.xpath("//div[@class='passwordPrompt']"):
return None
@@ -1193,7 +1194,7 @@ def __get_content(folderKey, folderPath="", content_type="folders"):
).json()
except Exception as e:
raise DirectDownloadLinkException(
- f"ERROR: {e.__class__.__name__} While getting content"
+ f"ERROR: {e.__class__.__name__} While getting content",
)
_res = _json["response"]
if "message" in _res:
@@ -1267,7 +1268,7 @@ def send_cm_file(url, file_id=None):
html = HTML(session.get(url).text)
except Exception as e:
raise DirectDownloadLinkException(
- f"ERROR: {e.__class__.__name__}"
+ f"ERROR: {e.__class__.__name__}",
) from e
if html.xpath("//input[@name='password']"):
_passwordNeed = True
@@ -1286,7 +1287,7 @@ def send_cm_file(url, file_id=None):
) from e
if _passwordNeed:
raise DirectDownloadLinkException(
- f"ERROR:\n{PASSWORD_ERROR_MESSAGE.format(url)}"
+ f"ERROR:\n{PASSWORD_ERROR_MESSAGE.format(url)}",
)
raise DirectDownloadLinkException("ERROR: Direct link not found")
@@ -1317,12 +1318,14 @@ def __collectFolders(html):
folders = []
folders_urls = html.xpath("//h6/a/@href")
folders_names = html.xpath("//h6/a/text()")
- for folders_url, folders_name in zip(folders_urls, folders_names):
+ for folders_url, folders_name in zip(
+ folders_urls, folders_names, strict=False
+ ):
folders.append(
{
"folder_link": folders_url.strip(),
"folder_name": folders_name.strip(),
- }
+ },
)
return folders
@@ -1335,7 +1338,7 @@ def __getFile_link(file_id):
)
if "Location" in _res.headers:
return _res.headers["Location"]
- except Exception:
+ except:
pass
def __getFiles(html):
@@ -1343,13 +1346,15 @@ def __getFiles(html):
hrefs = html.xpath('//tr[@class="selectable"]//a/@href')
file_names = html.xpath('//tr[@class="selectable"]//a/text()')
sizes = html.xpath('//tr[@class="selectable"]//span/text()')
- for href, file_name, size_text in zip(hrefs, file_names, sizes):
+ for href, file_name, size_text in zip(
+ hrefs, file_names, sizes, strict=False
+ ):
files.append(
{
"file_id": href.split("/")[-1],
"file_name": file_name.strip(),
"size": speed_string_to_bytes(size_text.strip()),
- }
+ },
)
return files
@@ -1374,7 +1379,7 @@ def __writeContents(html_text, folderPath=""):
except Exception as e:
session.close()
raise DirectDownloadLinkException(
- f"ERROR: {e.__class__.__name__} While getting mainHtml"
+ f"ERROR: {e.__class__.__name__} While getting mainHtml",
)
try:
__writeContents(mainHtml, details["title"])
@@ -1384,7 +1389,7 @@ def __writeContents(html_text, folderPath=""):
except Exception as e:
session.close()
raise DirectDownloadLinkException(
- f"ERROR: {e.__class__.__name__} While writing Contents"
+ f"ERROR: {e.__class__.__name__} While writing Contents",
)
session.close()
if len(details["contents"]) == 1:
@@ -1401,11 +1406,11 @@ def doods(url):
html = HTML(session.get(url).text)
except Exception as e:
raise DirectDownloadLinkException(
- f"ERROR: {e.__class__.__name__} While fetching token link"
+ f"ERROR: {e.__class__.__name__} While fetching token link",
) from e
if not (link := html.xpath("//div[@class='download-content']//a/@href")):
raise DirectDownloadLinkException(
- "ERROR: Token Link not found or maybe not allow to download! open in browser."
+ "ERROR: Token Link not found or maybe not allow to download! open in browser.",
)
link = f"{parsed_url.scheme}://{parsed_url.hostname}{link[0]}"
sleep(2)
@@ -1413,7 +1418,7 @@ def doods(url):
_res = session.get(link)
except Exception as e:
raise DirectDownloadLinkException(
- f"ERROR: {e.__class__.__name__} While fetching download link"
+ f"ERROR: {e.__class__.__name__} While fetching download link",
) from e
if not (link := search(r"window\.open\('(\S+)'", _res.text)):
raise DirectDownloadLinkException("ERROR: Download link not found try again")
@@ -1438,7 +1443,7 @@ def easyupload(url):
and not _password
):
raise DirectDownloadLinkException(
- f"ERROR:\n{PASSWORD_ERROR_MESSAGE.format(url)}"
+ f"ERROR:\n{PASSWORD_ERROR_MESSAGE.format(url)}",
)
if not (
match := search(
@@ -1447,7 +1452,7 @@ def easyupload(url):
)
):
raise DirectDownloadLinkException(
- "ERROR: Failed to get server for EasyUpload Link"
+ "ERROR: Failed to get server for EasyUpload Link",
)
action_url = match.group()
session.headers.update({"referer": "https://easyupload.io/"})
@@ -1479,10 +1484,10 @@ def easyupload(url):
return json_resp["download_link"]
if "data" in json_resp:
raise DirectDownloadLinkException(
- f"ERROR: Failed to generate direct link due to {json_resp['data']}"
+ f"ERROR: Failed to generate direct link due to {json_resp['data']}",
)
raise DirectDownloadLinkException(
- "ERROR: Failed to generate direct link from EasyUpload."
+ "ERROR: Failed to generate direct link from EasyUpload.",
)
@@ -1502,7 +1507,7 @@ def filelions_and_streamwish(url):
"mycloudz.cc",
]
):
- apiKey = config_dict["FILELION_API"]
+ apiKey = Config.FILELION_API
apiUrl = "https://vidhideapi.com"
elif any(
x in hostname
@@ -1514,11 +1519,11 @@ def filelions_and_streamwish(url):
"streamwish.to",
]
):
- apiKey = config_dict["STREAMWISH_API"]
+ apiKey = Config.STREAMWISH_API
apiUrl = "https://api.streamwish.com"
if not apiKey:
raise DirectDownloadLinkException(
- f"ERROR: API is not provided get it from {scheme}://{hostname}"
+ f"ERROR: API is not provided get it from {scheme}://{hostname}",
)
file_code = url.split("/")[-1]
quality = ""
@@ -1581,30 +1586,30 @@ def streamvid(url: str):
html = HTML(session.post(url, data=data).text)
except Exception as e:
raise DirectDownloadLinkException(
- f"ERROR: {e.__class__.__name__}"
+ f"ERROR: {e.__class__.__name__}",
) from e
if not (
script := html.xpath(
- '//script[contains(text(),"document.location.href")]/text()'
+ '//script[contains(text(),"document.location.href")]/text()',
)
):
if error := html.xpath(
- '//div[@class="alert alert-danger"][1]/text()[2]'
+ '//div[@class="alert alert-danger"][1]/text()[2]',
):
raise DirectDownloadLinkException(f"ERROR: {error[0]}")
raise DirectDownloadLinkException(
- "ERROR: direct link script not found!"
+ "ERROR: direct link script not found!",
)
if directLink := findall(r'document\.location\.href="(.*)"', script[0]):
return directLink[0]
raise DirectDownloadLinkException(
- "ERROR: direct link not found! in the script"
+ "ERROR: direct link not found! in the script",
)
if (qualities_urls := html.xpath('//div[@id="dl_versions"]/a/@href')) and (
qualities := html.xpath('//div[@id="dl_versions"]/a/text()[2]')
):
error = "\nProvide a quality to download the video\nAvailable Quality:"
- for quality_url, quality in zip(qualities_urls, qualities):
+ for quality_url, quality in zip(qualities_urls, qualities, strict=False):
error += f"\n{quality.strip()} {quality_url}
"
raise DirectDownloadLinkException(f"ERROR: {error}")
if error := html.xpath('//div[@class="not-found-text"]/text()'):
@@ -1638,7 +1643,7 @@ def streamhub(url):
f"ERROR: {e.__class__.__name__}"
) from e
if directLink := html.xpath(
- '//a[@class="btn btn-primary btn-go downloadbtn"]/@href'
+ '//a[@class="btn btn-primary btn-go downloadbtn"]/@href',
):
return directLink[0]
if error := html.xpath('//div[@class="alert alert-danger"]/text()[2]'):
@@ -1721,7 +1726,7 @@ def mp4upload(url):
data["referer"] = url
direct_link = session.post(url, data=data).url
return direct_link, header
- except Exception:
+ except:
raise DirectDownloadLinkException("ERROR: File Not Found!")
diff --git a/bot/helper/mirror_leech_utils/download_utils/gd_download.py b/bot/helper/mirror_leech_utils/download_utils/gd_download.py
index e269c99fd..04a10fac6 100644
--- a/bot/helper/mirror_leech_utils/download_utils/gd_download.py
+++ b/bot/helper/mirror_leech_utils/download_utils/gd_download.py
@@ -1,4 +1,4 @@
-from secrets import token_hex
+from secrets import token_urlsafe
from bot import LOGGER, task_dict, task_dict_lock
from bot.helper.ext_utils.bot_utils import sync_to_async
@@ -27,7 +27,7 @@ async def add_gd_download(listener, path):
return
listener.name = listener.name or name
- gid = token_hex(4)
+ gid = token_urlsafe(12)
msg, button = await stop_duplicate_check(listener)
if msg:
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 50d892507..80f7c36df 100644
--- a/bot/helper/mirror_leech_utils/download_utils/qbit_download.py
+++ b/bot/helper/mirror_leech_utils/download_utils/qbit_download.py
@@ -3,13 +3,8 @@
from aiofiles.os import path as aiopath
from aiofiles.os import remove
-from bot import (
- LOGGER,
- config_dict,
- task_dict,
- task_dict_lock,
- xnox_client,
-)
+from bot import LOGGER, xnox_client, task_dict, task_dict_lock
+from bot.core.config_manager import Config
from bot.helper.ext_utils.bot_utils import bt_selection_buttons, sync_to_async
from bot.helper.ext_utils.task_manager import check_running_tasks
from bot.helper.listeners.qbit_listener import on_download_start
@@ -84,21 +79,20 @@ async def add_qb_torrent(listener, path, ratio, seed_time):
async with task_dict_lock:
task_dict[listener.mid] = QbittorrentStatus(
- listener,
- queued=add_to_queue,
+ listener, queued=add_to_queue
)
await on_download_start(f"{listener.mid}")
if add_to_queue:
LOGGER.info(
- f"Added to Queue/Download: {tor_info.name} - Hash: {ext_hash}",
+ f"Added to Queue/Download: {tor_info.name} - Hash: {ext_hash}"
)
else:
LOGGER.info(f"QbitDownload started: {tor_info.name} - Hash: {ext_hash}")
await listener.on_download_start()
- if config_dict["BASE_URL"] and listener.select:
+ if Config.BASE_URL and listener.select:
if listener.link.startswith("magnet:"):
metamsg = "Downloading Metadata, wait then you can select files. Use torrent file to avoid this wait."
meta = await send_message(listener.message, metamsg)
@@ -119,7 +113,7 @@ async def add_qb_torrent(listener, path, ratio, seed_time):
]:
await delete_message(meta)
break
- except Exception:
+ except:
await delete_message(meta)
return
@@ -145,7 +139,11 @@ async def add_qb_torrent(listener, path, ratio, seed_time):
LOGGER.info(
f"Start Queued Download from Qbittorrent: {tor_info.name} - Hash: {ext_hash}",
)
- await sync_to_async(xnox_client.torrents_start, torrent_hashes=ext_hash)
+ await on_download_start(f"{listener.mid}")
+ await sync_to_async(
+ xnox_client.torrents_start,
+ torrent_hashes=ext_hash,
+ )
except Exception as e:
await listener.on_download_error(f"{e}")
diff --git a/bot/helper/mirror_leech_utils/download_utils/rclone_download.py b/bot/helper/mirror_leech_utils/download_utils/rclone_download.py
index 1cc133124..fde13f138 100644
--- a/bot/helper/mirror_leech_utils/download_utils/rclone_download.py
+++ b/bot/helper/mirror_leech_utils/download_utils/rclone_download.py
@@ -1,6 +1,6 @@
from asyncio import gather
from json import loads
-from secrets import token_hex
+from secrets import token_urlsafe
from aiofiles.os import remove
@@ -33,7 +33,7 @@ async def add_rclone_download(listener, path):
rpath = listener.link
cmd1 = [
- "xone",
+ "rclone",
"lsjson",
"--fast-list",
"--stat",
@@ -42,15 +42,25 @@ async def add_rclone_download(listener, path):
"--config",
config_path,
f"{remote}:{rpath}",
+ "--log-systemd",
+ "--log-file",
+ "rlog.txt",
+ "--log-level",
+ "ERROR",
]
cmd2 = [
- "xone",
+ "rclone",
"size",
"--fast-list",
"--json",
"--config",
config_path,
f"{remote}:{rpath}",
+ "--log-systemd",
+ "--log-file",
+ "rlog.txt",
+ "--log-level",
+ "ERROR",
]
if rclone_select:
cmd2.extend(("--files-from", listener.link))
@@ -103,7 +113,7 @@ async def add_rclone_download(listener, path):
else:
listener.name = listener.link.rsplit("/", 1)[-1]
listener.size = rsize["bytes"]
- gid = token_hex(4)
+ gid = token_urlsafe(12)
if not rclone_select:
msg, button = await stop_duplicate_check(listener)
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 980387c32..72f0faa79 100644
--- a/bot/helper/mirror_leech_utils/download_utils/telegram_download.py
+++ b/bot/helper/mirror_leech_utils/download_utils/telegram_download.py
@@ -3,13 +3,8 @@
from pyrogram.errors import FloodPremiumWait, FloodWait
-from bot import (
- LOGGER,
- bot,
- task_dict,
- task_dict_lock,
- user,
-)
+from bot import LOGGER, task_dict, task_dict_lock
+from bot.core.aeon_client import TgClient
from bot.helper.ext_utils.task_manager import (
check_running_tasks,
stop_duplicate_check,
@@ -56,15 +51,15 @@ async def _on_download_start(self, file_id, from_queue):
LOGGER.info(f"Download from Telegram: {self._listener.name}")
else:
LOGGER.info(
- f"Start Queued Download from Telegram: {self._listener.name}",
+ f"Start Queued Download from Telegram: {self._listener.name}"
)
async def _on_download_progress(self, current, _):
if self._listener.is_cancelled:
if self.session == "user":
- user.stop_transmission()
+ TgClient.user.stop_transmission()
else:
- bot.stop_transmission()
+ TgClient.bot.stop_transmission()
self._processed_bytes = current
async def _on_download_error(self, error):
@@ -85,11 +80,12 @@ async def _download(self, message, path):
progress=self._on_download_progress,
)
if self._listener.is_cancelled:
- await self._on_download_error("Cancelled by user!")
return
except (FloodWait, FloodPremiumWait) as f:
LOGGER.warning(str(f))
await sleep(f.value)
+ await self._download(message, path)
+ return
except Exception as e:
LOGGER.error(str(e))
await self._on_download_error(str(e))
@@ -107,7 +103,7 @@ async def add_download(self, message, path, session):
and self._listener.is_super_chat
):
self.session = "user"
- message = await user.get_messages(
+ message = await TgClient.user.get_messages(
chat_id=message.chat.id,
message_ids=message.id,
)
@@ -178,3 +174,4 @@ async def cancel_task(self):
LOGGER.info(
f"Cancelling download on user request: name: {self._listener.name} id: {self._id}",
)
+ await self._on_download_error("Cancelled by user!")
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 a9cf6c43a..2ccbe9efb 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
@@ -15,9 +15,7 @@
stop_duplicate_check,
)
from bot.helper.mirror_leech_utils.status_utils.queue_status import QueueStatus
-from bot.helper.mirror_leech_utils.status_utils.yt_dlp_status import (
- YtDlpStatus,
-)
+from bot.helper.mirror_leech_utils.status_utils.yt_dlp_status import YtDlpStatus
from bot.helper.telegram_helper.message_utils import send_status_message
LOGGER = getLogger(__name__)
@@ -31,7 +29,10 @@ def __init__(self, obj, 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)
@@ -127,9 +128,7 @@ def _on_download_progress(self, d):
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()
@@ -367,7 +366,7 @@ def _set_options(self, options):
elif value.lower() == "false":
value = False
elif value.startswith(("{", "[", "(")) and value.endswith(
- ("}", "]", ")"),
+ ("}", "]", ")")
):
value = eval(value)
diff --git a/bot/helper/mirror_leech_utils/gdrive_utils/__init__.py b/bot/helper/mirror_leech_utils/gdrive_utils/__init__.py
index e69de29bb..d3f5a12fa 100644
--- a/bot/helper/mirror_leech_utils/gdrive_utils/__init__.py
+++ b/bot/helper/mirror_leech_utils/gdrive_utils/__init__.py
@@ -0,0 +1 @@
+
diff --git a/bot/helper/mirror_leech_utils/gdrive_utils/clone.py b/bot/helper/mirror_leech_utils/gdrive_utils/clone.py
index eda9539c4..45e0b4e48 100644
--- a/bot/helper/mirror_leech_utils/gdrive_utils/clone.py
+++ b/bot/helper/mirror_leech_utils/gdrive_utils/clone.py
@@ -36,8 +36,10 @@ def user_setting(self):
self.listener.up_dest = self.listener.up_dest.replace("tp:", "", 1)
self.use_sa = False
elif self.listener.up_dest.startswith(
+ "sa:"
+ ) or self.listener.link.startswith(
"sa:",
- ) or self.listener.link.startswith("sa:"):
+ ):
self.listener.up_dest = self.listener.up_dest.replace("sa:", "", 1)
self.use_sa = True
@@ -60,8 +62,7 @@ def clone(self):
mime_type = meta.get("mimeType")
if mime_type == self.G_DRIVE_DIR_MIME_TYPE:
dir_id = self.create_directory(
- meta.get("name"),
- self.listener.up_dest,
+ meta.get("name"), self.listener.up_dest
)
self._clone_folder(meta.get("name"), meta.get("id"), dir_id)
durl = self.G_DRIVE_DIR_BASE_DOWNLOAD_URL.format(dir_id)
diff --git a/bot/helper/mirror_leech_utils/gdrive_utils/download.py b/bot/helper/mirror_leech_utils/gdrive_utils/download.py
index edf045d69..cbc2560c8 100644
--- a/bot/helper/mirror_leech_utils/gdrive_utils/download.py
+++ b/bot/helper/mirror_leech_utils/gdrive_utils/download.py
@@ -89,7 +89,7 @@ def _download_folder(self, folder_id, path, folder_name):
elif not ospath.isfile(
f"{path}{filename}",
) and not filename.lower().endswith(
- tuple(self.listener.extension_filter),
+ tuple(self.listener.extension_filter)
):
self._download_file(file_id, path, filename, mime_type)
if self.listener.is_cancelled:
diff --git a/bot/helper/mirror_leech_utils/gdrive_utils/helper.py b/bot/helper/mirror_leech_utils/gdrive_utils/helper.py
index e3e47e0f6..61a51348c 100644
--- a/bot/helper/mirror_leech_utils/gdrive_utils/helper.py
+++ b/bot/helper/mirror_leech_utils/gdrive_utils/helper.py
@@ -17,7 +17,7 @@
wait_exponential,
)
-from bot import config_dict
+from bot.core.config_manager import Config
from bot.helper.ext_utils.links_utils import is_gdrive_id
LOGGER = getLogger(__name__)
@@ -50,13 +50,13 @@ def __init__(self):
self.total_time = 0
self.status = None
self.update_interval = 3
- self.use_sa = config_dict["USE_SERVICE_ACCOUNTS"]
+ self.use_sa = Config.USE_SERVICE_ACCOUNTS
@property
def speed(self):
try:
return self.proc_bytes / self.total_time
- except Exception:
+ except:
return 0
@property
@@ -82,7 +82,7 @@ def authorize(self):
self.sa_number = len(json_files)
self.sa_index = randrange(self.sa_number)
LOGGER.info(
- f"Authorizing with {json_files[self.sa_index]} service account",
+ f"Authorizing with {json_files[self.sa_index]} service account"
)
credentials = service_account.Credentials.from_service_account_file(
f"accounts/{json_files[self.sa_index]}",
@@ -217,10 +217,10 @@ def create_directory(self, directory_name, dest_id):
.execute()
)
file_id = file.get("id")
- if not config_dict["IS_TEAM_DRIVE"]:
+ if not Config.IS_TEAM_DRIVE:
self.set_permission(file_id)
LOGGER.info(
- f'Created G-Drive Folder:\nName: {file.get("name")}\nID: {file_id}',
+ f'Created G-Drive Folder:\nName: {file.get("name")}\nID: {file_id}'
)
return file_id
diff --git a/bot/helper/mirror_leech_utils/gdrive_utils/list.py b/bot/helper/mirror_leech_utils/gdrive_utils/list.py
index e047bf8ec..8226b34cc 100644
--- a/bot/helper/mirror_leech_utils/gdrive_utils/list.py
+++ b/bot/helper/mirror_leech_utils/gdrive_utils/list.py
@@ -9,9 +9,9 @@
from pyrogram.handlers import CallbackQueryHandler
from tenacity import RetryError
-from bot import config_dict
+from bot.core.config_manager import Config
from bot.helper.ext_utils.bot_utils import new_task, update_user_ldata
-from bot.helper.ext_utils.db_handler import Database
+from bot.helper.ext_utils.db_handler import database
from bot.helper.ext_utils.status_utils import (
get_readable_file_size,
get_readable_time,
@@ -94,8 +94,8 @@ async def id_updates(_, query, obj):
if id_ != obj.listener.user_dict.get("gdrive_id"):
update_user_ldata(obj.listener.user_id, "gdrive_id", id_)
await obj.get_items_buttons()
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(obj.listener.user_id)
+ if Config.DATABASE_URL:
+ await database.update_user_data(obj.listener.user_id)
elif data[1] == "owner":
obj.token_path = "token.pickle"
obj.use_sa = False
@@ -150,7 +150,7 @@ async def _event_handler(self):
)
try:
await wait_for(self.event.wait(), timeout=self._timeout)
- except Exception:
+ except:
self.id = "Timed Out. Task has been cancelled!"
self.listener.is_cancelled = True
self.event.set()
@@ -161,9 +161,7 @@ async def _send_list_message(self, msg, button):
if not self.listener.is_cancelled:
if self._reply_to is None:
self._reply_to = await send_message(
- self.listener.message,
- msg,
- button,
+ self.listener.message, msg, button
)
else:
await edit_message(self._reply_to, msg, button)
@@ -200,9 +198,7 @@ async def get_items_buttons(self):
buttons.data_button("Files", "gdq itype files", position="footer")
else:
buttons.data_button(
- "Folders",
- "gdq itype folders",
- position="footer",
+ "Folders", "gdq itype folders", position="footer"
)
if self.list_status == "gdu" or len(self.items_list) > 0:
buttons.data_button("Choose Current Path", "gdq cur", position="footer")
@@ -222,9 +218,7 @@ async def get_items_buttons(self):
else "\nTransfer Type: Upload"
)
if self.list_status == "gdu":
- default_id = (
- self.listener.user_dict.get("gdrive_id") or config_dict["GDRIVE_ID"]
- )
+ default_id = self.listener.user_dict.get("gdrive_id") or Config.GDRIVE_ID
msg += f"\nDefault Gdrive ID: {default_id}" if default_id else ""
msg += f"\n\nItems: {items_no}"
if items_no > LIST_LIMIT:
@@ -245,22 +239,22 @@ async def get_items(self, itype=""):
try:
files = self.get_files_by_folder_id(self.id, self.item_type)
if self.listener.is_cancelled:
- return None
+ return
except Exception as err:
if isinstance(err, RetryError):
LOGGER.info(f"Total Attempts: {err.last_attempt.attempt_number}")
err = err.last_attempt.exception()
self.id = str(err).replace(">", "").replace("<", "")
self.event.set()
- return None
+ return
if len(files) == 0 and itype != self.item_type and self.list_status == "gdd":
itype = "folders" if self.item_type == "files" else "files"
self.item_type = itype
- return await self.get_items(itype)
- self.items_list = natsorted(files)
- self.iter_start = 0
- await self.get_items_buttons()
- return None
+ await self.get_items(itype)
+ else:
+ self.items_list = natsorted(files)
+ self.iter_start = 0
+ await self.get_items_buttons()
async def list_drives(self):
self.service = self.authorize()
diff --git a/bot/helper/mirror_leech_utils/gdrive_utils/search.py b/bot/helper/mirror_leech_utils/gdrive_utils/search.py
index b659609b8..673139a5d 100644
--- a/bot/helper/mirror_leech_utils/gdrive_utils/search.py
+++ b/bot/helper/mirror_leech_utils/gdrive_utils/search.py
@@ -9,11 +9,7 @@
class GoogleDriveSearch(GoogleDriveHelper):
def __init__(
- self,
- stop_dup=False,
- no_multi=False,
- is_recursive=True,
- item_type="",
+ self, stop_dup=False, no_multi=False, is_recursive=True, item_type=""
):
super().__init__()
self._stop_dup = stop_dup
diff --git a/bot/helper/mirror_leech_utils/gdrive_utils/upload.py b/bot/helper/mirror_leech_utils/gdrive_utils/upload.py
index ac2453a18..064859a8f 100644
--- a/bot/helper/mirror_leech_utils/gdrive_utils/upload.py
+++ b/bot/helper/mirror_leech_utils/gdrive_utils/upload.py
@@ -13,7 +13,7 @@
wait_exponential,
)
-from bot import config_dict
+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
from bot.helper.mirror_leech_utils.gdrive_utils.helper import GoogleDriveHelper
@@ -50,7 +50,7 @@ def upload(self, unwanted_files, ft_delete):
try:
if ospath.isfile(self._path):
if self._path.lower().endswith(
- tuple(self.listener.extension_filter),
+ tuple(self.listener.extension_filter)
):
raise Exception(
"This file extension is excluded by extension filter!",
@@ -76,10 +76,7 @@ def upload(self, unwanted_files, ft_delete):
self.listener.up_dest,
)
result = self._upload_dir(
- self._path,
- dir_id,
- unwanted_files,
- ft_delete,
+ self._path, dir_id, unwanted_files, ft_delete
)
if result is None:
raise Exception("Upload has been manually cancelled!")
@@ -133,7 +130,9 @@ def _upload_dir(self, input_directory, dest_id, unwanted_files, ft_delete):
self.total_folders += 1
elif (
current_file_name not in unwanted_files
- and not item.lower().endswith(tuple(self.listener.extension_filter))
+ and not item.lower().endswith(
+ tuple(self.listener.extension_filter),
+ )
):
mime_type = get_mime_type(current_file_name)
file_name = current_file_name.split("/")[-1]
@@ -179,9 +178,7 @@ def _upload_file(
if ospath.getsize(file_path) == 0:
media_body = MediaFileUpload(
- file_path,
- mimetype=mime_type,
- resumable=False,
+ file_path, mimetype=mime_type, resumable=False
)
response = (
self.service.files()
@@ -192,7 +189,7 @@ def _upload_file(
)
.execute()
)
- if not config_dict["IS_TEAM_DRIVE"]:
+ if not Config.IS_TEAM_DRIVE:
self.set_permission(response["id"])
drive_file = (
@@ -259,7 +256,7 @@ def _upload_file(
remove(file_path)
self.file_processed_bytes = 0
# Insert new permissions
- if not config_dict["IS_TEAM_DRIVE"]:
+ if not Config.IS_TEAM_DRIVE:
self.set_permission(response["id"])
# Define file instance and get url for download
if not in_dir:
diff --git a/bot/helper/mirror_leech_utils/rclone_utils/list.py b/bot/helper/mirror_leech_utils/rclone_utils/list.py
index 3fec40e4e..ab1627832 100644
--- a/bot/helper/mirror_leech_utils/rclone_utils/list.py
+++ b/bot/helper/mirror_leech_utils/rclone_utils/list.py
@@ -9,9 +9,10 @@
from pyrogram.filters import regex, user
from pyrogram.handlers import CallbackQueryHandler
-from bot import LOGGER, config_dict
+from bot import LOGGER
+from bot.core.config_manager import Config
from bot.helper.ext_utils.bot_utils import cmd_exec, new_task, update_user_ldata
-from bot.helper.ext_utils.db_handler import Database
+from bot.helper.ext_utils.db_handler import database
from bot.helper.ext_utils.status_utils import (
get_readable_file_size,
get_readable_time,
@@ -119,8 +120,8 @@ async def path_updates(_, query, obj):
if path != obj.listener.user_dict.get("rclone_path"):
update_user_ldata(obj.listener.user_id, "rclone_path", path)
await obj.get_path_buttons()
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(obj.listener.user_id)
+ if Config.DATABASE_URL:
+ await database.update_user_data(obj.listener.user_id)
elif data[1] == "owner":
obj.config_path = "rclone.conf"
obj.path = ""
@@ -168,7 +169,7 @@ async def _event_handler(self):
)
try:
await wait_for(self.event.wait(), timeout=self._timeout)
- except Exception:
+ except:
self.path = ""
self.remote = "Timed Out. Task has been cancelled!"
self.listener.is_cancelled = True
@@ -180,9 +181,7 @@ async def _send_list_message(self, msg, button):
if not self.listener.is_cancelled:
if self._reply_to is None:
self._reply_to = await send_message(
- self.listener.message,
- msg,
- button,
+ self.listener.message, msg, button
)
else:
await edit_message(self._reply_to, msg, button)
@@ -258,7 +257,7 @@ async def get_path_buttons(self):
else "\nTransfer Type: Upload"
)
if self.list_status == "rcu":
- default_path = config_dict["RCLONE_PATH"]
+ default_path = Config.RCLONE_PATH
msg += f"\nDefault Rclone Path: {default_path}" if default_path else ""
msg += f"\n\nItems: {items_no}"
if items_no > LIST_LIMIT:
@@ -276,7 +275,7 @@ async def get_path(self, itype=""):
elif self.list_status == "rcu":
self.item_type = "--dirs-only"
cmd = [
- "xone",
+ "rclone",
"lsjson",
self.item_type,
"--fast-list",
@@ -285,11 +284,34 @@ async def get_path(self, itype=""):
"--config",
self.config_path,
f"{self.remote}{self.path}",
+ "--log-systemd",
+ "--log-file",
+ "rlog.txt",
+ "--log-level",
+ "ERROR",
]
if self.listener.is_cancelled:
- return None
+ return
res, err, code = await cmd_exec(cmd)
- if code not in [0, -9]:
+ if code in [0, -9]:
+ result = loads(res)
+ if (
+ len(result) == 0
+ and itype != self.item_type
+ and self.list_status == "rcd"
+ ):
+ itype = (
+ "--dirs-only"
+ if self.item_type == "--files-only"
+ else "--files-only"
+ )
+ self.item_type = itype
+ await self.get_path(itype)
+ else:
+ self.path_list = sorted(result, key=lambda x: x["Path"])
+ self.iter_start = 0
+ await self.get_path_buttons()
+ else:
if not err:
err = "Use /shell cat rlog.txt
to see more information"
LOGGER.error(
@@ -298,22 +320,6 @@ async def get_path(self, itype=""):
self.remote = err[:4000]
self.path = ""
self.event.set()
- return None
- result = loads(res)
- if (
- len(result) == 0
- and itype != self.item_type
- and self.list_status == "rcd"
- ):
- itype = (
- "--dirs-only" if self.item_type == "--files-only" else "--files-only"
- )
- self.item_type = itype
- return await self.get_path(itype)
- self.path_list = sorted(result, key=lambda x: x["Path"])
- self.iter_start = 0
- await self.get_path_buttons()
- return None
async def list_remotes(self):
config = RawConfigParser()
diff --git a/bot/helper/mirror_leech_utils/rclone_utils/serve.py b/bot/helper/mirror_leech_utils/rclone_utils/serve.py
new file mode 100644
index 000000000..05d7e7d53
--- /dev/null
+++ b/bot/helper/mirror_leech_utils/rclone_utils/serve.py
@@ -0,0 +1,63 @@
+from asyncio import create_subprocess_exec
+from configparser import RawConfigParser
+
+from aiofiles import open as aiopen
+from aiofiles.os import path as aiopath
+
+from bot.core.config_manager import Config
+
+RcloneServe = []
+
+
+async def rclone_serve_booter():
+ if not Config.RCLONE_SERVE_URL or not await aiopath.exists("rclone.conf"):
+ if RcloneServe:
+ try:
+ RcloneServe[0].kill()
+ RcloneServe.clear()
+ except:
+ pass
+ return
+ config = RawConfigParser()
+ async with aiopen("rclone.conf") as f:
+ contents = await f.read()
+ config.read_string(contents)
+ if not config.has_section("combine"):
+ upstreams = " ".join(f"{remote}={remote}:" for remote in config.sections())
+ config.add_section("combine")
+ config.set("combine", "type", "combine")
+ config.set("combine", "upstreams", upstreams)
+ with open("rclone.conf", "w") as f:
+ config.write(f, space_around_delimiters=False)
+ if RcloneServe:
+ try:
+ RcloneServe[0].kill()
+ RcloneServe.clear()
+ except:
+ pass
+ cmd = [
+ "rclone",
+ "serve",
+ "http",
+ "--config",
+ "rclone.conf",
+ "--no-modtime",
+ "combine:",
+ "--addr",
+ f":{Config.RCLONE_SERVE_PORT}",
+ "--vfs-cache-mode",
+ "full",
+ "--vfs-cache-max-age",
+ "1m0s",
+ "--buffer-size",
+ "64M",
+ "--log-systemd",
+ "--log-file",
+ "rlog.txt",
+ "--log-level",
+ "ERROR",
+ ]
+ if (user := Config.RCLONE_SERVE_USER) and (pswd := Config.RCLONE_SERVE_PASS):
+ cmd.extend(("--user", user, "--pass", pswd))
+ rcs = await create_subprocess_exec(*cmd)
+ RcloneServe.append(rcs)
diff --git a/bot/helper/mirror_leech_utils/rclone_utils/transfer.py b/bot/helper/mirror_leech_utils/rclone_utils/transfer.py
index c7b922090..3c73fa557 100644
--- a/bot/helper/mirror_leech_utils/rclone_utils/transfer.py
+++ b/bot/helper/mirror_leech_utils/rclone_utils/transfer.py
@@ -11,7 +11,7 @@
from aiofiles.os import listdir, makedirs
from aiofiles.os import path as aiopath
-from bot import config_dict
+from bot.core.config_manager import Config
from bot.helper.ext_utils.bot_utils import cmd_exec, sync_to_async
from bot.helper.ext_utils.files_utils import (
clean_unwanted,
@@ -36,7 +36,7 @@ def __init__(self, listener):
self._sa_count = 1
self._sa_index = 0
self._sa_number = 0
- self._use_service_accounts = config_dict["USE_SERVICE_ACCOUNTS"]
+ self._use_service_accounts = Config.USE_SERVICE_ACCOUNTS
self.rclone_select = False
@property
@@ -63,7 +63,7 @@ async def _progress(self):
while not (self._proc is None or self._listener.is_cancelled):
try:
data = (await self._proc.stdout.readline()).decode()
- except Exception:
+ except:
continue
if not data:
break
@@ -192,7 +192,7 @@ async def download(self, remote, config_path, path):
if (
remote_type == "drive"
- and not config_dict["RCLONE_FLAGS"]
+ and not Config.RCLONE_FLAGS
and not self._listener.rc_flags
):
cmd.append("--drive-acknowledge-abuse")
@@ -214,7 +214,7 @@ async def _get_gdrive_link(self, config_path, remote, rc_path, mime_type):
destination = epath
cmd = [
- "xone",
+ "rclone",
"lsjson",
"--fast-list",
"--no-mimetype",
@@ -222,6 +222,11 @@ async def _get_gdrive_link(self, config_path, remote, rc_path, mime_type):
"--config",
config_path,
epath,
+ "--log-systemd",
+ "--log-file",
+ "rlog.txt",
+ "--log-level",
+ "ERROR",
]
res, err, code = await cmd_exec(cmd)
@@ -352,11 +357,11 @@ async def upload(self, path, unwanted_files, ft_delete):
)
if (
remote_type == "drive"
- and not config_dict["RCLONE_FLAGS"]
+ and not Config.RCLONE_FLAGS
and not self._listener.rc_flags
):
cmd.extend(
- ("--drive-chunk-size", "128M", "--drive-upload-cutoff", "128M"),
+ ("--drive-chunk-size", "128M", "--drive-upload-cutoff", "128M")
)
result = await self._start_upload(cmd, remote_type)
@@ -378,7 +383,18 @@ async def upload(self, path, unwanted_files, ft_delete):
else:
destination = f"{oremote}:{self._listener.name}"
- cmd = ["xone", "link", "--config", oconfig_path, destination]
+ cmd = [
+ "rclone",
+ "link",
+ "--config",
+ oconfig_path,
+ destination,
+ "--log-systemd",
+ "--log-file",
+ "rlog.txt",
+ "--log-level",
+ "ERROR",
+ ]
res, err, code = await cmd_exec(cmd)
if code == 0:
@@ -387,7 +403,7 @@ async def upload(self, path, unwanted_files, ft_delete):
if not err:
err = "Use /shell cat rlog.txt
to see more information"
LOGGER.error(
- f"while getting link. Path: {destination} | Stderr: {err}",
+ f"while getting link. Path: {destination} | Stderr: {err}"
)
link = ""
if self._listener.is_cancelled:
@@ -427,7 +443,7 @@ async def clone(self, config_path, src_remote, src_path, mime_type, method):
destination,
method,
)
- if not self._listener.rc_flags and not config_dict["RCLONE_FLAGS"]:
+ if not self._listener.rc_flags and not Config.RCLONE_FLAGS:
if src_remote_type == "drive" and dst_remote_type != "drive":
cmd.append("--drive-acknowledge-abuse")
elif src_remote_type == "drive":
@@ -464,7 +480,18 @@ async def clone(self, config_path, src_remote, src_path, mime_type, method):
f"/{self._listener.name}" if dst_path else self._listener.name
)
- cmd = ["xone", "link", "--config", config_path, destination]
+ cmd = [
+ "rclone",
+ "link",
+ "--config",
+ config_path,
+ destination,
+ "--log-systemd",
+ "--log-file",
+ "rlog.txt",
+ "--log-level",
+ "ERROR",
+ ]
res, err, code = await cmd_exec(cmd)
if self._listener.is_cancelled:
@@ -475,7 +502,9 @@ async def clone(self, config_path, src_remote, src_path, mime_type, method):
if code != -9:
if not err:
err = "Use /shell cat rlog.txt
to see more information"
- LOGGER.error(f"while getting link. Path: {destination} | Stderr: {err}")
+ LOGGER.error(
+ f"while getting link. Path: {destination} | Stderr: {err}",
+ )
return None, destination
return None
@@ -495,7 +524,7 @@ def _get_updated_command(
else:
ext = "*.{" + ",".join(self._listener.extension_filter) + "}"
cmd = [
- "xone",
+ "rclone",
method,
"--fast-list",
"--config",
@@ -509,16 +538,17 @@ def _get_updated_command(
"--low-level-retries",
"1",
"-M",
+ "--log-systemd",
"--log-file",
"rlog.txt",
"--log-level",
- "DEBUG",
+ "ERROR",
]
if self.rclone_select:
cmd.extend(("--files-from", self._listener.link))
else:
cmd.extend(("--exclude", ext))
- if rcflags := self._listener.rc_flags or config_dict["RCLONE_FLAGS"]:
+ if rcflags := self._listener.rc_flags or Config.RCLONE_FLAGS:
rcflags = rcflags.split("|")
for flag in rcflags:
if ":" in flag:
diff --git a/bot/helper/mirror_leech_utils/status_utils/aria2_status.py b/bot/helper/mirror_leech_utils/status_utils/aria2_status.py
index e32e20893..dd968420d 100644
--- a/bot/helper/mirror_leech_utils/status_utils/aria2_status.py
+++ b/bot/helper/mirror_leech_utils/status_utils/aria2_status.py
@@ -48,7 +48,7 @@ def size(self):
return self._download.total_length_string()
def eta(self):
- return get_readable_time(int(self._download.eta.total_seconds()))
+ return self._download.eta_string()
def status(self):
self.update()
@@ -95,10 +95,7 @@ async def cancel_task(self):
f"Seeding stopped with Ratio: {self.ratio()} and Time: {self.seeding_time()}",
)
await sync_to_async(
- aria2.remove,
- [self._download],
- force=True,
- files=True,
+ aria2.remove, [self._download], force=True, files=True
)
elif downloads := self._download.followed_by:
LOGGER.info(f"Cancelling Download: {self.name()}")
@@ -114,8 +111,5 @@ async def cancel_task(self):
msg = "Download stopped by user!"
await self.listener.on_download_error(msg)
await sync_to_async(
- aria2.remove,
- [self._download],
- force=True,
- files=True,
+ aria2.remove, [self._download], force=True, files=True
)
diff --git a/bot/helper/mirror_leech_utils/status_utils/direct_status.py b/bot/helper/mirror_leech_utils/status_utils/direct_status.py
index 8a11d63b2..33aff3021 100644
--- a/bot/helper/mirror_leech_utils/status_utils/direct_status.py
+++ b/bot/helper/mirror_leech_utils/status_utils/direct_status.py
@@ -17,7 +17,7 @@ def gid(self):
def progress_raw(self):
try:
return self._obj.processed_bytes / self.listener.size * 100
- except Exception:
+ except:
return 0
def progress(self):
@@ -38,7 +38,7 @@ def eta(self):
self.listener.size - self._obj.processed_bytes
) / self._obj.speed
return get_readable_time(seconds)
- except Exception:
+ except:
return "-"
def status(self):
diff --git a/bot/helper/mirror_leech_utils/status_utils/gdrive_status.py b/bot/helper/mirror_leech_utils/status_utils/gdrive_status.py
index 87c03a67c..03bb1bca2 100644
--- a/bot/helper/mirror_leech_utils/status_utils/gdrive_status.py
+++ b/bot/helper/mirror_leech_utils/status_utils/gdrive_status.py
@@ -35,7 +35,7 @@ def gid(self) -> str:
def progress_raw(self):
try:
return self._obj.processed_bytes / self._size * 100
- except Exception:
+ except:
return 0
def progress(self):
@@ -48,7 +48,7 @@ def eta(self):
try:
seconds = (self._size - self._obj.processed_bytes) / self._obj.speed
return get_readable_time(seconds)
- except Exception:
+ except:
return "-"
def task(self):
diff --git a/bot/helper/mirror_leech_utils/status_utils/sevenz_status.py b/bot/helper/mirror_leech_utils/status_utils/sevenz_status.py
index e2e92e9bf..1ef42f41d 100644
--- a/bot/helper/mirror_leech_utils/status_utils/sevenz_status.py
+++ b/bot/helper/mirror_leech_utils/status_utils/sevenz_status.py
@@ -28,7 +28,7 @@ async def progress_raw(self):
await self.processed_raw()
try:
return self._proccessed_bytes / self._size * 100
- except Exception:
+ except:
return 0
async def progress(self):
@@ -47,7 +47,7 @@ def eta(self):
try:
seconds = (self._size - self._proccessed_bytes) / self.speed_raw()
return get_readable_time(seconds)
- except Exception:
+ except:
return "-"
def status(self):
diff --git a/bot/helper/mirror_leech_utils/status_utils/telegram_status.py b/bot/helper/mirror_leech_utils/status_utils/telegram_status.py
index 4ee7c22c2..4030ac64c 100644
--- a/bot/helper/mirror_leech_utils/status_utils/telegram_status.py
+++ b/bot/helper/mirror_leech_utils/status_utils/telegram_status.py
@@ -30,7 +30,7 @@ def name(self):
def progress(self):
try:
progress_raw = self._obj.processed_bytes / self._size * 100
- except Exception:
+ except:
progress_raw = 0
return f"{round(progress_raw, 2)}%"
@@ -41,7 +41,7 @@ def eta(self):
try:
seconds = (self._size - self._obj.processed_bytes) / self._obj.speed
return get_readable_time(seconds)
- except Exception:
+ except:
return "-"
def gid(self):
diff --git a/bot/helper/mirror_leech_utils/status_utils/yt_dlp_status.py b/bot/helper/mirror_leech_utils/status_utils/yt_dlp_status.py
index cc180e7b7..53fa52124 100644
--- a/bot/helper/mirror_leech_utils/status_utils/yt_dlp_status.py
+++ b/bot/helper/mirror_leech_utils/status_utils/yt_dlp_status.py
@@ -49,7 +49,7 @@ def eta(self):
self._obj.size - self._proccessed_bytes
) / self._obj.download_speed
return get_readable_time(seconds)
- except Exception:
+ except:
return "-"
def task(self):
diff --git a/bot/helper/mirror_leech_utils/telegram_uploader.py b/bot/helper/mirror_leech_utils/telegram_uploader.py
index 378673dfd..f82259f36 100644
--- a/bot/helper/mirror_leech_utils/telegram_uploader.py
+++ b/bot/helper/mirror_leech_utils/telegram_uploader.py
@@ -17,7 +17,7 @@
from aioshutil import copy, rmtree
from natsort import natsorted
from PIL import Image
-from pyrogram.errors import FloodPremiumWait, FloodWait, RPCError
+from pyrogram.errors import BadRequest, FloodPremiumWait, FloodWait, RPCError
from pyrogram.types import (
InputMediaDocument,
InputMediaPhoto,
@@ -31,7 +31,8 @@
wait_exponential,
)
-from bot import config_dict, user
+from bot.core.config_manager import Config
+from bot.core.aeon_client import TgClient
from bot.helper.ext_utils.bot_utils import sync_to_async
from bot.helper.ext_utils.files_utils import (
clean_unwanted,
@@ -66,14 +67,16 @@ def __init__(self, listener, path):
self._last_msg_in_group = False
self._up_path = ""
self._lprefix = ""
+ self._media_group = False
self._is_private = False
self._sent_msg = None
self._user_session = self._listener.user_transmission
+ self._error = ""
async def _upload_progress(self, current, _):
if self._listener.is_cancelled:
if self._user_session:
- user.stop_transmission()
+ TgClient.user.stop_transmission()
else:
self._listener.client.stop_transmission()
chunk_size = current - self._last_uploaded
@@ -81,8 +84,13 @@ async def _upload_progress(self, current, _):
self._processed_bytes += chunk_size
async def _user_settings(self):
+ self._media_group = self._listener.user_dict.get("media_group") or (
+ Config.MEDIA_GROUP
+ if "media_group" not in self._listener.user_dict
+ else False
+ )
self._lprefix = self._listener.user_dict.get("lprefix") or (
- config_dict["LEECH_FILENAME_PREFIX"]
+ Config.LEECH_FILENAME_PREFIX
if "lprefix" not in self._listener.user_dict
else ""
)
@@ -98,7 +106,7 @@ async def _msg_to_reply(self):
)
try:
if self._user_session:
- self._sent_msg = await user.send_message(
+ self._sent_msg = await TgClient.user.send_message(
chat_id=self._listener.up_dest,
text=msg,
disable_web_page_preview=True,
@@ -118,12 +126,12 @@ async def _msg_to_reply(self):
await self._listener.on_upload_error(str(e))
return False
elif self._user_session:
- self._sent_msg = await user.get_messages(
+ self._sent_msg = await TgClient.user.get_messages(
chat_id=self._listener.message.chat.id,
message_ids=self._listener.mid,
)
if self._sent_msg is None:
- self._sent_msg = await user.send_message(
+ 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,
@@ -158,8 +166,7 @@ async def _prepare_file(self, file_, dirpath, delete_file):
name = get_base_name(file_)
ext = file_.split(name, 1)[1]
elif match := re_match(
- r".+(?=\..+\.0*\d+$)|.+(?=\.part\d+\..+$)",
- file_,
+ r".+(?=\..+\.0*\d+$)|.+(?=\.part\d+\..+$)", file_
):
name = match.group(0)
ext = file_.split(name, 1)[1]
@@ -227,7 +234,7 @@ async def _send_media_group(self, subkey, key, msgs):
message_ids=msg[1],
)
else:
- msgs[index] = await user.get_messages(
+ msgs[index] = await TgClient.user.get_messages(
chat_id=msg[0],
message_ids=msg[1],
)
@@ -254,12 +261,13 @@ async def upload(self, o_files, ft_delete):
for dirpath, _, files in natsorted(await sync_to_async(walk, self._path)):
if dirpath.endswith("/yt-dlp-thumb"):
continue
- if dirpath.endswith("_sshots"):
+ if dirpath.endswith("_ss"):
await self._send_screenshots(dirpath, files)
await rmtree(dirpath, ignore_errors=True)
continue
for file_ in natsorted(files):
delete_file = False
+ self._error = ""
self._up_path = f_path = ospath.join(dirpath, file_)
if self._up_path in ft_delete:
delete_file = True
@@ -286,8 +294,7 @@ async def upload(self, o_files, ft_delete):
x for v in self._media_dict.values() for x in v
]
match = re_match(
- r".+(?=\.0*\d+$)|.+(?=\.part\d+\..+$)",
- f_path,
+ r".+(?=\.0*\d+$)|.+(?=\.part\d+\..+$)", f_path
)
if not match or (
match and match.group(0) not in group_lists
@@ -296,14 +303,15 @@ async def upload(self, o_files, ft_delete):
for subkey, msgs in list(value.items()):
if len(msgs) > 1:
await self._send_media_group(
- subkey,
- key,
- msgs,
+ subkey, key, msgs
)
- if self._listener.mixed_leech:
+ if (
+ self._listener.mixed_leech
+ and self._listener.user_transmission
+ ):
self._user_session = f_size > 2097152000
if self._user_session:
- self._sent_msg = await user.get_messages(
+ self._sent_msg = await TgClient.user.get_messages(
chat_id=self._sent_msg.chat.id,
message_ids=self._sent_msg.id,
)
@@ -333,6 +341,7 @@ async def upload(self, o_files, ft_delete):
)
err = err.last_attempt.exception()
LOGGER.error(f"{err}. Path: {self._up_path}")
+ self._error = str(err)
self._corrupted += 1
if self._listener.is_cancelled:
return
@@ -368,7 +377,7 @@ async def upload(self, o_files, ft_delete):
return
if self._total_files <= self._corrupted:
await self._listener.on_upload_error(
- "Files Corrupted or unable to upload. Check logs!",
+ f"Files Corrupted or unable to upload. {self._error or 'Check logs!'}"
)
return
LOGGER.info(f"Leech Completed: {self._listener.name}")
@@ -479,6 +488,28 @@ async def _upload_file(self, cap_mono, file, o_path, force_document=False):
progress=self._upload_progress,
)
+ if (
+ not self._listener.is_cancelled
+ and self._media_group
+ and (self._sent_msg.video or self._sent_msg.document)
+ ):
+ key = "documents" if self._sent_msg.document else "videos"
+ if match := re_match(r".+(?=\.0*\d+$)|.+(?=\.part\d+\..+$)", o_path):
+ pname = match.group(0)
+ if pname in self._media_dict[key]:
+ self._media_dict[key][pname].append(
+ [self._sent_msg.chat.id, self._sent_msg.id],
+ )
+ else:
+ self._media_dict[key][pname] = [
+ [self._sent_msg.chat.id, self._sent_msg.id],
+ ]
+ msgs = self._media_dict[key][pname]
+ if len(msgs) == 10:
+ await self._send_media_group(pname, key, msgs)
+ else:
+ self._last_msg_in_group = True
+
if (
self._thumb is None
and thumb is not None
@@ -504,7 +535,7 @@ async def _upload_file(self, cap_mono, file, o_path, force_document=False):
await remove(thumb)
err_type = "RPCError: " if isinstance(err, RPCError) else ""
LOGGER.error(f"{err_type}{err}. Path: {self._up_path}")
- if "Telegram says: [400" in str(err) and key != "documents":
+ if isinstance(err, BadRequest) and key != "documents":
LOGGER.error(f"Retrying As Document. Path: {self._up_path}")
return await self._upload_file(cap_mono, file, o_path, True)
raise err
@@ -513,7 +544,7 @@ async def _upload_file(self, cap_mono, file, o_path, force_document=False):
def speed(self):
try:
return self._processed_bytes / (time() - self._start_time)
- except Exception:
+ except:
return 0
@property
diff --git a/bot/helper/telegram_helper/bot_commands.py b/bot/helper/telegram_helper/bot_commands.py
index cc2251cc5..1a7442a29 100644
--- a/bot/helper/telegram_helper/bot_commands.py
+++ b/bot/helper/telegram_helper/bot_commands.py
@@ -1,41 +1,63 @@
-# ruff: noqa: N811
-from bot import CMD_SUFFIX as i
+from bot.core.config_manager import Config
class _BotCommands:
def __init__(self):
- self.StartCommand = f"start{i}"
- self.MirrorCommand = [f"mirror{i}", f"m{i}"]
- self.YtdlCommand = [f"ytdl{i}", f"y{i}"]
- self.LeechCommand = [f"leech{i}", f"l{i}"]
- self.YtdlLeechCommand = [f"ytdlleech{i}", f"yl{i}"]
- self.CloneCommand = f"clone{i}"
- self.CountCommand = f"count{i}"
- self.DeleteCommand = f"del{i}"
- self.CancelAllCommand = f"stopall{i}"
- self.ForceStartCommand = [f"forcestart{i}", f"fs{i}"]
- self.ListCommand = f"list{i}"
- self.StatusCommand = f"status{i}"
- self.UsersCommand = f"users{i}"
- self.AuthorizeCommand = f"authorize{i}"
- self.UnAuthorizeCommand = f"unauthorize{i}"
- self.AddSudoCommand = f"addsudo{i}"
- self.RmSudoCommand = f"rmsudo{i}"
- self.PingCommand = f"ping{i}"
- self.RestartCommand = f"restart{i}"
- self.StatsCommand = f"stats{i}"
- self.HelpCommand = f"help{i}"
- self.LogCommand = f"log{i}"
- self.ShellCommand = f"shell{i}"
- self.AExecCommand = f"aexec{i}"
- self.ExecCommand = f"exec{i}"
- self.ClearLocalsCommand = f"clearlocals{i}"
- self.BotSetCommand = [f"botsettings{i}", f"bs{i}"]
- self.UserSetCommand = [f"settings{i}", f"us{i}"]
- self.SelectCommand = f"sel{i}"
- self.SpeedCommand = f"speedtest{i}"
- self.MediaInfoCommand: str = f"mediainfo{i}"
- self.BroadcastCommand: list[str] = [f"broadcast{i}", "broadcastall"]
+ self.StartCommand = f"start{Config.CMD_SUFFIX}"
+ self.MirrorCommand = [f"mirror{Config.CMD_SUFFIX}", f"m{Config.CMD_SUFFIX}"]
+ self.QbMirrorCommand = [
+ f"qbmirror{Config.CMD_SUFFIX}",
+ f"qm{Config.CMD_SUFFIX}",
+ ]
+ self.YtdlCommand = [f"ytdl{Config.CMD_SUFFIX}", f"y{Config.CMD_SUFFIX}"]
+ self.LeechCommand = [f"leech{Config.CMD_SUFFIX}", f"l{Config.CMD_SUFFIX}"]
+ self.QbLeechCommand = [
+ f"qbleech{Config.CMD_SUFFIX}",
+ f"ql{Config.CMD_SUFFIX}",
+ ]
+ self.YtdlLeechCommand = [
+ f"ytdlleech{Config.CMD_SUFFIX}",
+ f"yl{Config.CMD_SUFFIX}",
+ ]
+ self.CloneCommand = f"clone{Config.CMD_SUFFIX}"
+ self.CountCommand = f"count{Config.CMD_SUFFIX}"
+ self.DeleteCommand = f"del{Config.CMD_SUFFIX}"
+ self.CancelTaskCommand = [
+ f"cancel{Config.CMD_SUFFIX}",
+ f"c{Config.CMD_SUFFIX}",
+ ]
+ self.CancelAllCommand = f"cancelall{Config.CMD_SUFFIX}"
+ self.ForceStartCommand = [
+ f"forcestart{Config.CMD_SUFFIX}",
+ f"fs{Config.CMD_SUFFIX}",
+ ]
+ self.ListCommand = f"list{Config.CMD_SUFFIX}"
+ self.SearchCommand = f"search{Config.CMD_SUFFIX}"
+ self.StatusCommand = f"status{Config.CMD_SUFFIX}"
+ self.UsersCommand = f"users{Config.CMD_SUFFIX}"
+ self.AuthorizeCommand = f"authorize{Config.CMD_SUFFIX}"
+ self.UnAuthorizeCommand = f"unauthorize{Config.CMD_SUFFIX}"
+ self.AddSudoCommand = f"addsudo{Config.CMD_SUFFIX}"
+ self.RmSudoCommand = f"rmsudo{Config.CMD_SUFFIX}"
+ self.PingCommand = f"ping{Config.CMD_SUFFIX}"
+ self.RestartCommand = f"restart{Config.CMD_SUFFIX}"
+ self.StatsCommand = f"stats{Config.CMD_SUFFIX}"
+ self.HelpCommand = f"help{Config.CMD_SUFFIX}"
+ self.LogCommand = f"log{Config.CMD_SUFFIX}"
+ self.ShellCommand = f"shell{Config.CMD_SUFFIX}"
+ self.AExecCommand = f"aexec{Config.CMD_SUFFIX}"
+ self.ExecCommand = f"exec{Config.CMD_SUFFIX}"
+ self.ClearLocalsCommand = f"clearlocals{Config.CMD_SUFFIX}"
+ self.BotSetCommand = [
+ f"bsetting{Config.CMD_SUFFIX}",
+ f"bs{Config.CMD_SUFFIX}",
+ ]
+ self.UserSetCommand = [
+ f"usetting{Config.CMD_SUFFIX}",
+ f"us{Config.CMD_SUFFIX}",
+ ]
+ self.SelectCommand = f"sel{Config.CMD_SUFFIX}"
+ self.RssCommand = f"rss{Config.CMD_SUFFIX}"
BotCommands = _BotCommands()
diff --git a/bot/helper/telegram_helper/filters.py b/bot/helper/telegram_helper/filters.py
index 21b949b66..de858b253 100644
--- a/bot/helper/telegram_helper/filters.py
+++ b/bot/helper/telegram_helper/filters.py
@@ -1,13 +1,14 @@
from pyrogram.filters import create
-from bot import OWNER_ID, user_data
+from bot import user_data
+from bot.core.config_manager import Config
class CustomFilters:
async def owner_filter(self, _, update):
user = update.from_user or update.sender_chat
uid = user.id
- return uid == OWNER_ID
+ return uid == Config.OWNER_ID
owner = create(owner_filter)
@@ -17,7 +18,7 @@ async def authorized_user(self, _, update):
chat_id = update.chat.id
thread_id = update.message_thread_id if update.is_topic_message else None
return bool(
- uid == OWNER_ID
+ uid == Config.OWNER_ID
or (
uid in user_data
and (
@@ -41,7 +42,8 @@ async def sudo_user(self, _, update):
user = update.from_user or update.sender_chat
uid = user.id
return bool(
- uid == OWNER_ID or (uid in user_data and user_data[uid].get("is_sudo")),
+ uid == Config.OWNER_ID
+ or (uid in user_data and user_data[uid].get("is_sudo")),
)
sudo = create(sudo_user)
diff --git a/bot/helper/telegram_helper/message_utils.py b/bot/helper/telegram_helper/message_utils.py
index 948a38f2f..fe73ea309 100644
--- a/bot/helper/telegram_helper/message_utils.py
+++ b/bot/helper/telegram_helper/message_utils.py
@@ -2,155 +2,91 @@
from re import match as re_match
from time import time
-from cachetools import TTLCache
-from pyrogram import Client, enums
-from pyrogram.errors import FloodWait, MessageEmpty, MessageNotModified
-from pyrogram.types import InputMediaPhoto
+from pyrogram.errors import FloodPremiumWait, FloodWait
-from bot import (
- LOGGER,
- TELEGRAM_API,
- TELEGRAM_HASH,
- bot,
- intervals,
- status_dict,
- task_dict_lock,
- user,
- user_data,
-)
+from bot import LOGGER, intervals, status_dict, task_dict_lock
+from bot.core.config_manager import Config
+from bot.core.aeon_client 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
-session_cache = TTLCache(maxsize=1000, ttl=36000)
-
-async def send_message(
- message,
- text,
- buttons=None,
- block=True,
- photo=None,
- markdown=False,
-):
- parse_mode = enums.ParseMode.MARKDOWN if markdown else enums.ParseMode.HTML
+async def send_message(message, text, buttons=None):
try:
- if isinstance(message, int):
- return await bot.send_message(
- chat_id=message,
- text=text,
- disable_web_page_preview=True,
- disable_notification=True,
- reply_markup=buttons,
- parse_mode=parse_mode,
- )
- if photo:
- return await message.reply_photo(
- photo=photo,
- reply_to_message_id=message.id,
- caption=text,
- reply_markup=buttons,
- disable_notification=True,
- parse_mode=parse_mode,
- )
return await message.reply(
text=text,
quote=True,
disable_web_page_preview=True,
disable_notification=True,
reply_markup=buttons,
- parse_mode=parse_mode,
)
except FloodWait as f:
LOGGER.warning(str(f))
- if block:
- await sleep(f.value * 1.2)
- return await send_message(message, text, buttons, block, photo, markdown)
- return str(f)
+ await sleep(f.value * 1.2)
+ return await send_message(message, text, buttons)
except Exception as e:
LOGGER.error(str(e))
return str(e)
-async def edit_message(
- message,
- text,
- buttons=None,
- block=True,
- photo=None,
- markdown=False,
-):
- parse_mode = enums.ParseMode.MARKDOWN if markdown else enums.ParseMode.HTML
+async def edit_message(message, text, buttons=None):
try:
- if message.media:
- if photo:
- return await message.edit_media(
- InputMediaPhoto(photo, text),
- reply_markup=buttons,
- parse_mode=parse_mode,
- )
- return await message.edit_caption(
- caption=text,
- reply_markup=buttons,
- parse_mode=parse_mode,
- )
- await message.edit(
+ return await message.edit(
text=text,
disable_web_page_preview=True,
reply_markup=buttons,
- parse_mode=parse_mode,
)
except FloodWait as f:
LOGGER.warning(str(f))
- if block:
- await sleep(f.value * 1.2)
- return await edit_message(message, text, buttons, block, photo, markdown)
- except (MessageNotModified, MessageEmpty):
- pass
+ await sleep(f.value * 1.2)
+ return await edit_message(message, text, buttons)
except Exception as e:
LOGGER.error(str(e))
return str(e)
-async def send_file(message, file, caption="", buttons=None):
+async def send_file(message, file, caption=""):
try:
return await message.reply_document(
document=file,
quote=True,
caption=caption,
disable_notification=True,
- reply_markup=buttons,
)
except FloodWait as f:
LOGGER.warning(str(f))
await sleep(f.value * 1.2)
- return await send_file(message, file, caption, buttons)
+ return await send_file(message, file, caption)
except Exception as e:
LOGGER.error(str(e))
return str(e)
-async def delete_message(message):
+async def send_rss(text, chat_id, thread_id):
try:
- await message.delete()
+ app = TgClient.user or TgClient.bot
+ return await app.send_message(
+ chat_id=chat_id,
+ text=text,
+ disable_web_page_preview=True,
+ message_thread_id=thread_id,
+ disable_notification=True,
+ )
+ except (FloodWait, FloodPremiumWait) as f:
+ LOGGER.warning(str(f))
+ await sleep(f.value * 1.2)
+ return await send_rss(text)
except Exception as e:
LOGGER.error(str(e))
+ return str(e)
-async def one_minute_del(message):
- await sleep(60)
- await delete_message(message)
-
-
-async def five_minute_del(message):
- await sleep(300)
- await delete_message(message)
-
-
-async def delete_links(message):
- if reply_to := message.reply_to_message:
- await delete_message(reply_to)
- await delete_message(message)
+async def delete_message(message):
+ try:
+ await message.delete()
+ except Exception as e:
+ LOGGER.error(str(e))
async def auto_delete_message(cmd_message=None, bot_message=None):
@@ -171,30 +107,9 @@ async def delete_status():
LOGGER.error(str(e))
-async def get_tg_link_message(link, user_id=""):
+async def get_tg_link_message(link):
message = None
links = []
- user_s = None
-
- if user_id:
- if user_id in session_cache:
- user_s = session_cache[user_id]
- else:
- user_dict = user_data.get(user_id, {})
- session_string = user_dict.get("session_string")
- if session_string:
- user_s = Client(
- f"session_{user_id}",
- TELEGRAM_API,
- TELEGRAM_HASH,
- session_string=session_string,
- no_updates=True,
- )
- await user_s.start()
- session_cache[user_id] = user_s
- else:
- user_s = user
-
if link.startswith("https://t.me/"):
private = False
msg = re_match(
@@ -207,16 +122,17 @@ async def get_tg_link_message(link, user_id=""):
r"tg:\/\/openmessage\?user_id=([0-9]+)&message_id=([0-9-]+)",
link,
)
- if not user:
+ if not TgClient.user:
raise TgLinkException(
- "USER_SESSION_STRING required for this private link!",
+ "USER_SESSION_STRING required for this private link!"
)
chat = msg[1]
msg_id = msg[2]
if "-" in msg_id:
- start_id, end_id = map(int, msg_id.split("-"))
- msg_id = start_id
+ start_id, end_id = msg_id.split("-")
+ msg_id = start_id = int(start_id)
+ end_id = int(end_id)
btw = end_id - start_id
if private:
link = link.split("&message_id=")[0]
@@ -238,30 +154,45 @@ async def get_tg_link_message(link, user_id=""):
if not private:
try:
- message = await bot.get_messages(chat_id=chat, message_ids=msg_id)
+ message = await TgClient.bot.get_messages(
+ chat_id=chat, message_ids=msg_id
+ )
if message.empty:
private = True
except Exception as e:
private = True
- if not user_s:
+ if not TgClient.user:
raise e
if not private:
- return (links, bot) if links else (message, bot)
- if user_s:
+ return (links, "bot") if links else (message, "bot")
+ if TgClient.user:
try:
- user_message = await user_s.get_messages(
+ user_message = await TgClient.user.get_messages(
chat_id=chat,
message_ids=msg_id,
)
except Exception as e:
- raise TgLinkException("We don't have access to this chat!") from e
+ raise TgLinkException(
+ f"You don't have access to this chat!. ERROR: {e}",
+ ) from e
if not user_message.empty:
- return (links, user_s) if links else (user_message, user_s)
+ return (links, "user") if links else (user_message, "user")
return None
raise TgLinkException("Private: Please report!")
+async def check_permission(client, chat, uploader_id, up_dest):
+ member = await chat.get_member(uploader_id)
+ if (
+ not member.privileges.can_manage_chat
+ or not member.privileges.can_delete_messages
+ ):
+ raise ValueError(
+ "You don't have enough privileges in this chat!",
+ )
+
+
async def update_status_message(sid, force=False):
if intervals["stopAll"]:
return
@@ -277,11 +208,13 @@ async def update_status_message(sid, force=False):
page_no = status_dict[sid]["page_no"]
status = status_dict[sid]["status"]
is_user = status_dict[sid]["is_user"]
+ page_step = status_dict[sid]["page_step"]
text, buttons = await get_readable_message(
sid,
is_user,
page_no,
status,
+ page_step,
)
if text is None:
del status_dict[sid]
@@ -289,24 +222,22 @@ async def update_status_message(sid, force=False):
obj.cancel()
del intervals["status"][sid]
return
- if text != status_dict[sid]["message"].text:
- message = await edit_message(
- status_dict[sid]["message"],
- text,
- buttons,
- block=False,
- )
- if isinstance(message, str):
- if message.startswith("Telegram says: [400"):
+ old_message = status_dict[sid]["message"]
+ if text != old_message.text:
+ message = await edit_message(old_message, text, buttons)
+ if isinstance(message, str):
+ if message.startswith("Telegram says: [40"):
+ async with task_dict_lock:
del status_dict[sid]
if obj := intervals["status"].get(sid):
obj.cancel()
del intervals["status"][sid]
- else:
- LOGGER.error(
- f"Status with id: {sid} haven't been updated. Error: {message}",
- )
- return
+ else:
+ LOGGER.error(
+ f"Status with id: {sid} haven't been updated. Error: {message}",
+ )
+ return
+ async with task_dict_lock:
status_dict[sid]["message"].text = text
status_dict[sid]["time"] = time()
@@ -314,17 +245,19 @@ async def update_status_message(sid, force=False):
async def send_status_message(msg, user_id=0):
if intervals["stopAll"]:
return
+ sid = user_id or msg.chat.id
+ is_user = bool(user_id)
async with task_dict_lock:
- sid = user_id or msg.chat.id
- is_user = bool(user_id)
- if sid in list(status_dict.keys()):
+ if sid in status_dict:
page_no = status_dict[sid]["page_no"]
status = status_dict[sid]["status"]
+ page_step = status_dict[sid]["page_step"]
text, buttons = await get_readable_message(
sid,
is_user,
page_no,
status,
+ page_step,
)
if text is None:
del status_dict[sid]
@@ -334,7 +267,7 @@ async def send_status_message(msg, user_id=0):
return
message = status_dict[sid]["message"]
await delete_message(message)
- message = await send_message(msg, text, buttons, block=False)
+ message = await send_message(msg, text, buttons)
if isinstance(message, str):
LOGGER.error(
f"Status with id: {sid} haven't been sent. Error: {message}",
@@ -346,7 +279,7 @@ async def send_status_message(msg, user_id=0):
text, buttons = await get_readable_message(sid, is_user)
if text is None:
return
- message = await send_message(msg, text, buttons, block=False)
+ message = await send_message(msg, text, buttons)
if isinstance(message, str):
LOGGER.error(
f"Status with id: {sid} haven't been sent. Error: {message}",
@@ -361,5 +294,9 @@ async def send_status_message(msg, user_id=0):
"status": "All",
"is_user": is_user,
}
- if not intervals["status"].get(sid) and not is_user:
- intervals["status"][sid] = SetInterval(1, update_status_message, sid)
+ if not intervals["status"].get(sid) and not is_user:
+ intervals["status"][sid] = SetInterval(
+ Config.STATUS_UPDATE_INTERVAL,
+ update_status_message,
+ sid,
+ )
diff --git a/bot/modules/__init__.py b/bot/modules/__init__.py
index d3f5a12fa..4eef0b41d 100644
--- a/bot/modules/__init__.py
+++ b/bot/modules/__init__.py
@@ -1 +1,80 @@
+from .bot_settings import edit_bot_settings, send_bot_settings
+from .cancel_task import cancel, cancel_all_buttons, cancel_all_update, cancel_multi
+from .chat_permission import add_sudo, authorize, remove_sudo, unauthorize
+from .clone import clone_node
+from .exec import aioexecute, clear, execute
+from .file_selector import confirm_selection, select
+from .force_start import remove_from_queue
+from .gd_count import count_node
+from .gd_delete import delete_file
+from .gd_search import gdrive_search, select_type
+from .help import arg_usage, bot_help
+from .mirror_leech import (
+ leech,
+ mirror,
+ qb_leech,
+ qb_mirror,
+)
+from .restart import restart_bot, restart_notification
+from .rss import get_rss_menu, rss_listener
+from .search import initiate_search_tools, torrent_search, torrent_search_update
+from .services import log, ping, start
+from .shell import run_shell
+from .stats import bot_stats, get_packages_version
+from .status import status_pages, task_status
+from .users_settings import (
+ edit_user_settings,
+ get_users_settings,
+ send_user_settings,
+)
+from .ytdlp import ytdl, ytdl_leech
+__all__ = [
+ "add_sudo",
+ "aioexecute",
+ "arg_usage",
+ "authorize",
+ "bot_help",
+ "bot_stats",
+ "cancel",
+ "cancel_all_buttons",
+ "cancel_all_update",
+ "cancel_multi",
+ "clear",
+ "clone_node",
+ "confirm_selection",
+ "count_node",
+ "delete_file",
+ "edit_bot_settings",
+ "edit_user_settings",
+ "execute",
+ "gdrive_search",
+ "get_packages_version",
+ "get_rss_menu",
+ "get_users_settings",
+ "initiate_search_tools",
+ "leech",
+ "log",
+ "mirror",
+ "ping",
+ "qb_leech",
+ "qb_mirror",
+ "remove_from_queue",
+ "remove_sudo",
+ "restart_bot",
+ "restart_notification",
+ "rss_listener",
+ "run_shell",
+ "select",
+ "select_type",
+ "send_bot_settings",
+ "send_user_settings",
+ "start",
+ "status_pages",
+ "task_status",
+ "torrent_search",
+ "torrent_search_update",
+ "unauthorize",
+ "ytdl",
+ "ytdl_leech",
+]
diff --git a/bot/modules/bot_settings.py b/bot/modules/bot_settings.py
index 769b1a8a0..9372d72a6 100644
--- a/bot/modules/bot_settings.py
+++ b/bot/modules/bot_settings.py
@@ -6,39 +6,41 @@
)
from functools import partial
from io import BytesIO
-from os import environ, getcwd
+from os import getcwd
from time import time
from aiofiles import open as aiopen
from aiofiles.os import path as aiopath
from aiofiles.os import remove, rename
from aioshutil import rmtree
-from dotenv import load_dotenv
-from pyrogram.filters import command, create, regex
-from pyrogram.handlers import CallbackQueryHandler, MessageHandler
+from pyrogram.filters import create
+from pyrogram.handlers import MessageHandler
from bot import (
- IS_PREMIUM_USER,
LOGGER,
- MAX_SPLIT_SIZE,
aria2,
aria2_options,
- bot,
- config_dict,
drives_ids,
drives_names,
- global_extension_filter,
+ extension_filter,
index_urls,
intervals,
+ jd_lock,
+ nzb_options,
+ qbit_options,
+ xnox_client,
+ sabnzbd_client,
task_dict,
- user_data,
)
+from bot.core.config_manager import Config
+from bot.core.aeon_client import TgClient
+from bot.core.startup import update_nzb_options, update_qb_options, update_variables
from bot.helper.ext_utils.bot_utils import SetInterval, new_task, sync_to_async
-from bot.helper.ext_utils.db_handler import Database
+from bot.helper.ext_utils.db_handler import database
+from bot.helper.ext_utils.jdownloader_booter import jdownloader
from bot.helper.ext_utils.task_manager import start_from_queued
-from bot.helper.telegram_helper.bot_commands import BotCommands
+from bot.helper.mirror_leech_utils.rclone_utils.serve import rclone_serve_booter
from bot.helper.telegram_helper.button_build import ButtonMaker
-from bot.helper.telegram_helper.filters import CustomFilters
from bot.helper.telegram_helper.message_utils import (
delete_message,
edit_message,
@@ -47,13 +49,20 @@
update_status_message,
)
+from .rss import add_job
+from .search import initiate_search_tools
+
start = 0
state = "view"
handler_dict = {}
DEFAULT_VALUES = {
- "LEECH_SPLIT_SIZE": MAX_SPLIT_SIZE,
- "UPSTREAM_BRANCH": "main",
- "DEFAULT_UPLOAD": "gd",
+ "DOWNLOAD_DIR": "/usr/src/app/downloads/",
+ "LEECH_SPLIT_SIZE": TgClient.MAX_SPLIT_SIZE,
+ "RSS_DELAY": 600,
+ "STATUS_UPDATE_INTERVAL": 15,
+ "SEARCH_LIMIT": 0,
+ "UPSTREAM_BRANCH": "master",
+ "DEFAULT_UPLOAD": "rc",
}
@@ -62,6 +71,10 @@ async def get_buttons(key=None, edit_type=None):
if key is None:
buttons.data_button("Config Variables", "botset var")
buttons.data_button("Private Files", "botset private")
+ buttons.data_button("Qbit Settings", "botset qbit")
+ buttons.data_button("Aria2c Settings", "botset aria")
+ buttons.data_button("Sabnzbd Settings", "botset nzb")
+ buttons.data_button("JDownloader Sync", "botset syncjd")
buttons.data_button("Close", "botset close")
msg = "Bot Settings:"
elif edit_type is not None:
@@ -72,19 +85,39 @@ async def get_buttons(key=None, edit_type=None):
buttons.data_button("Default", f"botset resetvar {key}")
buttons.data_button("Close", "botset close")
if key in [
- "SUDO_USERS",
"CMD_SUFFIX",
"OWNER_ID",
"USER_SESSION_STRING",
"TELEGRAM_HASH",
"TELEGRAM_API",
- "AUTHORIZED_CHATS",
"BOT_TOKEN",
+ "DOWNLOAD_DIR",
+ "SUDO_USERS",
+ "AUTHORIZED_CHATS",
]:
- msg += "Restart required for this edit to take effect!\n\n"
- msg += f"Send a valid value for {key}. Current value is '{config_dict[key]}'. Timeout: 60 sec"
+ msg += "Restart required for this edit to take effect! You will not see the changes in bot vars, the edit will be in database only!\n\n"
+ msg += f"Send a valid value for {key}. Current value is '{Config.get(key)}'. Timeout: 60 sec"
elif key == "var":
- for k in list(config_dict.keys())[start : 10 + start]:
+ conf_dict = Config.get_all()
+ for k in list(conf_dict.keys())[start : 10 + start]:
+ if (
+ key
+ in [
+ "CMD_SUFFIX",
+ "OWNER_ID",
+ "USER_SESSION_STRING",
+ "TELEGRAM_HASH",
+ "TELEGRAM_API",
+ "BOT_TOKEN",
+ "DOWNLOAD_DIR",
+ "SUDO_USERS",
+ "AUTHORIZED_CHATS",
+ ]
+ and not Config.DATABASE_URL
+ ):
+ continue
+ if k == "DATABASE_URL" and state != "view":
+ continue
buttons.data_button(k, f"botset botvar {k}")
if state == "view":
buttons.data_button("Edit", "botset edit var")
@@ -92,7 +125,7 @@ async def get_buttons(key=None, edit_type=None):
buttons.data_button("View", "botset view var")
buttons.data_button("Back", "botset back")
buttons.data_button("Close", "botset close")
- for x in range(0, len(config_dict), 10):
+ for x in range(0, len(conf_dict), 10):
buttons.data_button(
f"{int(x / 10)}",
f"botset start var {x}",
@@ -102,11 +135,10 @@ async def get_buttons(key=None, edit_type=None):
elif key == "private":
buttons.data_button("Back", "botset back")
buttons.data_button("Close", "botset close")
- msg = """Send private file: config.env, token.pickle, rclone.conf, accounts.zip, list_drives.txt, cookies.txt, .netrc or any other private file!
+ msg = """Send private file: config.py, token.pickle, rclone.conf, accounts.zip, list_drives.txt, cookies.txt, .netrc or any other private file!
To delete private file send only the file name as text message.
Note: Changing .netrc will not take effect for aria2c until restart.
Timeout: 60 sec"""
-
button = buttons.build_menu(1) if key is None else buttons.build_menu(2)
return msg, button
@@ -124,6 +156,21 @@ async def edit_variable(_, message, pre_message, key):
value = True
elif value.lower() == "false":
value = False
+ if key == "INCOMPLETE_TASK_NOTIFIER" and Config.DATABASE_URL:
+ await database.trunc_table("tasks")
+ elif key == "DOWNLOAD_DIR":
+ if not value.endswith("/"):
+ value += "/"
+ elif key == "STATUS_UPDATE_INTERVAL":
+ value = int(value)
+ if len(task_dict) != 0 and (st := intervals["status"]):
+ for cid, intvl in list(st.items()):
+ intvl.cancel()
+ intervals["status"][cid] = SetInterval(
+ value,
+ update_status_message,
+ cid,
+ )
elif key == "TORRENT_TIMEOUT":
value = int(value)
downloads = await sync_to_async(aria2.get_downloads)
@@ -139,14 +186,14 @@ async def edit_variable(_, message, pre_message, key):
LOGGER.error(e)
aria2_options["bt-stop-timeout"] = f"{value}"
elif key == "LEECH_SPLIT_SIZE":
- value = min(int(value), MAX_SPLIT_SIZE)
+ value = min(int(value), TgClient.MAX_SPLIT_SIZE)
elif key == "EXTENSION_FILTER":
fx = value.split()
- global_extension_filter.clear()
- global_extension_filter.extend(["aria2", "!qB"])
+ extension_filter.clear()
+ extension_filter.extend(["aria2", "!qB"])
for x in fx:
x = x.lstrip(".")
- global_extension_filter.append(x.strip().lower())
+ extension_filter.append(x.strip().lower())
elif key == "GDRIVE_ID":
if drives_names and drives_names[0] == "Main":
drives_ids[0] = value
@@ -161,15 +208,34 @@ async def edit_variable(_, message, pre_message, key):
value = int(value)
elif value.startswith("[") and value.endswith("]"):
value = eval(value)
- config_dict[key] = value
+ if key not in [
+ "CMD_SUFFIX",
+ "OWNER_ID",
+ "USER_SESSION_STRING",
+ "TELEGRAM_HASH",
+ "TELEGRAM_API",
+ "BOT_TOKEN",
+ "DOWNLOAD_DIR",
+ "SUDO_USERS",
+ "AUTHORIZED_CHATS",
+ ]:
+ Config.set(key, value)
await update_buttons(pre_message, "var")
await delete_message(message)
- if key == "DATABASE_URL":
- await Database.connect()
- if config_dict["DATABASE_URL"]:
- await Database.update_config({key: value})
- if key in ["QUEUE_ALL", "QUEUE_DOWNLOAD", "QUEUE_UPLOAD"]:
+ await database.update_config({key: value})
+ if key in ["SEARCH_PLUGINS", "SEARCH_API_LINK"]:
+ await initiate_search_tools()
+ elif key in ["QUEUE_ALL", "QUEUE_DOWNLOAD", "QUEUE_UPLOAD"]:
await start_from_queued()
+ elif key in [
+ "RCLONE_SERVE_URL",
+ "RCLONE_SERVE_PORT",
+ "RCLONE_SERVE_USER",
+ "RCLONE_SERVE_PASS",
+ ]:
+ await rclone_serve_booter()
+ elif key == "RSS_DELAY":
+ add_job()
@new_task
@@ -177,16 +243,15 @@ async def update_private_file(_, message, pre_message):
handler_dict[message.chat.id] = False
if not message.media and (file_name := message.text):
fn = file_name.rsplit(".zip", 1)[0]
- if await aiopath.isfile(fn) and file_name != "config.env":
+ if await aiopath.isfile(fn) and file_name != "config.py":
await remove(fn)
if fn == "accounts":
if await aiopath.exists("accounts"):
await rmtree("accounts", ignore_errors=True)
if await aiopath.exists("rclone_sa"):
await rmtree("rclone_sa", ignore_errors=True)
- config_dict["USE_SERVICE_ACCOUNTS"] = False
- if config_dict["DATABASE_URL"]:
- await Database.update_config({"USE_SERVICE_ACCOUNTS": False})
+ Config.USE_SERVICE_ACCOUNTS = False
+ await database.update_config({"USE_SERVICE_ACCOUNTS": False})
elif file_name in [".netrc", "netrc"]:
await (await create_subprocess_exec("touch", ".netrc")).wait()
await (await create_subprocess_exec("chmod", "600", ".netrc")).wait()
@@ -196,7 +261,10 @@ async def update_private_file(_, message, pre_message):
await delete_message(message)
elif doc := message.document:
file_name = doc.file_name
- await message.download(file_name=f"{getcwd()}/{file_name}")
+ fpath = f"{getcwd()}/{file_name}"
+ if await aiopath.exists(fpath):
+ await remove(fpath)
+ await message.download(file_name=fpath)
if file_name == "accounts.zip":
if await aiopath.exists("accounts"):
await rmtree("accounts", ignore_errors=True)
@@ -219,10 +287,10 @@ async def update_private_file(_, message, pre_message):
drives_ids.clear()
drives_names.clear()
index_urls.clear()
- if GDRIVE_ID := config_dict["GDRIVE_ID"]:
+ if Config.GDRIVE_ID:
drives_names.append("Main")
- drives_ids.append(GDRIVE_ID)
- index_urls.append(config_dict["INDEX_URL"])
+ drives_ids.append(Config.GDRIVE_ID)
+ index_urls.append(Config.INDEX_URL)
async with aiopen("list_drives.txt", "r+") as f:
lines = await f.readlines()
for line in lines:
@@ -241,13 +309,13 @@ async def update_private_file(_, message, pre_message):
await (
await create_subprocess_exec("cp", ".netrc", "/root/.netrc")
).wait()
- elif file_name == "config.env":
- load_dotenv("config.env", override=True)
+ elif file_name == "config.py":
await load_config()
await delete_message(message)
+ if file_name == "rclone.conf":
+ await rclone_serve_booter()
await update_buttons(pre_message)
- if config_dict["DATABASE_URL"]:
- await Database.update_private_file(file_name)
+ await database.update_private_file(file_name)
if await aiopath.exists("accounts.zip"):
await remove("accounts.zip")
@@ -290,7 +358,7 @@ async def edit_bot_settings(client, query):
await query.answer()
globals()["start"] = 0
await update_buttons(message, None)
- elif data[1] in ["var"]:
+ elif data[1] == "var":
await query.answer()
await update_buttons(message, data[1])
elif data[1] == "resetvar":
@@ -311,8 +379,8 @@ async def edit_bot_settings(client, query):
key,
)
elif data[2] == "EXTENSION_FILTER":
- global_extension_filter.clear()
- global_extension_filter.extend(["aria2", "!qB"])
+ extension_filter.clear()
+ extension_filter.extend(["aria2", "!qB"])
elif data[2] == "TORRENT_TIMEOUT":
downloads = await sync_to_async(aria2.get_downloads)
for download in downloads:
@@ -326,12 +394,20 @@ async def edit_bot_settings(client, query):
except Exception as e:
LOGGER.error(e)
aria2_options["bt-stop-timeout"] = "0"
- if config_dict["DATABASE_URL"]:
- await Database.update_aria2("bt-stop-timeout", "0")
+ await database.update_aria2("bt-stop-timeout", "0")
elif data[2] == "BASE_URL":
await (
await create_subprocess_exec("pkill", "-9", "-f", "gunicorn")
).wait()
+ elif data[2] == "BASE_URL_PORT":
+ value = 80
+ if Config.BASE_URL:
+ await (
+ await create_subprocess_exec("pkill", "-9", "-f", "gunicorn")
+ ).wait()
+ await create_subprocess_shell(
+ "gunicorn web.wserver:app --bind 0.0.0.0:80 --worker-class gevent",
+ )
elif data[2] == "GDRIVE_ID":
if drives_names and drives_names[0] == "Main":
drives_names.pop(0)
@@ -340,14 +416,24 @@ async def edit_bot_settings(client, query):
elif data[2] == "INDEX_URL":
if drives_names and drives_names[0] == "Main":
index_urls[0] = ""
- config_dict[data[2]] = value
+ elif data[2] == "INCOMPLETE_TASK_NOTIFIER":
+ await database.trunc_table("tasks")
+ Config.set(data[2], value)
await update_buttons(message, "var")
if data[2] == "DATABASE_URL":
- await Database.disconnect()
- if config_dict["DATABASE_URL"]:
- await Database.update_config({data[2]: value})
- if data[2] in ["QUEUE_ALL", "QUEUE_DOWNLOAD", "QUEUE_UPLOAD"]:
+ await database.disconnect()
+ await database.update_config({data[2]: value})
+ if data[2] in ["SEARCH_PLUGINS", "SEARCH_API_LINK"]:
+ await initiate_search_tools()
+ elif data[2] in ["QUEUE_ALL", "QUEUE_DOWNLOAD", "QUEUE_UPLOAD"]:
await start_from_queued()
+ elif data[2] in [
+ "RCLONE_SERVE_URL",
+ "RCLONE_SERVE_PORT",
+ "RCLONE_SERVE_USER",
+ "RCLONE_SERVE_PASS",
+ ]:
+ await rclone_serve_booter()
elif data[1] == "private":
await query.answer()
await update_buttons(message, data[1])
@@ -361,7 +447,7 @@ async def edit_bot_settings(client, query):
rfunc = partial(update_buttons, message, "var")
await event_handler(client, query, pfunc, rfunc)
elif data[1] == "botvar" and state == "view":
- value = f"{config_dict[data[2]]}"
+ value = f"{Config.get(data[2])}"
if len(value) > 200:
await query.answer()
with BytesIO(str.encode(value)) as out_file:
@@ -387,7 +473,7 @@ async def edit_bot_settings(client, query):
@new_task
-async def bot_settings(_, message):
+async def send_bot_settings(_, message):
handler_dict[message.chat.id] = False
msg, button = await get_buttons()
globals()["start"] = 0
@@ -395,109 +481,23 @@ async def bot_settings(_, message):
async def load_config():
- BOT_TOKEN = environ.get("BOT_TOKEN", "")
- if len(BOT_TOKEN) == 0:
- BOT_TOKEN = config_dict["BOT_TOKEN"]
-
- TELEGRAM_API = environ.get("TELEGRAM_API", "")
- if len(TELEGRAM_API) == 0:
- TELEGRAM_API = config_dict["TELEGRAM_API"]
- else:
- TELEGRAM_API = int(TELEGRAM_API)
-
- TELEGRAM_HASH = environ.get("TELEGRAM_HASH", "")
- if len(TELEGRAM_HASH) == 0:
- TELEGRAM_HASH = config_dict["TELEGRAM_HASH"]
-
- OWNER_ID = environ.get("OWNER_ID", "")
- OWNER_ID = config_dict["OWNER_ID"] if len(OWNER_ID) == 0 else int(OWNER_ID)
-
- DATABASE_URL = environ.get("DATABASE_URL", "")
- if len(DATABASE_URL) == 0:
- DATABASE_URL = ""
-
- GDRIVE_ID = environ.get("GDRIVE_ID", "")
- if len(GDRIVE_ID) == 0:
- GDRIVE_ID = ""
-
- RCLONE_PATH = environ.get("RCLONE_PATH", "")
- if len(RCLONE_PATH) == 0:
- RCLONE_PATH = ""
-
- DEFAULT_UPLOAD = environ.get("DEFAULT_UPLOAD", "")
- if DEFAULT_UPLOAD != "gd":
- DEFAULT_UPLOAD = "rc"
-
- RCLONE_FLAGS = environ.get("RCLONE_FLAGS", "")
- if len(RCLONE_FLAGS) == 0:
- RCLONE_FLAGS = ""
-
- AUTHORIZED_CHATS = environ.get("AUTHORIZED_CHATS", "")
- if len(AUTHORIZED_CHATS) != 0:
- aid = AUTHORIZED_CHATS.split()
- for id_ in aid:
- chat_id, *thread_ids = id_.split("|")
- chat_id = int(chat_id.strip())
- if thread_ids:
- thread_ids = [int(x.strip()) for x in thread_ids]
- user_data[chat_id] = {"is_auth": True, "thread_ids": thread_ids}
- else:
- user_data[chat_id] = {"is_auth": True}
-
- SUDO_USERS = environ.get("SUDO_USERS", "")
- if len(SUDO_USERS) != 0:
- aid = SUDO_USERS.split()
- for id_ in aid:
- user_data[int(id_.strip())] = {"is_sudo": True}
-
- EXTENSION_FILTER = environ.get("EXTENSION_FILTER", "")
- if len(EXTENSION_FILTER) > 0:
- fx = EXTENSION_FILTER.split()
- global_extension_filter.clear()
- global_extension_filter.extend(["aria2", "!qB"])
- for x in fx:
- if x.strip().startswith("."):
- x = x.lstrip(".")
- global_extension_filter.append(x.strip().lower())
-
- FILELION_API = environ.get("FILELION_API", "")
- if len(FILELION_API) == 0:
- FILELION_API = ""
-
- STREAMWISH_API = environ.get("STREAMWISH_API", "")
- if len(STREAMWISH_API) == 0:
- STREAMWISH_API = ""
-
- INDEX_URL = environ.get("INDEX_URL", "").rstrip("/")
- if len(INDEX_URL) == 0:
- INDEX_URL = ""
-
- LEECH_FILENAME_PREFIX = environ.get("LEECH_FILENAME_PREFIX", "")
- if len(LEECH_FILENAME_PREFIX) == 0:
- LEECH_FILENAME_PREFIX = ""
-
- MAX_SPLIT_SIZE = 4194304000 if IS_PREMIUM_USER else 2097152000
-
- LEECH_SPLIT_SIZE = environ.get("LEECH_SPLIT_SIZE", "")
- if len(LEECH_SPLIT_SIZE) == 0 or int(LEECH_SPLIT_SIZE) > MAX_SPLIT_SIZE:
- LEECH_SPLIT_SIZE = MAX_SPLIT_SIZE
- else:
- LEECH_SPLIT_SIZE = int(LEECH_SPLIT_SIZE)
-
- YT_DLP_OPTIONS = environ.get("YT_DLP_OPTIONS", "")
- if len(YT_DLP_OPTIONS) == 0:
- YT_DLP_OPTIONS = ""
-
- LEECH_DUMP_CHAT = environ.get("LEECH_DUMP_CHAT", "")
- LEECH_DUMP_CHAT = "" if len(LEECH_DUMP_CHAT) == 0 else LEECH_DUMP_CHAT
-
- CMD_SUFFIX = environ.get("CMD_SUFFIX", "")
-
- USER_SESSION_STRING = environ.get("USER_SESSION_STRING", "")
+ Config.load()
+ drives_ids.clear()
+ drives_names.clear()
+ index_urls.clear()
+ await update_variables()
+
+ if len(task_dict) != 0 and (st := intervals["status"]):
+ for key, intvl in list(st.items()):
+ intvl.cancel()
+ intervals["status"][key] = SetInterval(
+ Config.STATUS_UPDATE_INTERVAL,
+ update_status_message,
+ key,
+ )
- TORRENT_TIMEOUT = environ.get("TORRENT_TIMEOUT", "")
downloads = aria2.get_downloads()
- if len(TORRENT_TIMEOUT) == 0:
+ if not Config.TORRENT_TIMEOUT:
for download in downloads:
if not download.is_complete:
try:
@@ -509,9 +509,7 @@ async def load_config():
except Exception as e:
LOGGER.error(e)
aria2_options["bt-stop-timeout"] = "0"
- if config_dict["DATABASE_URL"]:
- await Database.update_aria2("bt-stop-timeout", "0")
- TORRENT_TIMEOUT = ""
+ await database.update_aria2("bt-stop-timeout", "0")
else:
for download in downloads:
if not download.is_complete:
@@ -519,199 +517,27 @@ async def load_config():
await sync_to_async(
aria2.client.change_option,
download.gid,
- {"bt-stop-timeout": TORRENT_TIMEOUT},
+ {"bt-stop-timeout": Config.TORRENT_TIMEOUT},
)
except Exception as e:
LOGGER.error(e)
- aria2_options["bt-stop-timeout"] = TORRENT_TIMEOUT
- if config_dict["DATABASE_URL"]:
- await Database.update_aria2("bt-stop-timeout", TORRENT_TIMEOUT)
- TORRENT_TIMEOUT = int(TORRENT_TIMEOUT)
-
- QUEUE_ALL = environ.get("QUEUE_ALL", "")
- QUEUE_ALL = "" if len(QUEUE_ALL) == 0 else int(QUEUE_ALL)
-
- QUEUE_DOWNLOAD = environ.get("QUEUE_DOWNLOAD", "")
- QUEUE_DOWNLOAD = "" if len(QUEUE_DOWNLOAD) == 0 else int(QUEUE_DOWNLOAD)
-
- QUEUE_UPLOAD = environ.get("QUEUE_UPLOAD", "")
- QUEUE_UPLOAD = "" if len(QUEUE_UPLOAD) == 0 else int(QUEUE_UPLOAD)
-
- STOP_DUPLICATE = environ.get("STOP_DUPLICATE", "")
- STOP_DUPLICATE = STOP_DUPLICATE.lower() == "true"
-
- IS_TEAM_DRIVE = environ.get("IS_TEAM_DRIVE", "")
- IS_TEAM_DRIVE = IS_TEAM_DRIVE.lower() == "true"
-
- USE_SERVICE_ACCOUNTS = environ.get("USE_SERVICE_ACCOUNTS", "")
- USE_SERVICE_ACCOUNTS = USE_SERVICE_ACCOUNTS.lower() == "true"
-
- AS_DOCUMENT = environ.get("AS_DOCUMENT", "")
- AS_DOCUMENT = AS_DOCUMENT.lower() == "true"
-
- USER_TRANSMISSION = environ.get("USER_TRANSMISSION", "")
- USER_TRANSMISSION = USER_TRANSMISSION.lower() == "true" and IS_PREMIUM_USER
-
- BASE_URL_PORT = environ.get("BASE_URL_PORT", "")
- BASE_URL_PORT = 80 if len(BASE_URL_PORT) == 0 else int(BASE_URL_PORT)
+ aria2_options["bt-stop-timeout"] = Config.TORRENT_TIMEOUT
+ await database.update_aria2("bt-stop-timeout", Config.TORRENT_TIMEOUT)
- RCLONE_SERVE_URL = environ.get("RCLONE_SERVE_URL", "")
- if len(RCLONE_SERVE_URL) == 0:
- RCLONE_SERVE_URL = ""
-
- RCLONE_SERVE_PORT = environ.get("RCLONE_SERVE_PORT", "")
- RCLONE_SERVE_PORT = (
- 8080 if len(RCLONE_SERVE_PORT) == 0 else int(RCLONE_SERVE_PORT)
- )
-
- RCLONE_SERVE_USER = environ.get("RCLONE_SERVE_USER", "")
- if len(RCLONE_SERVE_USER) == 0:
- RCLONE_SERVE_USER = ""
-
- RCLONE_SERVE_PASS = environ.get("RCLONE_SERVE_PASS", "")
- if len(RCLONE_SERVE_PASS) == 0:
- RCLONE_SERVE_PASS = ""
-
- NAME_SUBSTITUTE = environ.get("NAME_SUBSTITUTE", "")
- NAME_SUBSTITUTE = "" if len(NAME_SUBSTITUTE) == 0 else NAME_SUBSTITUTE
-
- MIXED_LEECH = environ.get("MIXED_LEECH", "")
- MIXED_LEECH = MIXED_LEECH.lower() == "true" and IS_PREMIUM_USER
-
- THUMBNAIL_LAYOUT = environ.get("THUMBNAIL_LAYOUT", "")
- THUMBNAIL_LAYOUT = "" if len(THUMBNAIL_LAYOUT) == 0 else THUMBNAIL_LAYOUT
-
- FFMPEG_CMDS = environ.get("FFMPEG_CMDS", "")
- try:
- FFMPEG_CMDS = [] if len(FFMPEG_CMDS) == 0 else eval(FFMPEG_CMDS)
- except Exception:
- LOGGER.error(f"Wrong FFMPEG_CMDS format: {FFMPEG_CMDS}")
- FFMPEG_CMDS = []
+ if not Config.INCOMPLETE_TASK_NOTIFIER:
+ await database.trunc_table("tasks")
await (await create_subprocess_exec("pkill", "-9", "-f", "gunicorn")).wait()
- BASE_URL = environ.get("BASE_URL", "").rstrip("/")
- if len(BASE_URL) == 0:
- BASE_URL = ""
- else:
+ if Config.BASE_URL:
await create_subprocess_shell(
- f"gunicorn web.wserver:app --bind 0.0.0.0:{BASE_URL_PORT} --worker-class gevent",
+ f"gunicorn web.wserver:app --bind 0.0.0.0:{Config.BASE_URL_PORT} --worker-class gevent",
)
- UPSTREAM_REPO = environ.get("UPSTREAM_REPO", "")
- if len(UPSTREAM_REPO) == 0:
- UPSTREAM_REPO = ""
-
- UPSTREAM_BRANCH = environ.get("UPSTREAM_BRANCH", "")
- if len(UPSTREAM_BRANCH) == 0:
- UPSTREAM_BRANCH = "master"
-
- FSUB_IDS = environ.get("FSUB_IDS", "")
- if len(FSUB_IDS) == 0:
- FSUB_IDS = ""
-
- PAID_CHAT_ID = environ.get("PAID_CHAT_ID", "")
- PAID_CHAT_ID = "" if len(PAID_CHAT_ID) == 0 else int(PAID_CHAT_ID)
-
- PAID_CHAT_LINK = environ.get("PAID_CHAT_LINK", "")
- if len(PAID_CHAT_LINK) == 0:
- PAID_CHAT_LINK = ""
-
- TOKEN_TIMEOUT = environ.get("TOKEN_TIMEOUT", "")
- TOKEN_TIMEOUT = "" if len(TOKEN_TIMEOUT) == 0 else int(TOKEN_TIMEOUT)
-
- MEGA_EMAIL = environ.get("MEGA_EMAIL", "")
- MEGA_PASSWORD = environ.get("MEGA_PASSWORD", "")
- if len(MEGA_EMAIL) == 0 or len(MEGA_PASSWORD) == 0:
- MEGA_EMAIL = ""
- MEGA_PASSWORD = ""
-
- drives_ids.clear()
- drives_names.clear()
- index_urls.clear()
-
- if GDRIVE_ID:
- drives_names.append("Main")
- drives_ids.append(GDRIVE_ID)
- index_urls.append(INDEX_URL)
-
- if await aiopath.exists("list_drives.txt"):
- async with aiopen("list_drives.txt", "r+") as f:
- lines = await f.readlines()
- for line in lines:
- temp = line.strip().split()
- drives_ids.append(temp[1])
- drives_names.append(temp[0].replace("_", " "))
- if len(temp) > 2:
- index_urls.append(temp[2])
- else:
- index_urls.append("")
-
- config_dict.update(
- {
- "AS_DOCUMENT": AS_DOCUMENT,
- "AUTHORIZED_CHATS": AUTHORIZED_CHATS,
- "BASE_URL": BASE_URL,
- "BASE_URL_PORT": BASE_URL_PORT,
- "BOT_TOKEN": BOT_TOKEN,
- "CMD_SUFFIX": CMD_SUFFIX,
- "DATABASE_URL": DATABASE_URL,
- "DEFAULT_UPLOAD": DEFAULT_UPLOAD,
- "EXTENSION_FILTER": EXTENSION_FILTER,
- "FFMPEG_CMDS": FFMPEG_CMDS,
- "FILELION_API": FILELION_API,
- "FSUB_IDS": FSUB_IDS,
- "GDRIVE_ID": GDRIVE_ID,
- "INDEX_URL": INDEX_URL,
- "IS_TEAM_DRIVE": IS_TEAM_DRIVE,
- "LEECH_DUMP_CHAT": LEECH_DUMP_CHAT,
- "LEECH_FILENAME_PREFIX": LEECH_FILENAME_PREFIX,
- "LEECH_SPLIT_SIZE": LEECH_SPLIT_SIZE,
- "MIXED_LEECH": MIXED_LEECH,
- "MEGA_EMAIL": MEGA_EMAIL,
- "MEGA_PASSWORD": MEGA_PASSWORD,
- "NAME_SUBSTITUTE": NAME_SUBSTITUTE,
- "OWNER_ID": OWNER_ID,
- "PAID_CHAT_ID": PAID_CHAT_ID,
- "PAID_CHAT_LINK": PAID_CHAT_LINK,
- "TOKEN_TIMEOUT": TOKEN_TIMEOUT,
- "QUEUE_ALL": QUEUE_ALL,
- "QUEUE_DOWNLOAD": QUEUE_DOWNLOAD,
- "QUEUE_UPLOAD": QUEUE_UPLOAD,
- "RCLONE_FLAGS": RCLONE_FLAGS,
- "RCLONE_PATH": RCLONE_PATH,
- "STOP_DUPLICATE": STOP_DUPLICATE,
- "STREAMWISH_API": STREAMWISH_API,
- "SUDO_USERS": SUDO_USERS,
- "TELEGRAM_API": TELEGRAM_API,
- "TELEGRAM_HASH": TELEGRAM_HASH,
- "THUMBNAIL_LAYOUT": THUMBNAIL_LAYOUT,
- "TORRENT_TIMEOUT": TORRENT_TIMEOUT,
- "USER_TRANSMISSION": USER_TRANSMISSION,
- "UPSTREAM_REPO": UPSTREAM_REPO,
- "UPSTREAM_BRANCH": UPSTREAM_BRANCH,
- "USER_SESSION_STRING": USER_SESSION_STRING,
- "USE_SERVICE_ACCOUNTS": USE_SERVICE_ACCOUNTS,
- "YT_DLP_OPTIONS": YT_DLP_OPTIONS,
- },
- )
-
- if config_dict["DATABASE_URL"]:
- await Database.connect()
- await Database.update_config(config_dict)
+ if Config.DATABASE_URL:
+ await database.connect()
+ config_dict = Config.get_all()
+ await database.update_config(config_dict)
else:
- await Database.disconnect()
- await gather(start_from_queued())
-
-
-bot.add_handler(
- MessageHandler(
- bot_settings,
- filters=command(BotCommands.BotSetCommand) & CustomFilters.sudo,
- ),
-)
-bot.add_handler(
- CallbackQueryHandler(
- edit_bot_settings,
- filters=regex("^botset") & CustomFilters.sudo,
- ),
-)
+ await database.disconnect()
+ await gather(initiate_search_tools(), start_from_queued(), rclone_serve_booter())
+ add_job()
diff --git a/bot/modules/cancel_task.py b/bot/modules/cancel_task.py
index 709034e03..3b7fab2e0 100644
--- a/bot/modules/cancel_task.py
+++ b/bot/modules/cancel_task.py
@@ -1,9 +1,7 @@
from asyncio import sleep
-from pyrogram.filters import command, regex
-from pyrogram.handlers import CallbackQueryHandler, MessageHandler
-
-from bot import OWNER_ID, bot, multi_tags, task_dict, task_dict_lock, user_data
+from bot import multi_tags, task_dict, task_dict_lock, user_data
+from bot.core.aeon_client import Config
from bot.helper.ext_utils.bot_utils import new_task
from bot.helper.ext_utils.status_utils import (
MirrorStatus,
@@ -22,30 +20,35 @@
@new_task
-async def cancel_task(_, message):
+async def cancel(_, message):
user_id = message.from_user.id if message.from_user else message.sender_chat.id
- msg = message.text.split("_", maxsplit=1)
- await delete_message(message)
+ msg = message.text.split()
if len(msg) > 1:
- gid = msg[1].split("@", maxsplit=1)
- gid = gid[0]
+ gid = msg[1]
if len(gid) == 4:
multi_tags.discard(gid)
return
task = await get_task_by_gid(gid)
if task is None:
- await delete_message(message)
+ await send_message(message, f"GID: {gid}
Not Found.")
return
elif reply_to_id := message.reply_to_message_id:
async with task_dict_lock:
task = task_dict.get(reply_to_id)
if task is None:
+ await send_message(message, "This is not an active task!")
return
elif len(msg) == 1:
+ msg = (
+ "Reply to an active Command message which was used to start the download"
+ f" or send /{BotCommands.CancelTaskCommand[0]} GID
to cancel it!"
+ )
+ await send_message(message, msg)
return
- if user_id not in (OWNER_ID, task.listener.user_id) and (
+ if user_id not in (Config.OWNER_ID, task.listener.user_id) and (
user_id not in user_data or not user_data[user_id].get("is_sudo")
):
+ await send_message(message, "This task is not for you!")
return
obj = task.task()
await obj.cancel_task()
@@ -91,12 +94,10 @@ def create_cancel_buttons(is_sudo, user_id=""):
)
buttons.data_button("Seeding", f"canall ms {MirrorStatus.STATUS_SEED} {user_id}")
buttons.data_button(
- "Spltting",
- f"canall ms {MirrorStatus.STATUS_SPLIT} {user_id}",
+ "Spltting", f"canall ms {MirrorStatus.STATUS_SPLIT} {user_id}"
)
buttons.data_button(
- "Cloning",
- f"canall ms {MirrorStatus.STATUS_CLONE} {user_id}",
+ "Cloning", f"canall ms {MirrorStatus.STATUS_CLONE} {user_id}"
)
buttons.data_button(
"Extracting",
@@ -123,12 +124,10 @@ def create_cancel_buttons(is_sudo, user_id=""):
f"canall ms {MirrorStatus.STATUS_CONVERT} {user_id}",
)
buttons.data_button(
- "FFmpeg",
- f"canall ms {MirrorStatus.STATUS_FFMPEG} {user_id}",
+ "FFmpeg", f"canall ms {MirrorStatus.STATUS_FFMPEG} {user_id}"
)
buttons.data_button(
- "Paused",
- f"canall ms {MirrorStatus.STATUS_PAUSED} {user_id}",
+ "Paused", f"canall ms {MirrorStatus.STATUS_PAUSED} {user_id}"
)
buttons.data_button("All", f"canall ms All {user_id}")
if is_sudo:
@@ -141,7 +140,7 @@ def create_cancel_buttons(is_sudo, user_id=""):
@new_task
-async def cancell_all_buttons(_, message):
+async def cancel_all_buttons(_, message):
async with task_dict_lock:
count = len(task_dict)
if count == 0:
@@ -193,22 +192,3 @@ async def cancel_all_update(_, query):
res = await cancel_all(data[1], user_id)
if not res:
await send_message(reply_to, f"No matching tasks for {data[1]}!")
-
-
-bot.add_handler(
- MessageHandler(
- cancel_task,
- filters=regex(r"^/stop(_\w+)?(?!all)") & CustomFilters.authorized,
- ),
-)
-bot.add_handler(
- MessageHandler(
- cancell_all_buttons,
- filters=command(
- BotCommands.CancelAllCommand,
- )
- & CustomFilters.authorized,
- ),
-)
-bot.add_handler(CallbackQueryHandler(cancel_all_update, filters=regex("^canall")))
-bot.add_handler(CallbackQueryHandler(cancel_multi, filters=regex("^stopm")))
diff --git a/bot/modules/authorize.py b/bot/modules/chat_permission.py
similarity index 72%
rename from bot/modules/authorize.py
rename to bot/modules/chat_permission.py
index 2747b0970..fb08fc926 100644
--- a/bot/modules/authorize.py
+++ b/bot/modules/chat_permission.py
@@ -1,11 +1,6 @@
-from pyrogram.filters import command
-from pyrogram.handlers import MessageHandler
-
-from bot import bot, config_dict, user_data
+from bot import user_data
from bot.helper.ext_utils.bot_utils import new_task, update_user_ldata
-from bot.helper.ext_utils.db_handler import Database
-from bot.helper.telegram_helper.bot_commands import BotCommands
-from bot.helper.telegram_helper.filters import CustomFilters
+from bot.helper.ext_utils.db_handler import database
from bot.helper.telegram_helper.message_utils import send_message
@@ -42,8 +37,7 @@ async def authorize(_, message):
update_user_ldata(chat_id, "is_auth", True)
if thread_id is not None:
update_user_ldata(chat_id, "thread_ids", [thread_id])
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(chat_id)
+ await database.update_user_data(chat_id)
msg = "Authorized"
await send_message(message, msg)
@@ -73,8 +67,7 @@ async def unauthorize(_, message):
user_data[chat_id]["thread_ids"].remove(thread_id)
else:
update_user_ldata(chat_id, "is_auth", False)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(chat_id)
+ await database.update_user_data(chat_id)
msg = "Unauthorized"
else:
msg = "Already Unauthorized!"
@@ -96,8 +89,7 @@ async def add_sudo(_, message):
msg = "Already Sudo!"
else:
update_user_ldata(id_, "is_sudo", True)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(id_)
+ await database.update_user_data(id_)
msg = "Promoted as Sudo"
else:
msg = "Give ID or Reply To message of whom you want to Promote."
@@ -116,47 +108,8 @@ async def remove_sudo(_, message):
)
if (id_ and id_ not in user_data) or user_data[id_].get("is_sudo"):
update_user_ldata(id_, "is_sudo", False)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(id_)
+ await database.update_user_data(id_)
msg = "Demoted"
else:
msg = "Give ID or Reply To message of whom you want to remove from Sudo"
await send_message(message, msg)
-
-
-bot.add_handler(
- MessageHandler(
- authorize,
- filters=command(
- BotCommands.AuthorizeCommand,
- )
- & CustomFilters.sudo,
- ),
-)
-bot.add_handler(
- MessageHandler(
- unauthorize,
- filters=command(
- BotCommands.UnAuthorizeCommand,
- )
- & CustomFilters.sudo,
- ),
-)
-bot.add_handler(
- MessageHandler(
- add_sudo,
- filters=command(
- BotCommands.AddSudoCommand,
- )
- & CustomFilters.sudo,
- ),
-)
-bot.add_handler(
- MessageHandler(
- remove_sudo,
- filters=command(
- BotCommands.RmSudoCommand,
- )
- & CustomFilters.sudo,
- ),
-)
diff --git a/bot/modules/clone.py b/bot/modules/clone.py
index 39d0dd5cd..af1414dec 100644
--- a/bot/modules/clone.py
+++ b/bot/modules/clone.py
@@ -1,13 +1,10 @@
from asyncio import gather
from json import loads
-from secrets import token_hex
+from secrets import token_urlsafe
from aiofiles.os import remove
-from pyrogram.filters import command
-from pyrogram.handlers import MessageHandler
-from bot import LOGGER, bot, bot_loop, task_dict, task_dict_lock
-from bot.helper.aeon_utils.access_check import error_check
+from bot import LOGGER, bot_loop, task_dict, task_dict_lock
from bot.helper.ext_utils.bot_utils import (
COMMAND_USAGE,
arg_parser,
@@ -33,12 +30,8 @@
GoogleDriveStatus,
)
from bot.helper.mirror_leech_utils.status_utils.rclone_status import RcloneStatus
-from bot.helper.telegram_helper.bot_commands import BotCommands
-from bot.helper.telegram_helper.filters import CustomFilters
from bot.helper.telegram_helper.message_utils import (
- delete_links,
delete_message,
- five_minute_del,
send_message,
send_status_message,
)
@@ -70,11 +63,6 @@ def __init__(
self.is_clone = True
async def new_event(self):
- error_msg, error_button = await error_check(self.message)
- if error_msg:
- await delete_links(self.message)
- error = await send_message(self.message, error_msg, error_button)
- return await five_minute_del(error)
text = self.message.text.split("\n")
input_list = text[0].split(" ")
@@ -92,7 +80,7 @@ async def new_event(self):
try:
self.multi = int(args["-i"])
- except Exception:
+ except:
self.multi = 0
self.up_dest = args["-up"]
@@ -114,7 +102,7 @@ async def new_event(self):
if is_bulk:
await self.init_bulk(input_list, bulk_start, bulk_end, Clone)
- return None
+ return
await self.get_tag(text)
@@ -129,15 +117,14 @@ async def new_event(self):
COMMAND_USAGE["clone"][0],
COMMAND_USAGE["clone"][1],
)
- return None
+ return
LOGGER.info(self.link)
try:
await self.before_start()
except Exception as e:
await send_message(self.message, e)
- return None
+ return
await self._proceed_to_clone(sync)
- return None
async def _proceed_to_clone(self, sync):
if is_share_link(self.link):
@@ -172,13 +159,13 @@ async def _proceed_to_clone(self, sync):
)
else:
msg = ""
- gid = token_hex(4)
+ gid = token_urlsafe(12)
async with task_dict_lock:
task_dict[self.mid] = GoogleDriveStatus(self, drive, gid, "cl")
if self.multi <= 1:
await send_status_message(self.message)
flink, mime_type, files, folders, dir_id = await sync_to_async(
- drive.clone,
+ drive.clone
)
if msg:
await delete_message(msg)
@@ -210,7 +197,7 @@ async def _proceed_to_clone(self, sync):
else:
src_path = self.link
cmd = [
- "xone",
+ "rclone",
"lsjson",
"--fast-list",
"--stat",
@@ -218,6 +205,11 @@ async def _proceed_to_clone(self, sync):
"--config",
config_path,
f"{remote}:{src_path}",
+ "--log-systemd",
+ "--log-file",
+ "rlog.txt",
+ "--log-level",
+ "ERROR",
]
res = await cmd_exec(cmd)
if res[2] != 0:
@@ -247,7 +239,7 @@ async def _proceed_to_clone(self, sync):
LOGGER.info(
f"Clone Started: Name: {self.name} - Source: {self.link} - Destination: {self.up_dest}",
)
- gid = token_hex(4)
+ gid = token_urlsafe(12)
async with task_dict_lock:
task_dict[self.mid] = RcloneStatus(self, RCTransfer, gid, "cl")
if self.multi <= 1:
@@ -266,7 +258,7 @@ async def _proceed_to_clone(self, sync):
return
LOGGER.info(f"Cloning Done: {self.name}")
cmd1 = [
- "xone",
+ "rclone",
"lsf",
"--fast-list",
"-R",
@@ -276,7 +268,7 @@ async def _proceed_to_clone(self, sync):
destination,
]
cmd2 = [
- "xone",
+ "rclone",
"lsf",
"--fast-list",
"-R",
@@ -286,7 +278,7 @@ async def _proceed_to_clone(self, sync):
destination,
]
cmd3 = [
- "xone",
+ "rclone",
"size",
"--fast-list",
"--json",
@@ -328,16 +320,5 @@ async def _proceed_to_clone(self, sync):
)
-async def clone(client, message):
+async def clone_node(client, message):
bot_loop.create_task(Clone(client, message).new_event())
-
-
-bot.add_handler(
- MessageHandler(
- clone,
- filters=command(
- BotCommands.CloneCommand,
- )
- & CustomFilters.authorized,
- ),
-)
diff --git a/bot/modules/exec.py b/bot/modules/exec.py
index 9274843dc..5f63d776f 100644
--- a/bot/modules/exec.py
+++ b/bot/modules/exec.py
@@ -6,13 +6,10 @@
from traceback import format_exc
from aiofiles import open as aiopen
-from pyrogram.filters import command
-from pyrogram.handlers import MessageHandler
-from bot import LOGGER, bot
+from bot import LOGGER
+from bot.core.aeon_client import TgClient
from bot.helper.ext_utils.bot_utils import new_task, sync_to_async
-from bot.helper.telegram_helper.bot_commands import BotCommands
-from bot.helper.telegram_helper.filters import CustomFilters
from bot.helper.telegram_helper.message_utils import send_file, send_message
namespaces = {}
@@ -22,7 +19,7 @@ def namespace_of(message):
if message.chat.id not in namespaces:
namespaces[message.chat.id] = {
"__builtins__": globals()["__builtins__"],
- "bot": bot,
+ "bot": TgClient.bot,
"message": message,
"user": message.from_user or message.sender_chat,
"chat": message.chat,
@@ -90,7 +87,7 @@ async def do(func, message):
func_return = (
await sync_to_async(rfunc) if func == "exec" else await rfunc()
)
- except Exception:
+ except:
value = stdout.getvalue()
return f"{value}{format_exc()}"
else:
@@ -115,32 +112,3 @@ async def clear(_, message):
if message.chat.id in namespaces:
del namespaces[message.chat.id]
await send("Locals Cleared.", message)
-
-
-bot.add_handler(
- MessageHandler(
- aioexecute,
- filters=command(
- BotCommands.AExecCommand,
- )
- & CustomFilters.owner,
- ),
-)
-bot.add_handler(
- MessageHandler(
- execute,
- filters=command(
- BotCommands.ExecCommand,
- )
- & CustomFilters.owner,
- ),
-)
-bot.add_handler(
- MessageHandler(
- clear,
- filters=command(
- BotCommands.ClearLocalsCommand,
- )
- & CustomFilters.owner,
- ),
-)
diff --git a/bot/modules/file_selector.py b/bot/modules/file_selector.py
index d8ff3870c..6cd5890b3 100644
--- a/bot/modules/file_selector.py
+++ b/bot/modules/file_selector.py
@@ -2,28 +2,22 @@
from aiofiles.os import path as aiopath
from aiofiles.os import remove
-from pyrogram.filters import command, regex
-from pyrogram.handlers import CallbackQueryHandler, MessageHandler
from bot import (
LOGGER,
- OWNER_ID,
aria2,
- bot,
- config_dict,
+ xnox_client,
task_dict,
task_dict_lock,
user_data,
- xnox_client,
)
+from bot.core.config_manager import Config
from bot.helper.ext_utils.bot_utils import (
bt_selection_buttons,
new_task,
sync_to_async,
)
from bot.helper.ext_utils.status_utils import MirrorStatus, get_task_by_gid
-from bot.helper.telegram_helper.bot_commands import BotCommands
-from bot.helper.telegram_helper.filters import CustomFilters
from bot.helper.telegram_helper.message_utils import (
delete_message,
send_message,
@@ -33,7 +27,7 @@
@new_task
async def select(_, message):
- if not config_dict["BASE_URL"]:
+ if not Config.BASE_URL:
await send_message(message, "Base URL not defined!")
return
user_id = message.from_user.id
@@ -53,13 +47,13 @@ async def select(_, message):
elif len(msg) == 1:
msg = (
"Reply to an active /cmd which was used to start the download or add gid along with cmd\n\n"
- + "This command mainly for selection incase you decided to select files from already added torrent. "
+ + "This command mainly for selection incase you decided to select files from already added torrent/nzb. "
+ "But you can always use /cmd with arg `s` to select files before download start."
)
await send_message(message, msg)
return
- if user_id not in (OWNER_ID, task.listener.user_id) and (
+ if user_id not in (Config.OWNER_ID, task.listener.user_id) and (
user_id not in user_data or not user_data[user_id].get("is_sudo")
):
await send_message(message, "This task is not for you!")
@@ -71,7 +65,7 @@ async def select(_, message):
]:
await send_message(
message,
- "Task should be in download or pause (incase message deleted by wrong) or queued status (incase you have used torrent!",
+ "Task should be in download or pause (incase message deleted by wrong) or queued status (incase you have used torrent file)!",
)
return
if task.name().startswith("[METADATA]") or task.name().startswith("Trying"):
@@ -84,7 +78,10 @@ async def select(_, message):
if not task.queued:
await sync_to_async(task.update)
id_ = task.hash()
- await sync_to_async(xnox_client.torrents_stop, torrent_hashes=id_)
+ await sync_to_async(
+ xnox_client.torrents_stop,
+ torrent_hashes=id_,
+ )
elif not task.queued:
await sync_to_async(task.update)
try:
@@ -94,8 +91,8 @@ async def select(_, message):
f"{e} Error in pause, this mostly happens after abuse aria2",
)
task.listener.select = True
- except Exception:
- await send_message(message, "This is not a bittorrent task!")
+ except:
+ await send_message(message, "This is not a bittorrent or sabnzbd task!")
return
SBUTTONS = bt_selection_buttons(id_)
@@ -104,7 +101,7 @@ async def select(_, message):
@new_task
-async def get_confirm(_, query):
+async def confirm_selection(_, query):
user_id = query.from_user.id
data = query.data.split()
message = query.message
@@ -123,7 +120,10 @@ async def get_confirm(_, query):
if hasattr(task, "seeding"):
if task.listener.is_qbit:
tor_info = (
- await sync_to_async(xnox_client.torrents_info, torrent_hash=id_)
+ await sync_to_async(
+ xnox_client.torrents_info,
+ torrent_hash=id_,
+ )
)[0]
path = tor_info.content_path.rsplit("/", 1)[0]
res = await sync_to_async(
@@ -160,15 +160,3 @@ async def get_confirm(_, query):
else:
await delete_message(message)
await task.cancel_task()
-
-
-bot.add_handler(
- MessageHandler(
- select,
- filters=command(
- BotCommands.SelectCommand,
- )
- & CustomFilters.authorized,
- ),
-)
-bot.add_handler(CallbackQueryHandler(get_confirm, filters=regex("^sel")))
diff --git a/bot/modules/force_start.py b/bot/modules/force_start.py
index 7f4e7d4d4..7b294a484 100644
--- a/bot/modules/force_start.py
+++ b/bot/modules/force_start.py
@@ -1,9 +1,4 @@
-from pyrogram.filters import command
-from pyrogram.handlers import MessageHandler
-
from bot import (
- OWNER_ID,
- bot,
queue_dict_lock,
queued_dl,
queued_up,
@@ -11,6 +6,7 @@
task_dict_lock,
user_data,
)
+from bot.core.config_manager import Config
from bot.helper.ext_utils.bot_utils import new_task
from bot.helper.ext_utils.status_utils import get_task_by_gid
from bot.helper.ext_utils.task_manager import (
@@ -18,7 +14,6 @@
start_up_from_queued,
)
from bot.helper.telegram_helper.bot_commands import BotCommands
-from bot.helper.telegram_helper.filters import CustomFilters
from bot.helper.telegram_helper.message_utils import send_message
@@ -46,7 +41,7 @@ async def remove_from_queue(_, message):
)
await send_message(message, msg)
return
- if user_id not in (OWNER_ID, task.listener.user_id) and (
+ if user_id not in (Config.OWNER_ID, task.listener.user_id) and (
user_id not in user_data or not user_data[user_id].get("is_sudo")
):
await send_message(message, "This task is not for you!")
@@ -75,14 +70,3 @@ async def remove_from_queue(_, message):
msg = "Task have been force started to download and upload will start once download finish!"
if msg:
await send_message(message, msg)
-
-
-bot.add_handler(
- MessageHandler(
- remove_from_queue,
- filters=command(
- BotCommands.ForceStartCommand,
- )
- & CustomFilters.authorized,
- ),
-)
diff --git a/bot/modules/gd_count.py b/bot/modules/gd_count.py
index 9c769f2dd..2ee42ee95 100644
--- a/bot/modules/gd_count.py
+++ b/bot/modules/gd_count.py
@@ -1,13 +1,7 @@
-from pyrogram.filters import command
-from pyrogram.handlers import MessageHandler
-
-from bot import bot
from bot.helper.ext_utils.bot_utils import new_task, sync_to_async
from bot.helper.ext_utils.links_utils import is_gdrive_link
from bot.helper.ext_utils.status_utils import get_readable_file_size
from bot.helper.mirror_leech_utils.gdrive_utils.count import GoogleDriveCount
-from bot.helper.telegram_helper.bot_commands import BotCommands
-from bot.helper.telegram_helper.filters import CustomFilters
from bot.helper.telegram_helper.message_utils import delete_message, send_message
@@ -46,14 +40,3 @@ async def count_node(_, message):
msg = "Send Gdrive link along with command or by replying to the link by command"
await send_message(message, msg)
-
-
-bot.add_handler(
- MessageHandler(
- count_node,
- filters=command(
- BotCommands.CountCommand,
- )
- & CustomFilters.authorized,
- ),
-)
diff --git a/bot/modules/gd_delete.py b/bot/modules/gd_delete.py
index 8052f83a1..9b816f33e 100644
--- a/bot/modules/gd_delete.py
+++ b/bot/modules/gd_delete.py
@@ -1,12 +1,7 @@
-from pyrogram.filters import command
-from pyrogram.handlers import MessageHandler
-
-from bot import LOGGER, bot
+from bot import LOGGER
from bot.helper.ext_utils.bot_utils import new_task, sync_to_async
from bot.helper.ext_utils.links_utils import is_gdrive_link
from bot.helper.mirror_leech_utils.gdrive_utils.delete import GoogleDriveDelete
-from bot.helper.telegram_helper.bot_commands import BotCommands
-from bot.helper.telegram_helper.filters import CustomFilters
from bot.helper.telegram_helper.message_utils import (
auto_delete_message,
send_message,
@@ -14,7 +9,7 @@
@new_task
-async def deletefile(_, message):
+async def delete_file(_, message):
args = message.text.split()
user = message.from_user or message.sender_chat
if len(args) > 1:
@@ -30,14 +25,3 @@ async def deletefile(_, message):
msg = "Send Gdrive link along with command or by replying to the link by command"
reply_message = await send_message(message, msg)
await auto_delete_message(message, reply_message)
-
-
-bot.add_handler(
- MessageHandler(
- deletefile,
- filters=command(
- BotCommands.DeleteCommand,
- )
- & CustomFilters.authorized,
- ),
-)
diff --git a/bot/modules/gd_search.py b/bot/modules/gd_search.py
index acccff5cc..1c85463da 100644
--- a/bot/modules/gd_search.py
+++ b/bot/modules/gd_search.py
@@ -1,16 +1,11 @@
-from pyrogram.filters import command, regex
-from pyrogram.handlers import CallbackQueryHandler, MessageHandler
-
-from bot import LOGGER, bot, user_data
+from bot import LOGGER, user_data
from bot.helper.ext_utils.bot_utils import (
get_telegraph_list,
new_task,
sync_to_async,
)
from bot.helper.mirror_leech_utils.gdrive_utils.search import GoogleDriveSearch
-from bot.helper.telegram_helper.bot_commands import BotCommands
from bot.helper.telegram_helper.button_build import ButtonMaker
-from bot.helper.telegram_helper.filters import CustomFilters
from bot.helper.telegram_helper.message_utils import edit_message, send_message
@@ -104,15 +99,3 @@ async def gdrive_search(_, message):
buttons = await list_buttons(user_id)
await send_message(message, "Choose list options:", buttons)
return None
-
-
-bot.add_handler(
- MessageHandler(
- gdrive_search,
- filters=command(
- BotCommands.ListCommand,
- )
- & CustomFilters.authorized,
- ),
-)
-bot.add_handler(CallbackQueryHandler(select_type, filters=regex("^list_types")))
diff --git a/bot/modules/help.py b/bot/modules/help.py
index 5ac1f1708..104768da2 100644
--- a/bot/modules/help.py
+++ b/bot/modules/help.py
@@ -1,15 +1,16 @@
-from pyrogram.filters import regex
-from pyrogram.handlers import CallbackQueryHandler
-
-from bot import bot
from bot.helper.ext_utils.bot_utils import COMMAND_USAGE, new_task
from bot.helper.ext_utils.help_messages import (
CLONE_HELP_DICT,
MIRROR_HELP_DICT,
YT_HELP_DICT,
+ help_string,
)
from bot.helper.telegram_helper.button_build import ButtonMaker
-from bot.helper.telegram_helper.message_utils import delete_message, edit_message
+from bot.helper.telegram_helper.message_utils import (
+ delete_message,
+ edit_message,
+ send_message,
+)
@new_task
@@ -27,9 +28,7 @@ async def arg_usage(_, query):
)
elif data[2] == "y":
await edit_message(
- message,
- COMMAND_USAGE["yt"][0],
- COMMAND_USAGE["yt"][1],
+ message, COMMAND_USAGE["yt"][0], COMMAND_USAGE["yt"][1]
)
elif data[2] == "c":
await edit_message(
@@ -54,4 +53,6 @@ async def arg_usage(_, query):
await edit_message(message, CLONE_HELP_DICT[data[2]], button)
-bot.add_handler(CallbackQueryHandler(arg_usage, filters=regex("^help")))
+@new_task
+async def bot_help(_, message):
+ await send_message(message, help_string)
diff --git a/bot/modules/mirror_leech.py b/bot/modules/mirror_leech.py
index e9e362c81..9cfed6dae 100644
--- a/bot/modules/mirror_leech.py
+++ b/bot/modules/mirror_leech.py
@@ -1,14 +1,10 @@
-# ruff: noqa: RUF006
-from asyncio import create_task
from base64 import b64encode
from re import match as re_match
from aiofiles.os import path as aiopath
-from pyrogram.filters import command
-from pyrogram.handlers import MessageHandler
-from bot import DOWNLOAD_DIR, LOGGER, bot, bot_loop, task_dict_lock
-from bot.helper.aeon_utils.access_check import error_check
+from bot import LOGGER, bot_loop, task_dict_lock
+from bot.core.config_manager import Config
from bot.helper.ext_utils.bot_utils import (
COMMAND_USAGE,
arg_parser,
@@ -20,7 +16,6 @@
is_gdrive_id,
is_gdrive_link,
is_magnet,
- is_mega_link,
is_rclone_path,
is_telegram_link,
is_url,
@@ -36,9 +31,6 @@
direct_link_generator,
)
from bot.helper.mirror_leech_utils.download_utils.gd_download import add_gd_download
-from bot.helper.mirror_leech_utils.download_utils.mega_download import (
- add_mega_download,
-)
from bot.helper.mirror_leech_utils.download_utils.qbit_download import add_qb_torrent
from bot.helper.mirror_leech_utils.download_utils.rclone_download import (
add_rclone_download,
@@ -46,11 +38,7 @@
from bot.helper.mirror_leech_utils.download_utils.telegram_download import (
TelegramDownloadHelper,
)
-from bot.helper.telegram_helper.bot_commands import BotCommands
-from bot.helper.telegram_helper.filters import CustomFilters
from bot.helper.telegram_helper.message_utils import (
- delete_links,
- five_minute_del,
get_tg_link_message,
send_message,
)
@@ -83,11 +71,6 @@ def __init__(
self.is_leech = is_leech
async def new_event(self):
- error_msg, error_button = await error_check(self.message)
- if error_msg:
- await delete_links(self.message)
- error = await send_message(self.message, error_msg, error_button)
- return await five_minute_del(error)
text = self.message.text.split("\n")
input_list = text[0].split(" ")
@@ -164,7 +147,7 @@ async def new_event(self):
try:
self.multi = int(args["-i"])
- except Exception:
+ except:
self.multi = 0
try:
@@ -212,7 +195,7 @@ async def new_event(self):
self.folder_name: {
"total": self.multi,
"tasks": {self.mid},
- },
+ }
}
elif self.same_dir:
async with task_dict_lock:
@@ -220,7 +203,7 @@ async def new_event(self):
self.same_dir[fd_name]["total"] -= 1
else:
await self.init_bulk(input_list, bulk_start, bulk_end, Mirror)
- return None
+ return
if len(self.bulk) != 0:
del self.bulk[0]
@@ -229,22 +212,18 @@ async def new_event(self):
await self.get_tag(text)
- path = f"{DOWNLOAD_DIR}{self.mid}{self.folder_name}"
+ path = f"{Config.DOWNLOAD_DIR}{self.mid}{self.folder_name}"
- if (
- not self.link
- and (reply_to := self.message.reply_to_message)
- and reply_to.text
- ):
- self.link = reply_to.text.split("\n", 1)[0].strip()
+ if not self.link and (reply_to := self.message.reply_to_message):
+ if reply_to.text:
+ self.link = reply_to.text.split("\n", 1)[0].strip()
if is_telegram_link(self.link):
try:
reply_to, session = await get_tg_link_message(self.link)
except Exception as e:
- x = await send_message(self.message, f"ERROR: {e}")
+ await send_message(self.message, f"ERROR: {e}")
await self.remove_from_same_dir()
- await delete_links(self.message)
- return await five_minute_del(x)
+ return
if isinstance(reply_to, list):
self.bulk = reply_to
@@ -270,7 +249,7 @@ async def new_event(self):
self.multi_tag,
self.options,
).new_event()
- return await delete_links(self.message)
+ return
if reply_to:
file_ = (
@@ -292,18 +271,11 @@ async def new_event(self):
reply_to = None
elif reply_to.document and (
file_.mime_type == "application/x-bittorrent"
- or file_.file_name.endswith((".torrent", ".dlc"))
+ or file_.file_name.endswith((".torrent", ".dlc", ".nzb"))
):
self.link = await reply_to.download()
file_ = None
- if self.link and (
- is_magnet(self.link)
- or self.link.endswith(".torrent")
- or (file_ and file_.file_name.endswith(".torrent"))
- ):
- self.is_qbit = True
-
if (
(not self.link and file_ is None)
or (is_telegram_link(self.link) and reply_to is None)
@@ -317,14 +289,13 @@ async def new_event(self):
and not is_gdrive_link(self.link)
)
):
- x = await send_message(
+ await send_message(
self.message,
COMMAND_USAGE["mirror"][0],
COMMAND_USAGE["mirror"][1],
)
await self.remove_from_same_dir()
- await delete_links(self.message)
- return await five_minute_del(x)
+ return
if len(self.link) > 0:
LOGGER.info(self.link)
@@ -332,10 +303,9 @@ async def new_event(self):
try:
await self.before_start()
except Exception as e:
- x = await send_message(self.message, e)
+ await send_message(self.message, e)
await self.remove_from_same_dir()
- await delete_links(self.message)
- return await five_minute_del(x)
+ return
if (
not self.is_qbit
@@ -348,8 +318,7 @@ async def new_event(self):
):
content_type = await get_content_type(self.link)
if content_type is None or re_match(
- r"text/html|text/plain",
- content_type,
+ r"text/html|text/plain", content_type
):
try:
self.link = await sync_to_async(direct_link_generator, self.link)
@@ -362,74 +331,46 @@ async def new_event(self):
if "This link requires a password!" not in e:
LOGGER.info(e)
if e.startswith("ERROR:"):
- x = await send_message(self.message, e)
+ await send_message(self.message, e)
await self.remove_from_same_dir()
- await delete_links(self.message)
- return await five_minute_del(x)
+ return
if file_ is not None:
- create_task(
- TelegramDownloadHelper(self).add_download(
- reply_to,
- f"{path}/",
- session,
- ),
- )
- return None
- if isinstance(self.link, dict):
- create_task(add_direct_download(self, path))
- return None
- if self.is_qbit:
- create_task(add_qb_torrent(self, path, ratio, seed_time))
- return None
- if is_rclone_path(self.link):
- create_task(add_rclone_download(self, f"{path}/"))
- return None
- if is_gdrive_link(self.link) or is_gdrive_id(self.link):
- create_task(add_gd_download(self, path))
- return None
- if is_mega_link(self.link):
- create_task(
- add_mega_download(
- self,
- f"{path}/",
- ),
+ await TelegramDownloadHelper(self).add_download(
+ reply_to,
+ f"{path}/",
+ session,
)
- return None
- ussr = args["-au"]
- pssw = args["-ap"]
- if ussr or pssw:
- auth = f"{ussr}:{pssw}"
- headers += (
- f" authorization: Basic {b64encode(auth.encode()).decode('ascii')}"
- )
- create_task(add_aria2c_download(self, path, headers, ratio, seed_time))
- return None
+ elif isinstance(self.link, dict):
+ await add_direct_download(self, path)
+ elif self.is_qbit:
+ await add_qb_torrent(self, path, ratio, seed_time)
+ elif is_rclone_path(self.link):
+ await add_rclone_download(self, f"{path}/")
+ elif is_gdrive_link(self.link) or is_gdrive_id(self.link):
+ await add_gd_download(self, path)
+ else:
+ ussr = args["-au"]
+ pssw = args["-ap"]
+ if ussr or pssw:
+ auth = f"{ussr}:{pssw}"
+ headers += f" authorization: Basic {b64encode(auth.encode()).decode('ascii')}"
+ await add_aria2c_download(self, path, headers, ratio, seed_time)
async def mirror(client, message):
bot_loop.create_task(Mirror(client, message).new_event())
+async def qb_mirror(client, message):
+ bot_loop.create_task(Mirror(client, message, is_qbit=True).new_event())
+
+
async def leech(client, message):
bot_loop.create_task(Mirror(client, message, is_leech=True).new_event())
-bot.add_handler(
- MessageHandler(
- mirror,
- filters=command(
- BotCommands.MirrorCommand,
- )
- & CustomFilters.authorized,
- ),
-)
-bot.add_handler(
- MessageHandler(
- leech,
- filters=command(
- BotCommands.LeechCommand,
- )
- & CustomFilters.authorized,
- ),
-)
+async def qb_leech(client, message):
+ bot_loop.create_task(
+ Mirror(client, message, is_qbit=True, is_leech=True).new_event(),
+ )
diff --git a/bot/modules/restart.py b/bot/modules/restart.py
new file mode 100644
index 000000000..7bb85d1cc
--- /dev/null
+++ b/bot/modules/restart.py
@@ -0,0 +1,93 @@
+import contextlib
+from asyncio import create_subprocess_exec, gather
+from os import execl as osexecl
+from sys import executable
+
+from aiofiles import open as aiopen
+from aiofiles.os import path as aiopath
+from aiofiles.os import remove
+
+from bot import LOGGER, intervals, scheduler
+from bot.core.config_manager import Config
+from bot.core.aeon_client import TgClient
+from bot.helper.ext_utils.bot_utils import new_task, sync_to_async
+from bot.helper.ext_utils.db_handler import database
+from bot.helper.ext_utils.files_utils import clean_all
+from bot.helper.telegram_helper.message_utils import send_message
+
+
+@new_task
+async def restart_bot(_, message):
+ intervals["stopAll"] = True
+ restart_message = await send_message(message, "Restarting...")
+ if scheduler.running:
+ scheduler.shutdown(wait=False)
+ if qb := intervals["qb"]:
+ qb.cancel()
+ if st := intervals["status"]:
+ for intvl in list(st.values()):
+ intvl.cancel()
+ await sync_to_async(clean_all)
+ proc1 = await create_subprocess_exec(
+ "pkill",
+ "-9",
+ "-f",
+ "gunicorn|aria2c|qbittorrent-nox|ffmpeg|rclone|java|sabnzbdplus",
+ )
+ proc2 = await create_subprocess_exec("python3", "update.py")
+ await gather(proc1.wait(), proc2.wait())
+ async with aiopen(".restartmsg", "w") as f:
+ await f.write(f"{restart_message.chat.id}\n{restart_message.id}\n")
+ osexecl(executable, executable, "-m", "bot")
+
+
+async def restart_notification():
+ if await aiopath.isfile(".restartmsg"):
+ with open(".restartmsg") as f:
+ chat_id, msg_id = map(int, f)
+ else:
+ chat_id, msg_id = 0, 0
+
+ async def send_incomplete_task_message(cid, msg):
+ try:
+ if msg.startswith("Restarted Successfully!"):
+ await TgClient.bot.edit_message_text(
+ chat_id=chat_id,
+ message_id=msg_id,
+ text=msg,
+ )
+ 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:
+ LOGGER.error(e)
+
+ if Config.INCOMPLETE_TASK_NOTIFIER and Config.DATABASE_URL:
+ if notifier_dict := await database.get_incomplete_tasks():
+ for cid, data in notifier_dict.items():
+ msg = (
+ "Restarted Successfully!" if cid == chat_id else "Bot Restarted!"
+ )
+ for tag, links in data.items():
+ msg += f"\n\n{tag}: "
+ for index, link in enumerate(links, start=1):
+ msg += f" {index} |"
+ if len(msg.encode()) > 4000:
+ await send_incomplete_task_message(cid, msg)
+ msg = ""
+ if msg:
+ await send_incomplete_task_message(cid, msg)
+
+ if await aiopath.isfile(".restartmsg"):
+ with contextlib.suppress(Exception):
+ await TgClient.bot.edit_message_text(
+ chat_id=chat_id,
+ message_id=msg_id,
+ text="Restarted Successfully!",
+ )
+ await remove(".restartmsg")
diff --git a/bot/modules/rss.py b/bot/modules/rss.py
new file mode 100644
index 000000000..f6fb26b7e
--- /dev/null
+++ b/bot/modules/rss.py
@@ -0,0 +1,812 @@
+from asyncio import Lock, sleep
+from datetime import datetime, timedelta
+from functools import partial
+from io import BytesIO
+from time import time
+
+from apscheduler.triggers.interval import IntervalTrigger
+from feedparser import parse as feed_parse
+from httpx import AsyncClient
+from pyrogram.filters import create
+from pyrogram.handlers import MessageHandler
+
+from bot import LOGGER, rss_dict, scheduler
+from bot.core.config_manager import Config
+from bot.helper.ext_utils.bot_utils import arg_parser, new_task
+from bot.helper.ext_utils.db_handler import database
+from bot.helper.ext_utils.exceptions import RssShutdownException
+from bot.helper.ext_utils.help_messages import RSS_HELP_MESSAGE
+from bot.helper.telegram_helper.button_build import ButtonMaker
+from bot.helper.telegram_helper.filters import CustomFilters
+from bot.helper.telegram_helper.message_utils import (
+ delete_message,
+ edit_message,
+ send_file,
+ send_message,
+ send_rss,
+)
+
+rss_dict_lock = Lock()
+handler_dict = {}
+
+headers = {
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36",
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
+ "Accept-Language": "en-US,en;q=0.5",
+}
+
+
+async def rss_menu(event):
+ user_id = event.from_user.id
+ buttons = ButtonMaker()
+ buttons.data_button("Subscribe", f"rss sub {user_id}")
+ buttons.data_button("Subscriptions", f"rss list {user_id} 0")
+ buttons.data_button("Get Items", f"rss get {user_id}")
+ buttons.data_button("Edit", f"rss edit {user_id}")
+ buttons.data_button("Pause", f"rss pause {user_id}")
+ buttons.data_button("Resume", f"rss resume {user_id}")
+ buttons.data_button("Unsubscribe", f"rss unsubscribe {user_id}")
+ if await CustomFilters.sudo("", event):
+ buttons.data_button("All Subscriptions", f"rss listall {user_id} 0")
+ buttons.data_button("Pause All", f"rss allpause {user_id}")
+ buttons.data_button("Resume All", f"rss allresume {user_id}")
+ buttons.data_button("Unsubscribe All", f"rss allunsub {user_id}")
+ buttons.data_button("Delete User", f"rss deluser {user_id}")
+ if scheduler.running:
+ buttons.data_button("Shutdown Rss", f"rss shutdown {user_id}")
+ else:
+ buttons.data_button("Start Rss", f"rss start {user_id}")
+ buttons.data_button("Close", f"rss close {user_id}")
+ button = buttons.build_menu(2)
+ msg = f"Rss Menu | Users: {len(rss_dict)} | Running: {scheduler.running}"
+ return msg, button
+
+
+async def update_rss_menu(query):
+ msg, button = await rss_menu(query)
+ await edit_message(query.message, msg, button)
+
+
+@new_task
+async def get_rss_menu(_, message):
+ msg, button = await rss_menu(message)
+ await send_message(message, msg, button)
+
+
+@new_task
+async def rss_sub(_, message, pre_event):
+ user_id = message.from_user.id
+ handler_dict[user_id] = False
+ if username := message.from_user.username:
+ tag = f"@{username}"
+ else:
+ tag = message.from_user.mention
+ msg = ""
+ items = message.text.split("\n")
+ for index, item in enumerate(items, start=1):
+ args = item.split()
+ if len(args) < 2:
+ await send_message(
+ message,
+ f"{item}. Wrong Input format. Read help message before adding new subcription!",
+ )
+ continue
+ title = args[0].strip()
+ if (user_feeds := rss_dict.get(user_id, False)) and title in user_feeds:
+ await send_message(
+ message,
+ f"This title {title} already subscribed! Choose another title!",
+ )
+ continue
+ feed_link = args[1].strip()
+ if feed_link.startswith(("-inf", "-exf", "-c")):
+ await send_message(
+ message,
+ f"Wrong input in line {index}! Add Title! Read the example!",
+ )
+ continue
+ inf_lists = []
+ exf_lists = []
+ if len(args) > 2:
+ arg_base = {"-c": None, "-inf": None, "-exf": None, "-stv": None}
+ arg_parser(args[2:], arg_base)
+ cmd = arg_base["-c"]
+ inf = arg_base["-inf"]
+ exf = arg_base["-exf"]
+ stv = arg_base["-stv"]
+ if stv is not None:
+ stv = stv.lower() == "true"
+ if inf is not None:
+ filters_list = inf.split("|")
+ for x in filters_list:
+ y = x.split(" or ")
+ inf_lists.append(y)
+ if exf is not None:
+ filters_list = exf.split("|")
+ for x in filters_list:
+ y = x.split(" or ")
+ exf_lists.append(y)
+ else:
+ inf = None
+ exf = None
+ cmd = None
+ stv = False
+ try:
+ async with AsyncClient(
+ headers=headers,
+ follow_redirects=True,
+ timeout=60,
+ verify=False,
+ ) as client:
+ res = await client.get(feed_link)
+ html = res.text
+ rss_d = feed_parse(html)
+ last_title = rss_d.entries[0]["title"]
+ msg += "Subscribed!"
+ msg += (
+ f"\nTitle: {title}
\nFeed Url: {feed_link}"
+ )
+ msg += f"\nlatest record for {rss_d.feed.title}:"
+ msg += f"\nName: {last_title.replace('>', '').replace('<', '')}
"
+ try:
+ last_link = rss_d.entries[0]["links"][1]["href"]
+ except IndexError:
+ last_link = rss_d.entries[0]["link"]
+ msg += f"\nLink: {last_link}
"
+ msg += f"\nCommand: {cmd}
"
+ msg += f"\nFilters:-\ninf: {inf}
\nexf: {exf}
\nsensitive: {stv}"
+ async with rss_dict_lock:
+ if rss_dict.get(user_id, False):
+ rss_dict[user_id][title] = {
+ "link": feed_link,
+ "last_feed": last_link,
+ "last_title": last_title,
+ "inf": inf_lists,
+ "exf": exf_lists,
+ "paused": False,
+ "command": cmd,
+ "sensitive": stv,
+ "tag": tag,
+ }
+ else:
+ rss_dict[user_id] = {
+ title: {
+ "link": feed_link,
+ "last_feed": last_link,
+ "last_title": last_title,
+ "inf": inf_lists,
+ "exf": exf_lists,
+ "paused": False,
+ "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}",
+ )
+ except (IndexError, AttributeError) as e:
+ emsg = f"The link: {feed_link} doesn't seem to be a RSS feed or it's region-blocked!"
+ await send_message(message, emsg + "\nError: " + str(e))
+ except Exception as e:
+ await send_message(message, str(e))
+ if msg:
+ await database.rss_update(user_id)
+ await send_message(message, msg)
+ is_sudo = await CustomFilters.sudo("", message)
+ if scheduler.state == 2:
+ scheduler.resume()
+ elif is_sudo and not scheduler.running:
+ add_job()
+ scheduler.start()
+ await update_rss_menu(pre_event)
+
+
+async def get_user_id(title):
+ async with rss_dict_lock:
+ return next(
+ (
+ (True, user_id)
+ for user_id, feed in rss_dict.items()
+ if feed["title"] == title
+ ),
+ (False, False),
+ )
+
+
+@new_task
+async def rss_update(_, message, pre_event, state):
+ user_id = message.from_user.id
+ handler_dict[user_id] = False
+ titles = message.text.split()
+ is_sudo = await CustomFilters.sudo("", message)
+ updated = []
+ for title in titles:
+ title = title.strip()
+ if not (res := rss_dict[user_id].get(title, False)):
+ if is_sudo:
+ res, user_id = await get_user_id(title)
+ if not res:
+ user_id = message.from_user.id
+ await send_message(message, f"{title} not found!")
+ continue
+ istate = rss_dict[user_id][title].get("paused", False)
+ if (istate and state == "pause") or (not istate and state == "resume"):
+ await send_message(message, f"{title} already {state}d!")
+ continue
+ async with rss_dict_lock:
+ updated.append(title)
+ if state == "unsubscribe":
+ del rss_dict[user_id][title]
+ elif state == "pause":
+ rss_dict[user_id][title]["paused"] = True
+ elif state == "resume":
+ rss_dict[user_id][title]["paused"] = False
+ if state == "resume":
+ if scheduler.state == 2:
+ scheduler.resume()
+ elif is_sudo and not scheduler.running:
+ add_job()
+ scheduler.start()
+ if is_sudo and Config.DATABASE_URL and user_id != message.from_user.id:
+ await database.rss_update(user_id)
+ if not rss_dict[user_id]:
+ async with rss_dict_lock:
+ del rss_dict[user_id]
+ await database.rss_delete(user_id)
+ if not rss_dict:
+ await database.trunc_table("rss")
+ if updated:
+ LOGGER.info(f"Rss link with Title(s): {updated} has been {state}d!")
+ await send_message(
+ message,
+ f"Rss links with Title(s): {updated}
has been {state}d!",
+ )
+ if rss_dict.get(user_id):
+ await database.rss_update(user_id)
+ await update_rss_menu(pre_event)
+
+
+async def rss_list(query, start, all_users=False):
+ user_id = query.from_user.id
+ buttons = ButtonMaker()
+ if all_users:
+ list_feed = f"All subscriptions | Page: {int(start / 5)} "
+ async with rss_dict_lock:
+ keysCount = sum(len(v.keys()) for v in rss_dict.values())
+ index = 0
+ for titles in rss_dict.values():
+ for index, (title, data) in enumerate(
+ list(titles.items())[start : 5 + start],
+ ):
+ list_feed += f"\n\nTitle: {title}
\n"
+ list_feed += f"Feed Url: {data['link']}
\n"
+ list_feed += f"Command: {data['command']}
\n"
+ list_feed += f"Inf: {data['inf']}
\n"
+ list_feed += f"Exf: {data['exf']}
\n"
+ list_feed += f"Sensitive: {data.get('sensitive', False)}
\n"
+ list_feed += f"Paused: {data['paused']}
\n"
+ list_feed += f"User: {data['tag'].replace('@', '', 1)}"
+ index += 1
+ if index == 5:
+ break
+ else:
+ list_feed = f"Your subscriptions | Page: {int(start / 5)} "
+ async with rss_dict_lock:
+ keysCount = len(rss_dict.get(user_id, {}).keys())
+ for title, data in list(rss_dict[user_id].items())[start : 5 + start]:
+ list_feed += f"\n\nTitle: {title}
\nFeed Url: {data['link']}
\n"
+ list_feed += f"Command: {data['command']}
\n"
+ list_feed += f"Inf: {data['inf']}
\n"
+ list_feed += f"Exf: {data['exf']}
\n"
+ list_feed += f"Sensitive: {data.get('sensitive', False)}
\n"
+ list_feed += f"Paused: {data['paused']}
\n"
+ buttons.data_button("Back", f"rss back {user_id}")
+ buttons.data_button("Close", f"rss close {user_id}")
+ 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",
+ )
+ button = buttons.build_menu(2)
+ if query.message.text.html == list_feed:
+ return
+ await edit_message(query.message, list_feed, button)
+
+
+@new_task
+async def rss_get(_, message, pre_event):
+ user_id = message.from_user.id
+ handler_dict[user_id] = False
+ args = message.text.split()
+ 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!",
+ )
+ await update_rss_menu(pre_event)
+ return
+ try:
+ title = args[0]
+ count = int(args[1])
+ data = rss_dict[user_id].get(title, False)
+ if data and count > 0:
+ try:
+ msg = await send_message(
+ message,
+ f"Getting the last {count} item(s) from {title}",
+ )
+ async with AsyncClient(
+ headers=headers,
+ follow_redirects=True,
+ timeout=60,
+ verify=False,
+ ) as client:
+ res = await client.get(data["link"])
+ html = res.text
+ rss_d = feed_parse(html)
+ item_info = ""
+ for item_num in range(count):
+ try:
+ link = rss_d.entries[item_num]["links"][1]["href"]
+ except IndexError:
+ link = rss_d.entries[item_num]["link"]
+ item_info += f"Name: {rss_d.entries[item_num]['title'].replace('>', '').replace('<', '')}
\n"
+ item_info += f"Link: {link}
\n\n"
+ item_info_ecd = item_info.encode()
+ if len(item_info_ecd) > 4000:
+ with BytesIO(item_info_ecd) as out_file:
+ out_file.name = f"rssGet {title} items_no. {count}.txt"
+ await send_file(message, out_file)
+ await delete_message(msg)
+ else:
+ await edit_message(msg, item_info)
+ except IndexError as e:
+ LOGGER.error(str(e))
+ await edit_message(
+ msg,
+ "Parse depth exceeded. Try again with a lower value.",
+ )
+ except Exception as e:
+ LOGGER.error(str(e))
+ await edit_message(msg, str(e))
+ else:
+ await send_message(message, "Enter a valid title. Title not found!")
+ except Exception as e:
+ LOGGER.error(str(e))
+ await send_message(message, f"Enter a valid value!. {e}")
+ await update_rss_menu(pre_event)
+
+
+@new_task
+async def rss_edit(_, message, pre_event):
+ user_id = message.from_user.id
+ handler_dict[user_id] = False
+ items = message.text.split("\n")
+ updated = False
+ for item in items:
+ args = item.split()
+ title = args[0].strip()
+ if len(args) < 2:
+ await send_message(
+ message,
+ f"{item}. Wrong Input format. Read help message before editing!",
+ )
+ continue
+ if not rss_dict[user_id].get(title, False):
+ await send_message(message, "Enter a valid title. Title not found!")
+ continue
+ updated = True
+ inf_lists = []
+ exf_lists = []
+ arg_base = {"-c": None, "-inf": None, "-exf": None, "-stv": None}
+ arg_parser(args[1:], arg_base)
+ cmd = arg_base["-c"]
+ inf = arg_base["-inf"]
+ exf = arg_base["-exf"]
+ stv = arg_base["-stv"]
+ async with rss_dict_lock:
+ if stv is not None:
+ stv = stv.lower() == "true"
+ rss_dict[user_id][title]["sensitive"] = stv
+ if cmd is not None:
+ if cmd.lower() == "none":
+ cmd = None
+ rss_dict[user_id][title]["command"] = cmd
+ if inf is not None:
+ if inf.lower() != "none":
+ filters_list = inf.split("|")
+ for x in filters_list:
+ y = x.split(" or ")
+ inf_lists.append(y)
+ rss_dict[user_id][title]["inf"] = inf_lists
+ if exf is not None:
+ if exf.lower() != "none":
+ filters_list = exf.split("|")
+ for x in filters_list:
+ y = x.split(" or ")
+ exf_lists.append(y)
+ rss_dict[user_id][title]["exf"] = exf_lists
+ if updated:
+ await database.rss_update(user_id)
+ await update_rss_menu(pre_event)
+
+
+@new_task
+async def rss_delete(_, message, pre_event):
+ handler_dict[message.from_user.id] = False
+ users = message.text.split()
+ for user in users:
+ user = int(user)
+ async with rss_dict_lock:
+ del rss_dict[user]
+ await database.rss_delete(user)
+ await update_rss_menu(pre_event)
+
+
+async def event_handler(client, query, pfunc):
+ user_id = query.from_user.id
+ handler_dict[user_id] = True
+ start_time = time()
+
+ async def event_filter(_, __, event):
+ user = event.from_user or event.sender_chat
+ return bool(
+ user.id == user_id
+ and event.chat.id == query.message.chat.id
+ and event.text,
+ )
+
+ handler = client.add_handler(
+ MessageHandler(pfunc, create(event_filter)), group=-1
+ )
+ while handler_dict[user_id]:
+ await sleep(0.5)
+ if time() - start_time > 60:
+ handler_dict[user_id] = False
+ await update_rss_menu(query)
+ client.remove_handler(*handler)
+
+
+@new_task
+async def rss_listener(client, query):
+ user_id = query.from_user.id
+ message = query.message
+ 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,
+ )
+ elif data[1] == "close":
+ await query.answer()
+ handler_dict[user_id] = False
+ await delete_message(message.reply_to_message)
+ await delete_message(message)
+ elif data[1] == "back":
+ await query.answer()
+ handler_dict[user_id] = False
+ await update_rss_menu(query)
+ elif data[1] == "sub":
+ await query.answer()
+ handler_dict[user_id] = False
+ buttons = ButtonMaker()
+ buttons.data_button("Back", f"rss back {user_id}")
+ buttons.data_button("Close", f"rss close {user_id}")
+ button = buttons.build_menu(2)
+ await edit_message(message, RSS_HELP_MESSAGE, button)
+ pfunc = partial(rss_sub, pre_event=query)
+ await event_handler(client, query, pfunc)
+ elif data[1] == "list":
+ handler_dict[user_id] = False
+ if len(rss_dict.get(int(data[2]), {})) == 0:
+ await query.answer(text="No subscriptions!", show_alert=True)
+ else:
+ await query.answer()
+ start = int(data[3])
+ await rss_list(query, start)
+ elif data[1] == "get":
+ handler_dict[user_id] = False
+ if len(rss_dict.get(int(data[2]), {})) == 0:
+ await query.answer(text="No subscriptions!", show_alert=True)
+ else:
+ await query.answer()
+ buttons = ButtonMaker()
+ buttons.data_button("Back", f"rss back {user_id}")
+ buttons.data_button("Close", f"rss close {user_id}")
+ button = buttons.build_menu(2)
+ await edit_message(
+ message,
+ "Send one title with value separated by space get last X items.\nTitle Value\nTimeout: 60 sec.",
+ button,
+ )
+ pfunc = partial(rss_get, pre_event=query)
+ await event_handler(client, query, pfunc)
+ elif data[1] in ["unsubscribe", "pause", "resume"]:
+ handler_dict[user_id] = False
+ if len(rss_dict.get(int(data[2]), {})) == 0:
+ await query.answer(text="No subscriptions!", show_alert=True)
+ else:
+ await query.answer()
+ buttons = ButtonMaker()
+ buttons.data_button("Back", f"rss back {user_id}")
+ if data[1] == "pause":
+ buttons.data_button("Pause AllMyFeeds", f"rss uallpause {user_id}")
+ elif data[1] == "resume":
+ buttons.data_button("Resume AllMyFeeds", f"rss uallresume {user_id}")
+ elif data[1] == "unsubscribe":
+ buttons.data_button("Unsub AllMyFeeds", f"rss uallunsub {user_id}")
+ buttons.data_button("Close", f"rss close {user_id}")
+ button = buttons.build_menu(2)
+ await edit_message(
+ message,
+ f"Send one or more rss titles separated by space to {data[1]}.\nTimeout: 60 sec.",
+ button,
+ )
+ pfunc = partial(rss_update, pre_event=query, state=data[1])
+ await event_handler(client, query, pfunc)
+ elif data[1] == "edit":
+ handler_dict[user_id] = False
+ if len(rss_dict.get(int(data[2]), {})) == 0:
+ await query.answer(text="No subscriptions!", show_alert=True)
+ else:
+ await query.answer()
+ buttons = ButtonMaker()
+ buttons.data_button("Back", f"rss back {user_id}")
+ buttons.data_button("Close", f"rss close {user_id}")
+ button = buttons.build_menu(2)
+ msg = """Send one or more rss titles with new filters or command separated by new line.
+Examples:
+Title1 -c mirror -up remote:path/subdir -exf none -inf 1080 or 720 -stv true
+Title2 -c none -inf none -stv false
+Title3 -c mirror -rcf xxx -up xxx -z pswd -stv false
+Note: Only what you provide will be edited, the rest will be the same like example 2: exf will stay same as it is.
+Timeout: 60 sec. Argument -c for command and arguments
+ """
+ await edit_message(message, msg, button)
+ pfunc = partial(rss_edit, pre_event=query)
+ await event_handler(client, query, pfunc)
+ elif data[1].startswith("uall"):
+ handler_dict[user_id] = False
+ if len(rss_dict.get(int(data[2]), {})) == 0:
+ await query.answer(text="No subscriptions!", show_alert=True)
+ return
+ await query.answer()
+ if data[1].endswith("unsub"):
+ async with rss_dict_lock:
+ del rss_dict[int(data[2])]
+ await database.rss_delete(int(data[2]))
+ 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
+ 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
+ if scheduler.state == 2:
+ scheduler.resume()
+ await database.rss_update(int(data[2]))
+ await update_rss_menu(query)
+ elif data[1].startswith("all"):
+ if len(rss_dict) == 0:
+ await query.answer(text="No subscriptions!", show_alert=True)
+ return
+ await query.answer()
+ if data[1].endswith("unsub"):
+ async with rss_dict_lock:
+ rss_dict.clear()
+ await database.trunc_table("rss")
+ 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
+ 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
+ if scheduler.state == 2:
+ scheduler.resume()
+ elif not scheduler.running:
+ add_job()
+ scheduler.start()
+ await database.rss_update_all()
+ elif data[1] == "deluser":
+ if len(rss_dict) == 0:
+ await query.answer(text="No subscriptions!", show_alert=True)
+ else:
+ await query.answer()
+ buttons = ButtonMaker()
+ buttons.data_button("Back", f"rss back {user_id}")
+ buttons.data_button("Close", f"rss close {user_id}")
+ button = buttons.build_menu(2)
+ msg = "Send one or more user_id separated by space to delete their resources.\nTimeout: 60 sec."
+ await edit_message(message, msg, button)
+ pfunc = partial(rss_delete, pre_event=query)
+ await event_handler(client, query, pfunc)
+ elif data[1] == "listall":
+ if not rss_dict:
+ await query.answer(text="No subscriptions!", show_alert=True)
+ else:
+ await query.answer()
+ start = int(data[3])
+ await rss_list(query, start, all_users=True)
+ elif data[1] == "shutdown":
+ if scheduler.running:
+ await query.answer()
+ scheduler.shutdown(wait=False)
+ await sleep(0.5)
+ await update_rss_menu(query)
+ else:
+ await query.answer(text="Already Stopped!", show_alert=True)
+ elif data[1] == "start":
+ if not scheduler.running:
+ await query.answer()
+ add_job()
+ scheduler.start()
+ await update_rss_menu(query)
+ else:
+ await query.answer(text="Already Running!", show_alert=True)
+
+
+async def rss_monitor():
+ chat = Config.RSS_CHAT
+ if not chat:
+ LOGGER.warning("RSS_CHAT not added! Shutting down rss scheduler...")
+ scheduler.shutdown(wait=False)
+ return
+ if len(rss_dict) == 0:
+ scheduler.pause()
+ return
+ all_paused = True
+ rss_topic_id = rss_chat_id = None
+ if isinstance(chat, int):
+ rss_chat_id = chat
+ elif "|" in chat:
+ rss_chat_id, rss_topic_id = [
+ int(x) if x.lstrip("-").isdigit() else x for x in chat.split("|", 1)
+ ]
+ elif chat.lstrip("-").isdigit():
+ rss_chat_id = int(chat)
+ for user, items in list(rss_dict.items()):
+ for title, data in items.items():
+ try:
+ if data["paused"]:
+ continue
+ tries = 0
+ while True:
+ try:
+ async with AsyncClient(
+ headers=headers,
+ follow_redirects=True,
+ timeout=60,
+ verify=False,
+ ) as client:
+ res = await client.get(data["link"])
+ html = res.text
+ break
+ except:
+ tries += 1
+ if tries > 3:
+ 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 (
+ data["last_feed"] == last_link
+ or data["last_title"] == last_title
+ ):
+ continue
+ feed_count = 0
+ while True:
+ try:
+ await sleep(10)
+ except:
+ raise RssShutdownException("Rss Monitor Stopped!")
+ try:
+ item_title = rss_d.entries[feed_count]["title"]
+ try:
+ url = rss_d.entries[feed_count]["links"][1]["href"]
+ except IndexError:
+ url = rss_d.entries[feed_count]["link"]
+ if (
+ data["last_feed"] == url
+ or data["last_title"] == item_title
+ ):
+ break
+ 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",
+ )
+ break
+ parse = True
+ for flist in data["inf"]:
+ if (
+ data.get("sensitive", False)
+ and all(
+ x.lower() not in item_title.lower() for x in flist
+ )
+ ) or (
+ not data.get("sensitive", False)
+ and all(x not in item_title for x in flist)
+ ):
+ parse = False
+ feed_count += 1
+ break
+ if not parse:
+ continue
+ for flist in data["exf"]:
+ if (
+ data.get("sensitive", False)
+ and any(x.lower() in item_title.lower() for x in flist)
+ ) or (
+ not data.get("sensitive", False)
+ and any(x in item_title for x in flist)
+ ):
+ parse = False
+ feed_count += 1
+ break
+ if not parse:
+ continue
+ if command := data["command"]:
+ cmd = command.split(maxsplit=1)
+ cmd.insert(1, url)
+ feed_msg = " ".join(cmd)
+ if not feed_msg.startswith("/"):
+ feed_msg = f"/{feed_msg}"
+ else:
+ feed_msg = f"Name: {item_title.replace('>', '').replace('<', '')}
\n\n"
+ feed_msg += f"Link: {url}
"
+ feed_msg += f"\nTag: {data['tag']}
{user}
"
+ await send_rss(feed_msg, rss_chat_id, rss_topic_id)
+ feed_count += 1
+ async with rss_dict_lock:
+ 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},
+ )
+ 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
+ if all_paused:
+ scheduler.pause()
+
+
+def add_job():
+ scheduler.add_job(
+ rss_monitor,
+ trigger=IntervalTrigger(seconds=Config.RSS_DELAY),
+ id="0",
+ name="RSS",
+ misfire_grace_time=15,
+ max_instances=1,
+ next_run_time=datetime.now() + timedelta(seconds=20),
+ replace_existing=True,
+ )
+
+
+add_job()
+scheduler.start()
diff --git a/bot/modules/search.py b/bot/modules/search.py
new file mode 100644
index 000000000..6f8dbefb6
--- /dev/null
+++ b/bot/modules/search.py
@@ -0,0 +1,323 @@
+import contextlib
+from html import escape
+from urllib.parse import quote
+
+from httpx import AsyncClient
+
+from bot import LOGGER, xnox_client
+from bot.core.config_manager import Config
+from bot.helper.ext_utils.bot_utils import new_task, sync_to_async
+from bot.helper.ext_utils.status_utils import get_readable_file_size
+from bot.helper.ext_utils.telegraph_helper import telegraph
+from bot.helper.telegram_helper.button_build import ButtonMaker
+from bot.helper.telegram_helper.message_utils import edit_message, send_message
+
+PLUGINS = []
+SITES = None
+TELEGRAPH_LIMIT = 300
+
+
+async def initiate_search_tools():
+ qb_plugins = await sync_to_async(xnox_client.search_plugins)
+ if Config.SEARCH_PLUGINS:
+ globals()["PLUGINS"] = []
+ if qb_plugins:
+ names = [plugin["name"] for plugin in qb_plugins]
+ await sync_to_async(
+ xnox_client.search_uninstall_plugin, names=names
+ )
+ await sync_to_async(
+ xnox_client.search_install_plugin,
+ Config.SEARCH_PLUGINS,
+ )
+ elif qb_plugins:
+ for plugin in qb_plugins:
+ await sync_to_async(
+ xnox_client.search_uninstall_plugin,
+ names=plugin["name"],
+ )
+ globals()["PLUGINS"] = []
+
+ if Config.SEARCH_API_LINK:
+ global SITES
+ try:
+ async with AsyncClient() as client:
+ response = await client.get(f"{Config.SEARCH_API_LINK}/api/v1/sites")
+ data = response.json()
+ SITES = {
+ str(site): str(site).capitalize() for site in data["supported_sites"]
+ }
+ SITES["all"] = "All"
+ except Exception as e:
+ LOGGER.error(
+ f"{e} Can't fetching sites from SEARCH_API_LINK make sure use latest version of API",
+ )
+ SITES = None
+
+
+async def search(key, site, message, method):
+ if method.startswith("api"):
+ if method == "apisearch":
+ LOGGER.info(f"API Searching: {key} from {site}")
+ if site == "all":
+ api = f"{Config.SEARCH_API_LINK}/api/v1/all/search?query={key}&limit={Config.SEARCH_LIMIT}"
+ else:
+ api = f"{Config.SEARCH_API_LINK}/api/v1/search?site={site}&query={key}&limit={Config.SEARCH_LIMIT}"
+ elif method == "apitrend":
+ LOGGER.info(f"API Trending from {site}")
+ if site == "all":
+ api = f"{Config.SEARCH_API_LINK}/api/v1/all/trending?limit={Config.SEARCH_LIMIT}"
+ else:
+ api = f"{Config.SEARCH_API_LINK}/api/v1/trending?site={site}&limit={Config.SEARCH_LIMIT}"
+ elif method == "apirecent":
+ LOGGER.info(f"API Recent from {site}")
+ if site == "all":
+ api = f"{Config.SEARCH_API_LINK}/api/v1/all/recent?limit={Config.SEARCH_LIMIT}"
+ else:
+ api = f"{Config.SEARCH_API_LINK}/api/v1/recent?site={site}&limit={Config.SEARCH_LIMIT}"
+ try:
+ async with AsyncClient() as client:
+ response = await client.get(api)
+ search_results = response.json()
+ if "error" in search_results or search_results["total"] == 0:
+ await edit_message(
+ message,
+ f"No result found for {key}\nTorrent Site:- {SITES.get(site)}",
+ )
+ return
+ msg = f"Found {min(search_results['total'], TELEGRAPH_LIMIT)}"
+ if method == "apitrend":
+ msg += f" trending result(s)\nTorrent Site:- {SITES.get(site)}"
+ elif method == "apirecent":
+ msg += f" recent result(s)\nTorrent Site:- {SITES.get(site)}"
+ else:
+ msg += f" result(s) for {key}\nTorrent Site:- {SITES.get(site)}"
+ search_results = search_results["data"]
+ except Exception as e:
+ await edit_message(message, str(e))
+ return
+ else:
+ LOGGER.info(f"PLUGINS Searching: {key} from {site}")
+ search = await sync_to_async(
+ xnox_client.search_start,
+ pattern=key,
+ plugins=site,
+ category="all",
+ )
+ search_id = search.id
+ while True:
+ result_status = await sync_to_async(
+ xnox_client.search_status,
+ search_id=search_id,
+ )
+ status = result_status[0].status
+ if status != "Running":
+ break
+ dict_search_results = await sync_to_async(
+ xnox_client.search_results,
+ search_id=search_id,
+ limit=TELEGRAPH_LIMIT,
+ )
+ search_results = dict_search_results.results
+ total_results = dict_search_results.total
+ if total_results == 0:
+ await edit_message(
+ message,
+ f"No result found for {key}\nTorrent Site:- {site.capitalize()}",
+ )
+ return
+ msg = f"Found {min(total_results, TELEGRAPH_LIMIT)}"
+ msg += f" result(s) for {key}\nTorrent Site:- {site.capitalize()}"
+ await sync_to_async(xnox_client.search_delete, search_id=search_id)
+ link = await get_result(search_results, key, message, method)
+ buttons = ButtonMaker()
+ buttons.url_button("đ VIEW", link)
+ button = buttons.build_menu(1)
+ await edit_message(message, msg, button)
+
+
+async def get_result(search_results, key, message, method):
+ telegraph_content = []
+ if method == "apirecent":
+ msg = "{escape(result['name'])}
{escape(lprefix)}
Leech Destination is {leech_dest}
Leech by {leech_method} session
@@ -196,7 +203,7 @@ async def get_user_settings(from_user):
YT-DLP Options is {escape(ytopt)}
FFMPEG Commands is {ffc}
"""
- return text, buttons.build_menu(2)
+ return text, buttons.build_menu(1)
async def update_user_settings(query):
@@ -205,7 +212,7 @@ async def update_user_settings(query):
@new_task
-async def user_settings(_, message):
+async def send_user_settings(_, message):
from_user = message.from_user
handler_dict[from_user.id] = False
msg, button = await get_user_settings(from_user)
@@ -220,8 +227,7 @@ async def set_thumb(_, message, pre_event):
update_user_ldata(user_id, "thumb", des_dir)
await delete_message(message)
await update_user_settings(pre_event)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_doc(user_id, "thumb", des_dir)
+ await database.update_user_doc(user_id, "thumb", des_dir)
@new_task
@@ -235,8 +241,7 @@ async def add_rclone(_, message, pre_event):
update_user_ldata(user_id, "rclone_config", f"rclone/{user_id}.conf")
await delete_message(message)
await update_user_settings(pre_event)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_doc(user_id, "rclone_config", des_dir)
+ await database.update_user_doc(user_id, "rclone_config", des_dir)
@new_task
@@ -250,8 +255,7 @@ async def add_token_pickle(_, message, pre_event):
update_user_ldata(user_id, "token_pickle", f"tokens/{user_id}.pickle")
await delete_message(message)
await update_user_settings(pre_event)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_doc(user_id, "token_pickle", des_dir)
+ await database.update_user_doc(user_id, "token_pickle", des_dir)
@new_task
@@ -267,8 +271,7 @@ async def delete_path(_, message, pre_event):
update_user_ldata(user_id, "upload_paths", new_value)
await delete_message(message)
await update_user_settings(pre_event)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_doc(user_id, "upload_paths", new_value)
+ await database.update_user_doc(user_id, "upload_paths", new_value)
@new_task
@@ -279,7 +282,7 @@ async def set_option(_, message, pre_event, option):
if option == "split_size":
if not value.isdigit():
value = get_size_bytes(value)
- value = min(int(value), MAX_SPLIT_SIZE)
+ value = min(int(value), TgClient.MAX_SPLIT_SIZE)
elif option == "excluded_extensions":
fx = value.split()
value = ["aria2", "!qB"]
@@ -314,8 +317,7 @@ async def set_option(_, message, pre_event, option):
update_user_ldata(user_id, option, value)
await delete_message(message)
await update_user_settings(pre_event)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(user_id)
+ await database.update_user_data(user_id)
async def event_handler(client, query, pfunc, photo=False, document=False):
@@ -364,6 +366,8 @@ async def edit_user_settings(client, query):
await query.answer("Not Yours!", show_alert=True)
elif data[2] in [
"as_doc",
+ "equal_splits",
+ "media_group",
"user_transmission",
"stop_duplicate",
"mixed_leech",
@@ -371,8 +375,7 @@ async def edit_user_settings(client, query):
update_user_ldata(user_id, data[2], data[3] == "true")
await query.answer()
await update_user_settings(query)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(user_id)
+ await database.update_user_data(user_id)
elif data[2] in ["thumb", "rclone_config", "token_pickle"]:
if data[2] == "thumb":
fpath = thumb_path
@@ -385,8 +388,7 @@ async def edit_user_settings(client, query):
await remove(fpath)
update_user_ldata(user_id, data[2], "")
await update_user_settings(query)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_doc(user_id, data[2])
+ await database.update_user_doc(user_id, data[2])
else:
await query.answer("Old Settings", show_alert=True)
await update_user_settings(query)
@@ -402,15 +404,13 @@ async def edit_user_settings(client, query):
await query.answer()
update_user_ldata(user_id, data[2], "")
await update_user_settings(query)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(user_id)
+ await database.update_user_data(user_id)
elif data[2] in ["split_size", "leech_dest", "rclone_path", "gdrive_id"]:
await query.answer()
if data[2] in user_data.get(user_id, {}):
del user_data[user_id][data[2]]
await update_user_settings(query)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(user_id)
+ await database.update_user_data(user_id)
elif data[2] == "leech":
await query.answer()
thumbpath = f"Thumbnails/{user_id}.jpg"
@@ -421,38 +421,66 @@ async def edit_user_settings(client, query):
if user_dict.get("split_size", False):
split_size = user_dict["split_size"]
else:
- split_size = config_dict["LEECH_SPLIT_SIZE"]
+ split_size = Config.LEECH_SPLIT_SIZE
buttons.data_button("Leech Destination", f"userset {user_id} ldest")
if user_dict.get("leech_dest", False):
leech_dest = user_dict["leech_dest"]
- elif "leech_dest" not in user_dict and config_dict["LEECH_DUMP_CHAT"]:
- leech_dest = config_dict["LEECH_DUMP_CHAT"]
+ elif "leech_dest" not in user_dict and Config.LEECH_DUMP_CHAT:
+ leech_dest = Config.LEECH_DUMP_CHAT
else:
leech_dest = "None"
buttons.data_button("Leech Prefix", f"userset {user_id} leech_prefix")
if user_dict.get("lprefix", False):
lprefix = user_dict["lprefix"]
- elif "lprefix" not in user_dict and config_dict["LEECH_FILENAME_PREFIX"]:
- lprefix = config_dict["LEECH_FILENAME_PREFIX"]
+ elif "lprefix" not in user_dict and Config.LEECH_FILENAME_PREFIX:
+ lprefix = Config.LEECH_FILENAME_PREFIX
else:
lprefix = "None"
if user_dict.get("as_doc", False) or (
- "as_doc" not in user_dict and config_dict["AS_DOCUMENT"]
+ "as_doc" not in user_dict and Config.AS_DOCUMENT
):
ltype = "DOCUMENT"
buttons.data_button("Send As Media", f"userset {user_id} as_doc false")
else:
ltype = "MEDIA"
buttons.data_button("Send As Document", f"userset {user_id} as_doc true")
- if (IS_PREMIUM_USER and user_dict.get("user_transmission", False)) or (
- "user_transmission" not in user_dict and config_dict["USER_TRANSMISSION"]
+ if user_dict.get("equal_splits", False) or (
+ "equal_splits" not in user_dict and Config.EQUAL_SPLITS
+ ):
+ buttons.data_button(
+ "Disable Equal Splits",
+ f"userset {user_id} equal_splits false",
+ )
+ equal_splits = "Enabled"
+ else:
+ buttons.data_button(
+ "Enable Equal Splits",
+ f"userset {user_id} equal_splits true",
+ )
+ equal_splits = "Disabled"
+ if user_dict.get("media_group", False) or (
+ "media_group" not in user_dict and Config.MEDIA_GROUP
):
+ buttons.data_button(
+ "Disable Media Group",
+ f"userset {user_id} media_group false",
+ )
+ media_group = "Enabled"
+ else:
+ buttons.data_button(
+ "Enable Media Group",
+ f"userset {user_id} media_group true",
+ )
+ media_group = "Disabled"
+ if (
+ TgClient.IS_PREMIUM_USER and user_dict.get("user_transmission", False)
+ ) or ("user_transmission" not in user_dict and Config.USER_TRANSMISSION):
buttons.data_button(
"Leech by Bot",
f"userset {user_id} user_transmission false",
)
leech_method = "user"
- elif IS_PREMIUM_USER:
+ elif TgClient.IS_PREMIUM_USER:
leech_method = "bot"
buttons.data_button(
"Leech by User",
@@ -461,15 +489,15 @@ async def edit_user_settings(client, query):
else:
leech_method = "bot"
- if (IS_PREMIUM_USER and user_dict.get("mixed_leech", False)) or (
- "mixed_leech" not in user_dict and config_dict["MIXED_LEECH"]
+ if (TgClient.IS_PREMIUM_USER and user_dict.get("mixed_leech", False)) or (
+ "mixed_leech" not in user_dict and Config.MIXED_LEECH
):
mixed_leech = "Enabled"
buttons.data_button(
"Disable Mixed Leech",
f"userset {user_id} mixed_leech false",
)
- elif IS_PREMIUM_USER:
+ elif TgClient.IS_PREMIUM_USER:
mixed_leech = "Disabled"
buttons.data_button(
"Enable Mixed Leech",
@@ -481,8 +509,8 @@ async def edit_user_settings(client, query):
buttons.data_button("Thumbnail Layout", f"userset {user_id} tlayout")
if user_dict.get("thumb_layout", False):
thumb_layout = user_dict["thumb_layout"]
- elif "thumb_layout" not in user_dict and config_dict["THUMBNAIL_LAYOUT"]:
- thumb_layout = config_dict["THUMBNAIL_LAYOUT"]
+ elif "thumb_layout" not in user_dict and Config.THUMBNAIL_LAYOUT:
+ thumb_layout = Config.THUMBNAIL_LAYOUT
else:
thumb_layout = "None"
@@ -492,6 +520,8 @@ async def edit_user_settings(client, query):
Leech Type is {ltype}
Custom Thumbnail {thumbmsg}
Leech Split Size is {split_size}
+Equal Splits is {equal_splits}
+Media Group is {media_group}
Leech Prefix is {escape(lprefix)}
Leech Destination is {leech_dest}
Leech by {leech_method} session
@@ -509,7 +539,7 @@ async def edit_user_settings(client, query):
rccmsg = "Exists" if await aiopath.exists(rclone_conf) else "Not Exists"
if user_dict.get("rclone_path", False):
rccpath = user_dict["rclone_path"]
- elif RP := config_dict["RCLONE_PATH"]:
+ elif RP := Config.RCLONE_PATH:
rccpath = RP
else:
rccpath = "None"
@@ -524,7 +554,7 @@ async def edit_user_settings(client, query):
buttons.data_button("Default Gdrive ID", f"userset {user_id} gdid")
buttons.data_button("Index URL", f"userset {user_id} index")
if user_dict.get("stop_duplicate", False) or (
- "stop_duplicate" not in user_dict and config_dict["STOP_DUPLICATE"]
+ "stop_duplicate" not in user_dict and Config.STOP_DUPLICATE
):
buttons.data_button(
"Disable Stop Duplicate",
@@ -542,7 +572,7 @@ async def edit_user_settings(client, query):
tokenmsg = "Exists" if await aiopath.exists(token_pickle) else "Not Exists"
if user_dict.get("gdrive_id", False):
gdrive_id = user_dict["gdrive_id"]
- elif GDID := config_dict["GDRIVE_ID"]:
+ elif GDID := Config.GDRIVE_ID:
gdrive_id = GDID
else:
gdrive_id = "None"
@@ -577,7 +607,7 @@ async def edit_user_settings(client, query):
elif data[2] == "yto":
await query.answer()
buttons = ButtonMaker()
- if user_dict.get("yt_opt", False) or config_dict["YT_DLP_OPTIONS"]:
+ if user_dict.get("yt_opt", False) or Config.YT_DLP_OPTIONS:
buttons.data_button(
"Remove YT-DLP Options",
f"userset {user_id} yt_opt",
@@ -597,7 +627,7 @@ async def edit_user_settings(client, query):
elif data[2] == "ffc":
await query.answer()
buttons = ButtonMaker()
- if user_dict.get("ffmpeg_cmds", False) or config_dict["FFMPEG_CMDS"]:
+ if user_dict.get("ffmpeg_cmds", False) or Config.FFMPEG_CMDS:
buttons.data_button(
"Remove FFMPEG Commands",
f"userset {user_id} ffmpeg_cmds",
@@ -607,15 +637,14 @@ async def edit_user_settings(client, query):
buttons.data_button("Close", f"userset {user_id} close")
rmsg = """list of lists of ffmpeg commands. You can set multiple ffmpeg commands for all files before upload. Don't write ffmpeg at beginning, start directly with the arguments.
Notes:
-1. Add -del
to the list(s) which you want from the bot to delete the original files after command run complete!
+1. Add -del
to the list which you want from the bot to delete the original files after command run complete!
2. Seed will get disbaled while using this option
-3. It must be list of list(s) event of one list added like [["-i", "file.mkv", "-c", "copy", "-c:s", "srt", "file.mkv", "-del"]]
-Examples: [["-i", "file.mkv", "-c", "copy", "-c:s", "srt", "file.mkv", "-del"], ["-i", "file.video", "-c", "copy", "-c:s", "srt", "file"], ["-i", "file.m4a", "-c:a", "libmp3lame", "-q:a", "2", "file.mp3"], ["-i", "file.audio", "-c:a", "libmp3lame", "-q:a", "2", "file.mp3"]]
-Here I will explain how to use file.* which is reference to files you want to work on.
-1. First cmd: the input is file.mkv so this cmd will work only on mkv videos and the output is file.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 file.video so this cmd will work on all videos and the output is only file so the extenstion is same as input files.
-3. Third cmd: the input in file.m4a so this cmd will work only on m4a audios and the output is file.mp3 so the output extension is mp3.
-4. Fourth cmd: the input is file.audio so this cmd will work on all audios and the output is file.mp3 so the output extension is mp3."""
+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"]
+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.
+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."""
await edit_message(message, rmsg, buttons.build_menu(1))
pfunc = partial(set_option, pre_event=query, option="ffmpeg_cmds")
await event_handler(client, query, pfunc)
@@ -628,7 +657,7 @@ async def edit_user_settings(client, query):
buttons.data_button("Close", f"userset {user_id} close")
await edit_message(
message,
- f"Send Leech split size in bytes. IS_PREMIUM_USER: {IS_PREMIUM_USER}. Timeout: 60 sec",
+ f"Send Leech split size in bytes. IS_PREMIUM_USER: {TgClient.IS_PREMIUM_USER}. Timeout: 60 sec",
buttons.build_menu(1),
)
pfunc = partial(set_option, pre_event=query, option="split_size")
@@ -655,8 +684,7 @@ async def edit_user_settings(client, query):
buttons = ButtonMaker()
if user_dict.get("rclone_path", False):
buttons.data_button(
- "Reset Rclone Path",
- f"userset {user_id} rclone_path",
+ "Reset Rclone Path", f"userset {user_id} rclone_path"
)
buttons.data_button("Back", f"userset {user_id} rclone")
buttons.data_button("Close", f"userset {user_id} close")
@@ -707,7 +735,7 @@ async def edit_user_settings(client, query):
await query.answer()
buttons = ButtonMaker()
if user_dict.get("lprefix", False) or (
- "lprefix" not in user_dict and config_dict["LEECH_FILENAME_PREFIX"]
+ "lprefix" not in user_dict and Config.LEECH_FILENAME_PREFIX
):
buttons.data_button("Remove Leech Prefix", f"userset {user_id} lprefix")
buttons.data_button("Back", f"userset {user_id} leech")
@@ -723,7 +751,7 @@ async def edit_user_settings(client, query):
await query.answer()
buttons = ButtonMaker()
if user_dict.get("leech_dest", False) or (
- "leech_dest" not in user_dict and config_dict["LEECH_DUMP_CHAT"]
+ "leech_dest" not in user_dict and Config.LEECH_DUMP_CHAT
):
buttons.data_button(
"Reset Leech Destination",
@@ -742,7 +770,7 @@ async def edit_user_settings(client, query):
await query.answer()
buttons = ButtonMaker()
if user_dict.get("thumb_layout", False) or (
- "thumb_layout" not in user_dict and config_dict["THUMBNAIL_LAYOUT"]
+ "thumb_layout" not in user_dict and Config.THUMBNAIL_LAYOUT
):
buttons.data_button(
"Reset Thumbnail Layout",
@@ -761,7 +789,7 @@ async def edit_user_settings(client, query):
await query.answer()
buttons = ButtonMaker()
if user_dict.get("excluded_extensions", False) or (
- "excluded_extensions" not in user_dict and global_extension_filter
+ "excluded_extensions" not in user_dict and extension_filter
):
buttons.data_button(
"Remove Excluded Extensions",
@@ -781,20 +809,19 @@ async def edit_user_settings(client, query):
buttons = ButtonMaker()
if user_dict.get("name_sub", False):
buttons.data_button(
- "Remove Name Subtitute",
- f"userset {user_id} name_sub",
+ "Remove Name Subtitute", f"userset {user_id} name_sub"
)
buttons.data_button("Back", f"userset {user_id} back")
buttons.data_button("Close", f"userset {user_id} close")
emsg = 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/ | \[hello\]/hello | \\text\\/text/s
+Example: script/code/s | mirror/leech | tea/ /s | clone | cpu/ | \[mltb\]/mltb | \\text\\/text/s
1. script will get replaced by code with sensitive case
2. mirror will get replaced by leech
4. tea will get replaced by space with sensitive case
5. clone will get removed
6. cpu will get replaced by space
-7. [hello] will get replaced by hello
+7. [mltb] will get replaced by mltb
8. \text\ will get replaced by text with sensitive case
"""
emsg += (
@@ -812,15 +839,13 @@ async def edit_user_settings(client, query):
du = "rc" if data[2] == "gd" else "gd"
update_user_ldata(user_id, "default_upload", du)
await update_user_settings(query)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(user_id)
+ await database.update_user_data(user_id)
elif data[2] == "user_tokens":
await query.answer()
tr = data[3].lower() == "false"
update_user_ldata(user_id, "user_tokens", tr)
await update_user_settings(query)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(user_id)
+ await database.update_user_data(user_id)
elif data[2] == "upload_paths":
await query.answer()
buttons = ButtonMaker()
@@ -884,8 +909,7 @@ async def edit_user_settings(client, query):
else:
user_data[user_id].clear()
await update_user_settings(query)
- if config_dict["DATABASE_URL"]:
- await Database.update_user_data(user_id)
+ await database.update_user_data(user_id)
for fpath in [thumb_path, rclone_conf, token_pickle]:
if await aiopath.exists(fpath):
await remove(fpath)
@@ -899,7 +923,7 @@ async def edit_user_settings(client, query):
@new_task
-async def send_users_settings(_, message):
+async def get_users_settings(_, message):
if user_data:
msg = ""
for u, d in user_data.items():
@@ -918,24 +942,3 @@ async def send_users_settings(_, message):
await send_message(message, msg)
else:
await send_message(message, "No users data!")
-
-
-bot.add_handler(
- MessageHandler(
- send_users_settings,
- filters=command(
- BotCommands.UsersCommand,
- )
- & CustomFilters.sudo,
- ),
-)
-bot.add_handler(
- MessageHandler(
- user_settings,
- filters=command(
- BotCommands.UserSetCommand,
- )
- & CustomFilters.authorized,
- ),
-)
-bot.add_handler(CallbackQueryHandler(edit_user_settings, filters=regex("^userset")))
diff --git a/bot/modules/ytdlp.py b/bot/modules/ytdlp.py
index 3d174128d..0c33190ab 100644
--- a/bot/modules/ytdlp.py
+++ b/bot/modules/ytdlp.py
@@ -3,12 +3,12 @@
from time import time
from httpx import AsyncClient
-from pyrogram.filters import command, regex, user
-from pyrogram.handlers import CallbackQueryHandler, MessageHandler
+from pyrogram.filters import regex, user
+from pyrogram.handlers import CallbackQueryHandler
from yt_dlp import YoutubeDL
-from bot import DOWNLOAD_DIR, LOGGER, bot, bot_loop, config_dict, task_dict_lock
-from bot.helper.aeon_utils.access_check import error_check
+from bot import LOGGER, bot_loop, task_dict_lock
+from bot.core.config_manager import Config
from bot.helper.ext_utils.bot_utils import (
COMMAND_USAGE,
arg_parser,
@@ -24,14 +24,10 @@
from bot.helper.mirror_leech_utils.download_utils.yt_dlp_download import (
YoutubeDLHelper,
)
-from bot.helper.telegram_helper.bot_commands import BotCommands
from bot.helper.telegram_helper.button_build import ButtonMaker
-from bot.helper.telegram_helper.filters import CustomFilters
from bot.helper.telegram_helper.message_utils import (
- delete_links,
delete_message,
edit_message,
- five_minute_del,
send_message,
)
@@ -95,7 +91,7 @@ async def _event_handler(self):
)
try:
await wait_for(self.event.wait(), timeout=self._timeout)
- except Exception:
+ except:
await edit_message(self._reply_to, "Timed Out. Task has been cancelled!")
self.qual = None
self.listener.is_cancelled = True
@@ -300,11 +296,6 @@ def __init__(
self.is_leech = is_leech
async def new_event(self):
- error_msg, error_button = await error_check(self.message)
- if error_msg:
- await delete_links(self.message)
- error = await send_message(self.message, error_msg, error_button)
- return await five_minute_del(error)
text = self.message.text.split("\n")
input_list = text[0].split(" ")
qual = ""
@@ -341,7 +332,7 @@ async def new_event(self):
try:
self.multi = int(args["-i"])
- except Exception:
+ except:
self.multi = 0
try:
@@ -416,16 +407,16 @@ async def new_event(self):
self.same_dir[fd_name]["total"] -= 1
else:
await self.init_bulk(input_list, bulk_start, bulk_end, YtDlp)
- return None
+ return
if len(self.bulk) != 0:
del self.bulk[0]
- path = f"{DOWNLOAD_DIR}{self.mid}{self.folder_name}"
+ path = f"{Config.DOWNLOAD_DIR}{self.mid}{self.folder_name}"
await self.get_tag(text)
- opt = opt or self.user_dict.get("yt_opt") or config_dict["YT_DLP_OPTIONS"]
+ opt = opt or self.user_dict.get("yt_opt") or Config.YT_DLP_OPTIONS
if not self.link and (reply_to := self.message.reply_to_message):
self.link = reply_to.text.split("\n", 1)[0].strip()
@@ -437,7 +428,7 @@ async def new_event(self):
COMMAND_USAGE["yt"][1],
)
await self.remove_from_same_dir()
- return None
+ return
if "mdisk.me" in self.link:
self.name, self.link = await _mdisk(self.link, self.name)
@@ -447,7 +438,7 @@ async def new_event(self):
except Exception as e:
await send_message(self.message, e)
await self.remove_from_same_dir()
- return None
+ return
options = {"usenetrc": True, "cookiefile": "cookies.txt"}
if opt:
@@ -483,7 +474,7 @@ async def new_event(self):
msg = str(e).replace("<", " ").replace(">", " ")
await send_message(self.message, f"{self.tag} {msg}")
await self.remove_from_same_dir()
- return None
+ return
finally:
await self.run_multi(input_list, YtDlp)
@@ -491,13 +482,12 @@ async def new_event(self):
qual = await YtSelection(self).get_quality(result)
if qual is None:
await self.remove_from_same_dir()
- return None
+ return
LOGGER.info(f"Downloading with YT-DLP: {self.link}")
playlist = "entries" in result
ydl = YoutubeDLHelper(self)
await ydl.add_download(path, qual, playlist, opt)
- return None
async def ytdl(client, message):
@@ -506,23 +496,3 @@ async def ytdl(client, message):
async def ytdl_leech(client, message):
bot_loop.create_task(YtDlp(client, message, is_leech=True).new_event())
-
-
-bot.add_handler(
- MessageHandler(
- ytdl,
- filters=command(
- BotCommands.YtdlCommand,
- )
- & CustomFilters.authorized,
- ),
-)
-bot.add_handler(
- MessageHandler(
- ytdl_leech,
- filters=command(
- BotCommands.YtdlLeechCommand,
- )
- & CustomFilters.authorized,
- ),
-)
diff --git a/sam_conf.py b/sam_conf.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/sample_config.env b/sample_config.env
deleted file mode 100644
index 57b59287b..000000000
--- a/sample_config.env
+++ /dev/null
@@ -1,7 +0,0 @@
-OWNER_ID = ""
-TELEGRAM_API = ""
-TELEGRAM_HASH = ""
-DATABASE_URL = ""
-
-UPSTREAM_REPO = "https://github.com/AeonOrg/Aeon-MLTB"
-UPSTREAM_BRANCH = "main"
\ No newline at end of file
diff --git a/update.py b/update.py
index c0f07ba79..bba64a0fe 100644
--- a/update.py
+++ b/update.py
@@ -1,57 +1,26 @@
-from datetime import datetime
+from importlib import import_module
from logging import (
ERROR,
INFO,
FileHandler,
- Formatter,
- LogRecord,
StreamHandler,
basicConfig,
- error,
getLogger,
- info,
)
-from os import environ, path, remove
-from subprocess import run
+from logging import (
+ error as log_error,
+)
+from logging import (
+ info as log_info,
+)
+from os import path, remove
+from subprocess import run as srun
+from sys import exit
-from dotenv import dotenv_values, load_dotenv
from pymongo.mongo_client import MongoClient
from pymongo.server_api import ServerApi
-from pytz import timezone
-
-
-class CustomFormatter(Formatter):
- def formatTime( # noqa: N802
- self,
- record: LogRecord,
- datefmt: str | None,
- ) -> str:
- dt: datetime = datetime.fromtimestamp(
- record.created,
- tz=timezone("Asia/Dhaka"),
- )
- return dt.strftime(datefmt)
-
- def format(self, record: LogRecord) -> str:
- return super().format(record).replace(record.levelname, record.levelname[:1])
-
-
-formatter = CustomFormatter(
- "[%(asctime)s] %(levelname)s - %(message)s [%(module)s:%(lineno)d]",
- datefmt="%d-%b %I:%M:%S %p",
-)
-
-
-file_handler = FileHandler("log.txt")
-file_handler.setFormatter(formatter)
-
-stream_handler = StreamHandler()
-stream_handler.setFormatter(formatter)
-
-basicConfig(handlers=[file_handler, stream_handler], level=INFO)
getLogger("pymongo").setLevel(ERROR)
-getLogger("httpx").setLevel(ERROR)
if path.exists("log.txt"):
with open("log.txt", "r+") as f:
@@ -60,18 +29,27 @@ def format(self, record: LogRecord) -> str:
if path.exists("rlog.txt"):
remove("rlog.txt")
+basicConfig(
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
+ handlers=[FileHandler("log.txt"), StreamHandler()],
+ level=INFO,
+)
-load_dotenv("config.env", override=True)
+settings = import_module("config")
+config_file = {
+ key: value.strip() if isinstance(value, str) else value
+ for key, value in vars(settings).items()
+ if not key.startswith("__")
+}
-BOT_TOKEN = environ["BOT_TOKEN"]
+BOT_TOKEN = config_file.get("BOT_TOKEN", "")
+if not BOT_TOKEN:
+ log_error("BOT_TOKEN variable is missing! Exiting now")
+ exit(1)
BOT_ID = BOT_TOKEN.split(":", 1)[0]
-DATABASE_URL = environ.get("DATABASE_URL", "")
-if len(DATABASE_URL) == 0:
- DATABASE_URL = None
-
-if DATABASE_URL is not None:
+if DATABASE_URL := config_file.get("DATABASE_URL", "").strip():
try:
conn = MongoClient(DATABASE_URL, server_api=ServerApi("1"))
db = conn.luna
@@ -80,35 +58,28 @@ def format(self, record: LogRecord) -> str:
if old_config is not None:
del old_config["_id"]
if (
- (
- old_config is not None
- and old_config == dict(dotenv_values("config.env"))
- )
+ (old_config is not None and old_config == config_file)
or old_config is None
) and config_dict is not None:
- environ["UPSTREAM_REPO"] = config_dict["UPSTREAM_REPO"]
- environ["UPSTREAM_BRANCH"] = config_dict["UPSTREAM_BRANCH"]
+ config_file["UPSTREAM_REPO"] = config_dict["UPSTREAM_REPO"]
+ config_file["UPSTREAM_BRANCH"] = config_dict["UPSTREAM_BRANCH"]
conn.close()
except Exception as e:
- error(f"Database ERROR: {e}")
+ log_error(f"Database ERROR: {e}")
-UPSTREAM_REPO = environ.get("UPSTREAM_REPO", "")
-if len(UPSTREAM_REPO) == 0:
- UPSTREAM_REPO = None
+UPSTREAM_REPO = config_file.get("UPSTREAM_REPO", "https://github.com/AeonOrg/Aeon-MLTB").strip()
-UPSTREAM_BRANCH = environ.get("UPSTREAM_BRANCH", "")
-if len(UPSTREAM_BRANCH) == 0:
- UPSTREAM_BRANCH = "main"
+UPSTREAM_BRANCH = config_file.get("UPSTREAM_BRANCH", "").strip() or "beta"
-if UPSTREAM_REPO is not None:
+if UPSTREAM_REPO:
if path.exists(".git"):
- run(["rm", "-rf", ".git"], check=False)
+ srun(["rm", "-rf", ".git"], check=False)
- update = run(
+ update = srun(
[
f"git init -q \
- && git config --global user.email yesiamshojib@gmail.com \
- && git config --global user.name 5hojib \
+ && git config --global user.email e.anastayyar@gmail.com \
+ && git config --global user.name mltb \
&& git add . \
&& git commit -sm update -q \
&& git remote add origin {UPSTREAM_REPO} \
@@ -120,8 +91,8 @@ def format(self, record: LogRecord) -> str:
)
if update.returncode == 0:
- info("Successfully updated with latest commit from UPSTREAM_REPO")
+ log_info("Successfully updated with latest commit from UPSTREAM_REPO")
else:
- error(
+ log_error(
"Something went wrong while updating, check UPSTREAM_REPO if valid or not!",
)