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:
parent
d7d0c50fef
commit
96fdf225a8
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user