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.
This commit is contained in:
Slipstreamm 2025-06-14 13:15:28 -06:00
parent d7d0c50fef
commit 96fdf225a8

View File

@ -5,257 +5,267 @@ 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:
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:
cpu_name_base = "Unknown CPU"
except:
cpu_name_base = platform.processor() or "Unknown CPU"
else:
cpu_name_base = platform.processor() or "Unknown CPU"
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)
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}"
)
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.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:
if system == "Windows" and WMI_AVAILABLE:
w = wmi.WMI()
for board in w.Win32_BaseBoard():
board = w.Win32_BaseBoard()[0]
return f"{board.Manufacturer} {board.Product}"
return "WMI module not available"
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"
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):