| | from asyncio import gather, sleep, wait_for, TimeoutError |
| | from platform import platform, version |
| | from re import search as research |
| | from time import time |
| |
|
| | from aiofiles.os import path as aiopath |
| | from psutil import ( |
| | Process, |
| | boot_time, |
| | cpu_count, |
| | cpu_freq, |
| | cpu_percent, |
| | disk_io_counters, |
| | disk_usage, |
| | getloadavg, |
| | net_io_counters, |
| | swap_memory, |
| | virtual_memory, |
| | process_iter, |
| | NoSuchProcess, |
| | AccessDenied, |
| | ) |
| |
|
| | from .. import LOGGER, bot_cache, bot_start_time, bot_loop |
| | from ..core.config_manager import Config, BinConfig |
| | from ..helper.ext_utils.bot_utils import cmd_exec, compare_versions, new_task |
| | from ..helper.ext_utils.status_utils import ( |
| | get_progress_bar_string, |
| | get_readable_file_size, |
| | get_readable_time, |
| | ) |
| | from ..helper.telegram_helper.filters import CustomFilters |
| | from ..helper.telegram_helper.button_build import ButtonMaker |
| | from ..helper.telegram_helper.message_utils import ( |
| | delete_message, |
| | edit_message, |
| | send_message, |
| | ) |
| | from ..version import get_version |
| |
|
| | commands = { |
| | "aria2": ([BinConfig.ARIA2_NAME, "--version"], r"aria2 version ([\d.]+)"), |
| | "qBittorrent": ([BinConfig.QBIT_NAME, "--version"], r"qBittorrent v([\d.]+)"), |
| | "SABnzbd+": ( |
| | [BinConfig.SABNZBD_NAME, "--version"], |
| | rf"{BinConfig.SABNZBD_NAME}-([\d.]+)", |
| | ), |
| | "python": (["python3", "--version"], r"Python ([\d.]+)"), |
| | "rclone": ([BinConfig.RCLONE_NAME, "--version"], r"rclone v([\d.]+)"), |
| | "yt-dlp": (["yt-dlp", "--version"], r"([\d.]+)"), |
| | "ffmpeg": ( |
| | [BinConfig.FFMPEG_NAME, "-version"], |
| | r"ffmpeg version ([\d.]+(-\w+)?).*", |
| | ), |
| | "7z": (["7z", "i"], r"7-Zip ([\d.]+)"), |
| | "aiohttp": (["uv", "pip", "show", "aiohttp"], r"Version: ([\d.]+)"), |
| | "pyrotgfork": (["uv", "pip", "show", "pyrotgfork"], r"Version: ([\d.]+)"), |
| | "gapi": (["uv", "pip", "show", "google-api-python-client"], r"Version: ([\d.]+)"), |
| | "mega": (["mega-version"], r"version: ([\d.]+)"), |
| | } |
| |
|
| |
|
| | async def get_stats(event, key="home"): |
| | user_id = event.from_user.id |
| | btns = ButtonMaker() |
| | if key == "home": |
| | btns = ButtonMaker() |
| | btns.data_button("Bot Stats", f"stats {user_id} stbot") |
| | btns.data_button("OS Stats", f"stats {user_id} stsys") |
| | btns.data_button("Repo Stats", f"stats {user_id} strepo") |
| | btns.data_button("Pkgs Stats", f"stats {user_id} stpkgs") |
| | btns.data_button("Task Limits", f"stats {user_id} tlimits") |
| | btns.data_button("Sys Tasks", f"stats {user_id} systasks") |
| | msg = "β¬ <b><i>Bot & OS Statistics!</i></b>" |
| | elif key == "stbot": |
| | total, used, free, disk = disk_usage("/") |
| | swap = swap_memory() |
| | memory = virtual_memory() |
| | disk_io = disk_io_counters() |
| | msg = f"""β¬ <b><i>BOT STATISTICS :</i></b> |
| | β <b>Bot Uptime :</b> {get_readable_time(time() - bot_start_time)} |
| | |
| | β <b><i>RAM ( MEMORY ) :</i></b> |
| | β {get_progress_bar_string(memory.percent)} {memory.percent}% |
| | β <b>U :</b> {get_readable_file_size(memory.used)} | <b>F :</b> {get_readable_file_size(memory.available)} | <b>T :</b> {get_readable_file_size(memory.total)} |
| | |
| | β <b><i>SWAP MEMORY :</i></b> |
| | β {get_progress_bar_string(swap.percent)} {swap.percent}% |
| | β <b>U :</b> {get_readable_file_size(swap.used)} | <b>F :</b> {get_readable_file_size(swap.free)} | <b>T :</b> {get_readable_file_size(swap.total)} |
| | |
| | β <b><i>DISK :</i></b> |
| | β {get_progress_bar_string(disk)} {disk}% |
| | β <b>Total Disk Read :</b> {f"{get_readable_file_size(disk_io.read_bytes)} ({get_readable_time(disk_io.read_time / 1000)})" if disk_io else "Access Denied"} |
| | β <b>Total Disk Write :</b> {f"{get_readable_file_size(disk_io.write_bytes)} ({get_readable_time(disk_io.write_time / 1000)})" if disk_io else "Access Denied"} |
| | β <b>U :</b> {get_readable_file_size(used)} | <b>F :</b> {get_readable_file_size(free)} | <b>T :</b> {get_readable_file_size(total)} |
| | """ |
| | elif key == "stsys": |
| | cpu_usage = cpu_percent(interval=0.5) |
| | msg = f"""β¬ <b><i>OS SYSTEM :</i></b> |
| | β <b>OS Uptime :</b> {get_readable_time(time() - boot_time())} |
| | β <b>OS Version :</b> {version()} |
| | β <b>OS Arch :</b> {platform()} |
| | |
| | β¬ <b><i>NETWORK STATS :</i></b> |
| | β <b>Upload Data:</b> {get_readable_file_size(net_io_counters().bytes_sent)} |
| | β <b>Download Data:</b> {get_readable_file_size(net_io_counters().bytes_recv)} |
| | β <b>Pkts Sent:</b> {str(net_io_counters().packets_sent)[:-3]}k |
| | β <b>Pkts Received:</b> {str(net_io_counters().packets_recv)[:-3]}k |
| | β <b>Total I/O Data:</b> {get_readable_file_size(net_io_counters().bytes_recv + net_io_counters().bytes_sent)} |
| | |
| | β <b>CPU :</b> |
| | β {get_progress_bar_string(cpu_usage)} {cpu_usage}% |
| | β <b>CPU Frequency :</b> {f"{cpu_freq().current / 1000:.2f} GHz" if cpu_freq() else "Access Denied"} |
| | β <b>System Avg Load :</b> {"%, ".join(str(round((x / cpu_count() * 100), 2)) for x in getloadavg())}%, (1m, 5m, 15m) |
| | β <b>P-Core(s) :</b> {cpu_count(logical=False)} | <b>V-Core(s) :</b> {cpu_count(logical=True) - cpu_count(logical=False)} |
| | β <b>Total Core(s) :</b> {cpu_count(logical=True)} |
| | β <b>Usable CPU(s) :</b> {len(Process().cpu_affinity())} |
| | """ |
| | elif key == "strepo": |
| | last_commit, changelog = "No Data", "N/A" |
| | if await aiopath.exists(".git"): |
| | last_commit = ( |
| | await cmd_exec( |
| | "git log -1 --pretty='%cd ( %cr )' --date=format-local:'%d/%m/%Y'", |
| | True, |
| | ) |
| | )[0] |
| | changelog = ( |
| | await cmd_exec( |
| | "git log -1 --pretty=format:'<code>%s</code> <b>By</b> %an'", True |
| | ) |
| | )[0] |
| | official_v = ( |
| | await cmd_exec( |
| | f"curl -o latestversion.py https://raw.githubusercontent.com/SilentDemonSD/WZML-X/{Config.UPSTREAM_BRANCH}/bot/version.py -s && python3 latestversion.py && rm latestversion.py", |
| | True, |
| | ) |
| | )[0] |
| | msg = f"""β¬ <b><i>Repo Statistics :</i></b> |
| | β |
| | β <b>Bot Updated :</b> {last_commit} |
| | β <b>Current Version :</b> {get_version()} |
| | β <b>Latest Version :</b> {official_v} |
| | β <b>Last ChangeLog :</b> {changelog} |
| | |
| | β¬ <b>REMARKS :</b> <code>{compare_versions(get_version(), official_v)}</code> |
| | """ |
| | elif key == "stpkgs": |
| | ver = bot_cache.get("eng_versions", {}) |
| | msg = f"""β¬ <b><i>Packages Statistics :</i></b> |
| | β |
| | β <b>python:</b> {ver.get("python", "N/A")} |
| | β <b>aria2:</b> {ver.get("aria2", "N/A")} |
| | β <b>qBittorrent:</b> {ver.get("qBittorrent", "N/A")} |
| | β <b>SABnzbd+:</b> {ver.get("SABnzbd+", "N/A")} |
| | β <b>rclone:</b> {ver.get("rclone", "N/A")} |
| | β <b>yt-dlp:</b> {ver.get("yt-dlp", "N/A")} |
| | β <b>ffmpeg:</b> {ver.get("ffmpeg", "N/A")} |
| | β <b>7z:</b> {ver.get("7z", "N/A")} |
| | β <b>Aiohttp:</b> {ver.get("aiohttp", "N/A")} |
| | β <b>PyroTgFork:</b> {ver.get("pyrotgfork", "N/A")} |
| | β <b>Google API:</b> {ver.get("gapi", "N/A")} |
| | β <b>Mega CMD:</b> {ver.get("mega", "N/A")} |
| | """ |
| | elif key == "tlimits": |
| | msg = f"""β¬ <b><i>Bot Task Limits :</i></b> |
| | β |
| | β <b>Direct Limit :</b> {Config.DIRECT_LIMIT or "β"} GB |
| | β <b>Torrent Limit :</b> {Config.TORRENT_LIMIT or "β"} GB |
| | β <b>GDriveDL Limit :</b> {Config.GD_DL_LIMIT or "β"} GB |
| | β <b>RCloneDL Limit :</b> {Config.RC_DL_LIMIT or "β"} GB |
| | β <b>Clone Limit :</b> {Config.CLONE_LIMIT or "β"} GB |
| | β <b>JDown Limit :</b> {Config.JD_LIMIT or "β"} GB |
| | β <b>NZB Limit :</b> {Config.NZB_LIMIT or "β"} GB |
| | β <b>YT-DLP Limit :</b> {Config.YTDLP_LIMIT or "β"} GB |
| | β <b>Playlist Limit :</b> {Config.PLAYLIST_LIMIT or "β"} |
| | β <b>Mega Limit :</b> {Config.MEGA_LIMIT or "β"} GB |
| | β <b>Leech Limit :</b> {Config.LEECH_LIMIT or "β"} GB |
| | β <b>Archive Limit :</b> {Config.ARCHIVE_LIMIT or "β"} GB |
| | β <b>Extract Limit :</b> {Config.EXTRACT_LIMIT or "β"} GB |
| | β <b>Threshold Storage :</b> {Config.STORAGE_LIMIT or "β"} GB |
| | β |
| | β <b>Token Validity :</b> {get_readable_time(Config.VERIFY_TIMEOUT) if Config.VERIFY_TIMEOUT else "Disabled"} |
| | β <b>User Time Limit :</b> {Config.USER_TIME_INTERVAL or "0"}s / task |
| | β <b>User Max Tasks :</b> {Config.USER_MAX_TASKS or "β"} |
| | β <b>Bot Max Tasks :</b> {Config.BOT_MAX_TASKS or "β"} |
| | """ |
| |
|
| | elif key == "systasks": |
| | try: |
| | processes = [] |
| | for proc in process_iter( |
| | ["pid", "name", "cpu_percent", "memory_percent", "username"] |
| | ): |
| | try: |
| | info = proc.info |
| | if ( |
| | info.get("cpu_percent", 0) > 1.0 |
| | or info.get("memory_percent", 0) > 1.0 |
| | ): |
| | processes.append(info) |
| | except (NoSuchProcess, AccessDenied): |
| | continue |
| | processes.sort( |
| | key=lambda x: x.get("cpu_percent", 0) + x.get("memory_percent", 0), |
| | reverse=True, |
| | ) |
| | processes = processes[:15] |
| | except Exception: |
| | processes = [] |
| |
|
| | msg = "β¬ <b><i>System Tasks (High Usage)</i></b>\nβ\n" |
| |
|
| | if processes: |
| | for i, proc in enumerate(processes, 1): |
| | name = proc.get("name", "Unknown")[:20] |
| | cpu = proc.get("cpu_percent", 0) |
| | mem = proc.get("memory_percent", 0) |
| | user = proc.get("username", "Unknown")[:10] |
| | msg += f"β <b>{i:2d}.</b> <code>{name}</code>\nβ πΉ <b>CPU:</b> {cpu:.1f}% | <b>MEM:</b> {mem:.1f}%\nβ π€ <b>User:</b> {user} | <b>PID:</b> {proc['pid']}\n" |
| | btns.data_button(f"{i}", f"stats {user_id} killproc {proc['pid']}") |
| | msg += "β\nβ <i>Click serial number to terminate process</i>" |
| | else: |
| | msg += "β\nβ <i>No high usage processes found</i>" |
| |
|
| | btns.data_button("π Refresh", f"stats {user_id} systasks", "header") |
| |
|
| | btns.data_button("Back", f"stats {user_id} home", "footer") |
| | btns.data_button("Close", f"stats {user_id} close", "footer") |
| | return msg, btns.build_menu(8 if key == "systasks" else 2) |
| |
|
| |
|
| | @new_task |
| | async def bot_stats(_, message): |
| | msg, btns = await get_stats(message) |
| | await send_message(message, msg, btns) |
| |
|
| |
|
| | @new_task |
| | async def stats_pages(_, query): |
| | data = query.data.split() |
| | message = query.message |
| | user_id = query.from_user.id |
| | if user_id != int(data[1]): |
| | await query.answer("Not Yours!", show_alert=True) |
| | elif data[2] == "close": |
| | await query.answer() |
| | await delete_message(message, message.reply_to_message) |
| | elif data[2] == "killproc": |
| | if data[2] == "systasks" and not await CustomFilters.owner(_, query): |
| | await query.answer("Sorry! You cannot Kill System Tasks!", show_alert=True) |
| | return |
| | pid = int(data[3]) |
| | try: |
| | process = Process(pid) |
| | proc_name = process.name() |
| | process.terminate() |
| | await sleep(2) |
| | if process.is_running(): |
| | process.kill() |
| | status = "π₯ Force killed" |
| | else: |
| | status = "β
Terminated" |
| | await query.answer(f"{status}: {proc_name} (PID: {pid})", show_alert=True) |
| | except NoSuchProcess: |
| | await query.answer( |
| | "β Process not found or already terminated!", show_alert=True |
| | ) |
| | except AccessDenied: |
| | await query.answer( |
| | "β Access denied! Cannot kill this process.", show_alert=True |
| | ) |
| | except Exception as e: |
| | await query.answer(f"β Error: {str(e)}", show_alert=True) |
| |
|
| | msg, btns = await get_stats(query, "systasks") |
| | await edit_message(message, msg, btns) |
| | else: |
| | if data[2] == "systasks" and not await CustomFilters.sudo(_, query): |
| | await query.answer("Sorry! You cannot open System Tasks!", show_alert=True) |
| | return |
| | await query.answer() |
| | msg, btns = await get_stats(query, data[2]) |
| | await edit_message(message, msg, btns) |
| |
|
| |
|
| | async def get_version_async(command, regex, timeout=5): |
| | try: |
| | out, err, code = await wait_for(cmd_exec(command), timeout=timeout) |
| | if code != 0: |
| | return f"Error: {err}" |
| | match = research(regex, out) |
| | return match.group(1) if match else "-" |
| | except TimeoutError: |
| | return "Timeout" |
| | except Exception as e: |
| | return f"Exception: {str(e)}" |
| |
|
| |
|
| | async def retry_mega_version(): |
| | await sleep(60) |
| | command, regex = commands["mega"] |
| | version = await get_version_async(command, regex, timeout=10) |
| | if version != "Timeout" and not version.startswith("Exception"): |
| | bot_cache["eng_versions"]["mega"] = version |
| | LOGGER.info(f"MegaCMD Version Fetched: {version}") |
| | else: |
| | LOGGER.warning(f"Failed to fetch MegaCMD Version: {version}") |
| |
|
| |
|
| | @new_task |
| | async def get_packages_version(): |
| | tasks = [get_version_async(command, regex) for command, regex in commands.values()] |
| | versions = await gather(*tasks) |
| | bot_cache["eng_versions"] = {} |
| | for tool, ver in zip(commands.keys(), versions): |
| | bot_cache["eng_versions"][tool] = ver |
| | if await aiopath.exists(".git"): |
| | last_commit = await cmd_exec( |
| | "git log -1 --date=short --pretty=format:'%cd <b>From</b> %cr'", True |
| | ) |
| | last_commit = last_commit[0] |
| | else: |
| | last_commit = "No UPSTREAM_REPO" |
| | bot_cache["commit"] = last_commit |
| |
|
| | if bot_cache["eng_versions"]["mega"] in ["Timeout", "N/A"] or bot_cache[ |
| | "eng_versions" |
| | ]["mega"].startswith("Exception"): |
| | bot_loop.create_task(retry_mega_version()) |
| |
|
| | LOGGER.info("Fetched Package Versions!") |
| |
|