263 lines
10 KiB
Python
263 lines
10 KiB
Python
import discord
|
|
from discord.ext import commands
|
|
from discord import app_commands
|
|
import time
|
|
import psutil
|
|
import platform
|
|
import GPUtil
|
|
import distro # Ensure this is installed
|
|
|
|
# Import wmi for Windows motherboard info
|
|
try:
|
|
import wmi
|
|
|
|
WMI_AVAILABLE = True
|
|
except ImportError:
|
|
WMI_AVAILABLE = False
|
|
|
|
|
|
class SystemCheckCog(commands.Cog):
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
|
|
async def _system_check_logic(self, context_or_interaction):
|
|
"""Check the bot and system status."""
|
|
# Defer the response to prevent interaction timeout
|
|
await context_or_interaction.response.defer(thinking=True)
|
|
try:
|
|
embed = await self._system_check_logic(context_or_interaction)
|
|
await context_or_interaction.followup.send(embed=embed)
|
|
except Exception as e:
|
|
print(f"Error in systemcheck command: {e}")
|
|
await context_or_interaction.followup.send(
|
|
f"An error occurred while checking system status: {e}"
|
|
)
|
|
|
|
async def _system_check_logic(self, context_or_interaction):
|
|
"""Return detailed bot and system information as a Discord embed."""
|
|
# 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_count = len(user_ids)
|
|
|
|
# System information
|
|
system = platform.system()
|
|
os_info = f"{system} {platform.release()}"
|
|
hostname = platform.node()
|
|
distro_info_str = "" # Renamed variable
|
|
|
|
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})"
|
|
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
|
|
|
|
uptime_seconds = time.time() - psutil.boot_time()
|
|
days, remainder = divmod(uptime_seconds, 86400)
|
|
hours, remainder = divmod(remainder, 3600)
|
|
minutes, seconds = divmod(remainder, 60)
|
|
|
|
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
|
|
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"
|
|
|
|
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}")
|
|
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 = []
|
|
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)
|
|
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}"
|
|
|
|
# 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
|
|
|
|
# Create embed
|
|
embed = discord.Embed(title="📊 System Status", color=discord.Color.blue())
|
|
if bot_user:
|
|
embed.set_thumbnail(url=bot_user.display_avatar.url)
|
|
|
|
# Bot Info Field
|
|
if bot_user:
|
|
embed.add_field(
|
|
name="🤖 Bot Information",
|
|
value=f"**Name:** {bot_user.name}\n"
|
|
f"**ID:** {bot_user.id}\n"
|
|
f"**Servers:** {guild_count}\n"
|
|
f"**Unique Users:** {user_count}",
|
|
inline=False,
|
|
)
|
|
else:
|
|
embed.add_field(
|
|
name="🤖 Bot Information",
|
|
value="Bot user information not available.",
|
|
inline=False,
|
|
)
|
|
|
|
# System Info Field
|
|
embed.add_field(
|
|
name="🖥️ System Information",
|
|
value=f"**OS:** {os_info}{distro_info_str}\n" # Use renamed variable
|
|
f"**Hostname:** {hostname}\n"
|
|
f"**Uptime:** {uptime}",
|
|
inline=False,
|
|
)
|
|
|
|
# Hardware Info Field
|
|
embed.add_field(
|
|
name="⚙️ Hardware Information",
|
|
value=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}",
|
|
inline=False,
|
|
)
|
|
|
|
if user:
|
|
embed.set_footer(
|
|
text=f"Requested by: {user.display_name}", icon_url=avatar_url
|
|
)
|
|
|
|
embed.timestamp = discord.utils.utcnow()
|
|
return embed
|
|
|
|
# --- Prefix Command ---
|
|
@commands.command(name="systemcheck")
|
|
async def system_check(self, ctx: commands.Context):
|
|
"""Check the bot and system status."""
|
|
embed = await self._system_check_logic(ctx) # Pass context
|
|
await ctx.reply(embed=embed)
|
|
|
|
# --- 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:
|
|
embed = await self._system_check_logic(interaction) # Pass interaction
|
|
# Use followup since we've already deferred
|
|
await interaction.followup.send(embed=embed)
|
|
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):
|
|
"""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"
|
|
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"
|
|
|
|
|
|
async def setup(bot):
|
|
await bot.add_cog(SystemCheckCog(bot))
|