From 96fdf225a8d1f350ca9c10d6fea0517698818c19 Mon Sep 17 00:00:00 2001 From: Slipstreamm Date: Sat, 14 Jun 2025 13:15:28 -0600 Subject: [PATCH] feat(system-check): Revamp status command UI with components v2 Overhauls the output of the `/systemcheck` and `!systemcheck` commands using Discord UI Components V2 for a more modern and readable display. - Implemented `ui.LayoutView` for structured information presentation. - Added progress bars for CPU and RAM utilization for quick visual assessment. - Standardized display of OS, hardware, and bot statistics. - Separated data collection from UI rendering logic. - Added `status` alias for the prefix command. - Improved error handling for system information retrieval to display 'N/A' on failure. --- cogs/system_check_cog.py | 416 ++++++++++++++++++++------------------- 1 file changed, 213 insertions(+), 203 deletions(-) diff --git a/cogs/system_check_cog.py b/cogs/system_check_cog.py index 48a0159..72bee24 100644 --- a/cogs/system_check_cog.py +++ b/cogs/system_check_cog.py @@ -5,258 +5,268 @@ import time import psutil import platform import GPUtil -import distro # Ensure this is installed +import distro # Import wmi for Windows motherboard info try: import wmi - WMI_AVAILABLE = True except ImportError: WMI_AVAILABLE = False +def create_progress_bar(value: float, total: float, length: int = 12) -> str: + """Creates a text-based progress bar.""" + if total == 0: + percentage = 0 + else: + percentage = value / total + + filled_length = int(length * percentage) + bar = '▓' * filled_length + '░' * (length - filled_length) + return f"[{bar}] {percentage:.1%}" + + +class SystemStatusView(ui.LayoutView): + """ + A view that displays system and bot statistics in a visually appealing way. + It uses components v2 for a modern look. + """ + def __init__( + self, + bot_user: discord.User, + guild_count: int, + user_count: int, + os_info: str, + distro_info: str, + hostname: str, + uptime: str, + motherboard_info: str, + cpu_name: str, + cpu_usage: float, + ram_used: int, + ram_total: int, + ram_percent: float, + gpu_info: str, + requester: discord.User, + ) -> None: + super().__init__(timeout=None) + + # --- Store all data for the view --- + self.bot_user = bot_user + self.guild_count = guild_count + self.user_count = user_count + self.os_info = os_info + self.distro_info = distro_info + self.hostname = hostname + self.uptime = uptime + self.motherboard_info = motherboard_info + self.cpu_name = cpu_name + self.cpu_usage = cpu_usage + self.ram_used = ram_used + self.ram_total = ram_total + self.ram_percent = ram_percent + self.gpu_info = gpu_info + self.requester = requester + + # --- Build the UI --- + self._build_ui() + + def _build_ui(self): + """Constructs the UI elements of the view.""" + # Main container with Discord's "Blurple" color + container = ui.Container(accent_colour=discord.Color.from_rgb(88, 101, 242)) + + # --- Header --- + header = ui.Section(accessory=ui.Thumbnail(media=self.bot_user.display_avatar.url)) + header.add_item(ui.TextDisplay("**📊 System & Bot Status**")) + container.add_item(header) + + container.add_item(ui.Separator(spacing=discord.SeparatorSpacing.small)) + + # --- Bot & System Info --- + self._add_bot_system_info(container) + + container.add_item(ui.Separator(spacing=discord.SeparatorSpacing.small)) + + # --- Hardware Info --- + self._add_hardware_info(container) + + container.add_item(ui.Separator(spacing=discord.SeparatorSpacing.small)) + + # --- Footer --- + self._add_footer(container) + + self.add_item(container) + + def _add_bot_system_info(self, container: ui.Container): + """Adds bot and system information fields.""" + bot_info = ( + f"**🤖 Bot Information**\n" + f"`Servers :` {self.guild_count}\n" + f"`Users :` {self.user_count}\n" + ) + container.add_item(ui.TextDisplay(bot_info)) + + system_info = ( + f"**🖥️ System Information**\n" + f"`OS :` {self.os_info}{self.distro_info}\n" + f"`Hostname:` {self.hostname}\n" + f"`Uptime :` {self.uptime}" + ) + container.add_item(ui.TextDisplay(system_info)) + + def _add_hardware_info(self, container: ui.Container): + """Adds hardware information with progress bars.""" + container.add_item(ui.TextDisplay("**⚙️ Hardware Utilization**")) + + # RAM usage text + ram_usage_text = f"{self.ram_used // (1024**2)}MB / {self.ram_total // (1024**2)}MB" + + # Progress Bars + cpu_bar = create_progress_bar(self.cpu_usage, 100.0) + ram_bar = create_progress_bar(self.ram_used, self.ram_total) + + hardware_details = ( + f"`CPU ` {cpu_bar}\n" + f"`RAM ` {ram_bar}\n" + f"└ {ram_usage_text}" + ) + container.add_item(ui.TextDisplay(hardware_details)) + + hardware_specs = ( + f"**📋 Hardware Specifications**\n" + f"`Board:` {self.motherboard_info}\n" + f"`CPU :` {self.cpu_name}\n" + f"`GPU :` {self.gpu_info}" + ) + container.add_item(ui.TextDisplay(hardware_specs)) + + def _add_footer(self, container: ui.Container): + """Adds the footer with timestamp and requester info.""" + timestamp = discord.utils.format_dt(discord.utils.utcnow(), style="R") + footer_text = f"Updated: {timestamp} | Requested by: {self.requester.display_name}" + container.add_item(ui.TextDisplay(f"_{footer_text}_")) + + class SystemCheckCog(commands.Cog): def __init__(self, bot): self.bot = bot - async def _build_system_check_view(self, context_or_interaction): - """Return a view with detailed bot and system information.""" + async def _build_system_check_view(self, context_or_interaction) -> SystemStatusView: + """Gathers all system data and returns the constructed view.""" # Bot information - bot_user = self.bot.user guild_count = len(self.bot.guilds) - - # More efficient member counting - use cached members when available - # This avoids API calls that can cause timeouts - user_ids = set() - for guild in self.bot.guilds: - try: - # Use members that are already cached - for member in guild.members: - if not member.bot: - user_ids.add(member.id) - except Exception as e: - print(f"Error counting members in guild {guild.name}: {e}") + user_ids = {member.id for guild in self.bot.guilds for member in guild.members if not member.bot} user_count = len(user_ids) # System information system = platform.system() os_info = f"{system} {platform.release()}" hostname = platform.node() - distro_info_str = "" # Renamed variable + distro_info_str = "" if system == "Linux": try: - # Use distro library for better Linux distribution detection distro_name = distro.name(pretty=True) - distro_info_str = f"\n**Distro:** {distro_name}" - except ImportError: - distro_info_str = "\n**Distro:** (Install 'distro' package for details)" - except Exception as e: - distro_info_str = f"\n**Distro:** (Error getting info: {e})" + distro_info_str = f" ({distro_name})" + except Exception: + distro_info_str = "" # Fail silently elif system == "Windows": - # Add Windows version details if possible try: - win_ver = platform.version() # e.g., '10.0.19041' - win_build = platform.win32_ver()[1] # e.g., '19041' - os_info = f"Windows {win_ver} (Build {win_build})" - except Exception as e: - print(f"Could not get detailed Windows version: {e}") - # Keep the basic os_info - + win_ver = platform.version() + os_info = f"Windows {win_ver}" + except Exception: + pass # Fail silently + uptime_seconds = time.time() - psutil.boot_time() - days, remainder = divmod(uptime_seconds, 86400) - hours, remainder = divmod(remainder, 3600) - minutes, seconds = divmod(remainder, 60) + days, rem = divmod(uptime_seconds, 86400) + hours, rem = divmod(rem, 3600) + minutes, _ = divmod(rem, 60) + uptime_str = f"{int(days)}d {int(hours)}h {int(minutes)}m" - uptime_str = "" - if days > 0: - uptime_str += f"{int(days)}d " - uptime_str += f"{int(hours):02}:{int(minutes):02}:{int(seconds):02}" - uptime = uptime_str.strip() - - # Hardware information - use a shorter interval for CPU usage + # Hardware information cpu_usage = psutil.cpu_percent(interval=0.1) - - # Get CPU info with a timeout to prevent hanging + try: - # Use a simpler approach for CPU name to avoid potential slowdowns - if platform.system() == "Windows": - cpu_name_base = platform.processor() - elif platform.system() == "Linux": - try: - with open("/proc/cpuinfo", "r") as f: - for line in f: - if line.startswith("model name"): - cpu_name_base = line.split(":")[1].strip() - break - else: - cpu_name_base = "Unknown CPU" - except: - cpu_name_base = platform.processor() or "Unknown CPU" - else: - cpu_name_base = platform.processor() or "Unknown CPU" - + if system == "Linux": + with open("/proc/cpuinfo") as f: + for line in f: + if line.startswith("model name"): + cpu_name_base = line.split(":")[1].strip() + break + else: # Windows or fallback + cpu_name_base = platform.processor() or "N/A" + physical_cores = psutil.cpu_count(logical=False) total_threads = psutil.cpu_count(logical=True) cpu_name = f"{cpu_name_base} ({physical_cores}C/{total_threads}T)" - except Exception as e: - print(f"Error getting CPU info: {e}") + except Exception: cpu_name = "N/A" - # Get motherboard information motherboard_info = self._get_motherboard_info() - memory = psutil.virtual_memory() - ram_usage = f"{memory.used // (1024 ** 2)} MB / {memory.total // (1024 ** 2)} MB ({memory.percent}%)" - - # GPU Information (using GPUtil for cross-platform consistency if available) - gpu_info_lines = [] + + # GPU Information try: gpus = GPUtil.getGPUs() if gpus: - for gpu in gpus: - gpu_info_lines.append( - f"{gpu.name} ({gpu.load*100:.1f}% Load, {gpu.memoryUsed:.0f}/{gpu.memoryTotal:.0f} MB VRAM)" - ) - gpu_info = "\n".join(gpu_info_lines) + gpu_info_lines = [f"{gpu.name} ({gpu.load*100:.1f}% Load)" for gpu in gpus] + gpu_info = " | ".join(gpu_info_lines) else: - gpu_info = "No dedicated GPU detected by GPUtil." - except ImportError: - gpu_info = "GPUtil library not installed. Cannot get detailed GPU info." - except Exception as e: - print(f"Error getting GPU info via GPUtil: {e}") - gpu_info = f"Error retrieving GPU info: {e}" + gpu_info = "No dedicated GPU detected" + except Exception: + gpu_info = "N/A" - # Determine user and avatar URL based on context type - if isinstance(context_or_interaction, commands.Context): - user = context_or_interaction.author - avatar_url = user.display_avatar.url - elif isinstance(context_or_interaction, discord.Interaction): - user = context_or_interaction.user - avatar_url = user.display_avatar.url - else: - # Fallback or handle error if needed - user = self.bot.user # Or some default - avatar_url = self.bot.user.display_avatar.url if self.bot.user else None + # Determine user based on context type + user = context_or_interaction.author if isinstance(context_or_interaction, commands.Context) else context_or_interaction.user - class SystemStatusView(ui.LayoutView): - def __init__(self) -> None: - super().__init__(timeout=None) + return SystemStatusView( + bot_user=self.bot.user, + guild_count=guild_count, + user_count=user_count, + os_info=os_info, + distro_info=distro_info_str, + hostname=hostname, + uptime=uptime_str, + motherboard_info=motherboard_info, + cpu_name=cpu_name, + cpu_usage=cpu_usage, + ram_used=memory.used, + ram_total=memory.total, + ram_percent=memory.percent, + gpu_info=gpu_info, + requester=user, + ) - container = ui.Container(accent_colour=discord.Color.blue()) - self.add_item(container) - - if bot_user: - header = ui.Section( - accessory=ui.Thumbnail(media=bot_user.display_avatar.url) - ) - header.add_item(ui.TextDisplay("**📊 System Status**")) - container.add_item(header) - else: - container.add_item(ui.TextDisplay("**📊 System Status**")) - - container.add_item(ui.Separator(spacing=discord.SeparatorSpacing.small)) - - container.add_item(ui.TextDisplay("**🤖 Bot Information**")) - if bot_user: - bot_info = ( - f"**Name:** {bot_user.name}\n" - f"**ID:** {bot_user.id}\n" - f"**Servers:** {guild_count}\n" - f"**Unique Users:** {user_count}" - ) - else: - bot_info = "Bot user information not available." - container.add_item(ui.TextDisplay(bot_info)) - - container.add_item(ui.Separator(spacing=discord.SeparatorSpacing.small)) - - container.add_item(ui.TextDisplay("**🖥️ System Information**")) - container.add_item( - ui.TextDisplay( - f"**OS:** {os_info}{distro_info_str}\n" - f"**Hostname:** {hostname}\n" - f"**Uptime:** {uptime}" - ) - ) - - container.add_item(ui.Separator(spacing=discord.SeparatorSpacing.small)) - - container.add_item(ui.TextDisplay("**⚙️ Hardware Information**")) - container.add_item( - ui.TextDisplay( - f"**Device Model:** {motherboard_info}\n" - f"**CPU:** {cpu_name}\n" - f"**CPU Usage:** {cpu_usage}%\n" - f"**RAM Usage:** {ram_usage}\n" - f"**GPU Info:**\n{gpu_info}" - ) - ) - - container.add_item(ui.Separator(spacing=discord.SeparatorSpacing.small)) - - timestamp = discord.utils.format_dt(discord.utils.utcnow(), style="f") - footer_text = timestamp - if user: - footer_text += f" | Requested by: {user.display_name}" - container.add_item(ui.TextDisplay(footer_text)) - - return SystemStatusView() - - # --- Prefix Command --- - @commands.command(name="systemcheck") - async def system_check(self, ctx: commands.Context): - """Check the bot and system status.""" - view = await self._build_system_check_view(ctx) - await ctx.reply(view=view) - - # --- Slash Command --- - @app_commands.command( - name="systemcheck", description="Check the bot and system status" - ) - async def system_check_slash(self, interaction: discord.Interaction): - """Slash command version of system check.""" - # Defer the response to prevent interaction timeout - await interaction.response.defer(thinking=True) - try: - view = await self._build_system_check_view(interaction) - await interaction.followup.send(view=view) - except Exception as e: - # Handle any errors that might occur during processing - print(f"Error in system_check_slash: {e}") - await interaction.followup.send( - f"An error occurred while checking system status: {e}" - ) - - def _get_motherboard_info(self): + def _get_motherboard_info(self) -> str: """Get motherboard information based on the operating system.""" system = platform.system() try: - if system == "Windows": - if WMI_AVAILABLE: - w = wmi.WMI() - for board in w.Win32_BaseBoard(): - return f"{board.Manufacturer} {board.Product}" - return "WMI module not available" + if system == "Windows" and WMI_AVAILABLE: + w = wmi.WMI() + board = w.Win32_BaseBoard()[0] + return f"{board.Manufacturer} {board.Product}" elif system == "Linux": - # Read motherboard product name from sysfs - try: - with open("/sys/devices/virtual/dmi/id/product_name", "r") as f: - product_name = f.read().strip() - return product_name if product_name else "Unknown motherboard" - except FileNotFoundError: - return "/sys/devices/virtual/dmi/id/product_name not found" - except Exception as e: - return f"Error reading motherboard info: {e}" - except Exception as e: - return f"Error: {str(e)}" - else: - return f"Unsupported OS: {system}" - except Exception as e: - print(f"Error getting motherboard info: {e}") - return "Error retrieving motherboard info" + with open("/sys/devices/virtual/dmi/id/product_name", "r") as f: + return f.read().strip() + return "N/A" + except Exception: + return "N/A" + + @commands.command(name="systemcheck", aliases=["status"]) + async def system_check(self, ctx: commands.Context): + """Check the bot and system status.""" + view = await self._build_system_check_view(ctx) + await ctx.reply(view=view, mention_author=False) + + @app_commands.command(name="systemcheck", description="Check the bot and system status") + async def system_check_slash(self, interaction: discord.Interaction): + """Slash command version of system check.""" + await interaction.response.defer(thinking=True, ephemeral=False) + view = await self._build_system_check_view(interaction) + await interaction.followup.send(view=view) async def setup(bot): - await bot.add_cog(SystemCheckCog(bot)) + await bot.add_cog(SystemCheckCog(bot)) \ No newline at end of file