Reverted to 5d746027e2 for funguy.py and loadplugin.py

This commit is contained in:
2025-10-19 15:55:44 -05:00
parent d61036d5ac
commit ed62397661
5 changed files with 939 additions and 86 deletions

View File

@@ -17,15 +17,6 @@ import toml # Library for parsing TOML configuration files
# Importing FunguyConfig class from plugins.config module
from plugins.config import FunguyConfig
# Whitelist of allowed plugins to prevent arbitrary code execution
ALLOWED_PLUGINS = {
'ai', 'config', 'cron', 'date', 'fortune', 'help', 'isup', 'karma',
'loadplugin', 'plugins', 'proxy', 'sd_text', 'stable-diffusion',
'xkcd', 'youtube-preview', 'youtube-search', 'weather', 'urbandictionary',
'bitcoin', 'dns', 'shodan', 'dnsdumpster', 'exploitdb', 'headers', 'hashid',
'sslscan'
}
class FunguyBot:
"""
A bot class for managing plugins and handling commands in a Matrix chat environment.
@@ -87,22 +78,17 @@ class FunguyBot:
"""
Method to load plugins from the specified directory.
"""
# Iterating through whitelisted plugins only
for plugin_name in ALLOWED_PLUGINS:
plugin_file = os.path.join(self.PLUGINS_DIR, f"{plugin_name}.py")
# Verify that the plugin file exists
if not os.path.isfile(plugin_file):
logging.warning(f"Plugin file not found: {plugin_file}, skipping")
continue
try:
# Importing plugin module dynamically with validated plugin name
module = importlib.import_module(f"{self.PLUGINS_DIR}.{plugin_name}")
self.PLUGINS[plugin_name] = module # Storing loaded plugin module
logging.info(f"Loaded plugin: {plugin_name}") # Logging successful plugin loading
except Exception as e:
logging.error(f"Error loading plugin {plugin_name}: {e}") # Logging error if plugin loading fails
# Iterating through files in the plugins directory
for plugin_file in os.listdir(self.PLUGINS_DIR):
if plugin_file.endswith(".py"): # Checking if file is a Python file
plugin_name = os.path.splitext(plugin_file)[0] # Extracting plugin name
try:
# Importing plugin module dynamically
module = importlib.import_module(f"{self.PLUGINS_DIR}.{plugin_name}")
self.PLUGINS[plugin_name] = module # Storing loaded plugin module
logging.info(f"Loaded plugin: {plugin_name}") # Logging successful plugin loading
except Exception as e:
logging.error(f"Error loading plugin {plugin_name}: {e}") # Logging error if plugin loading fails
def reload_plugins(self):
"""
@@ -247,10 +233,3 @@ class FunguyBot:
if __name__ == "__main__":
bot = FunguyBot() # Creating instance of FunguyBot
bot.run() # Running the bot
from plugins import cron # Import your cron plugin
# After bot starts running, periodically check for cron jobs
while True:
asyncio.sleep(60) # Check every minute (adjust as needed)
cron.run_cron_jobs(bot) # Check and execute cron jobs

482
plugins/ddg.py Normal file
View File

@@ -0,0 +1,482 @@
"""
This plugin provides DuckDuckGo search functionality using the DuckDuckGo Instant Answer API.
"""
import logging
import requests
import json
import simplematrixbotlib as botlib
from urllib.parse import quote, urlencode
import html
DDG_API_URL = "https://api.duckduckgo.com/"
DDG_SEARCH_URL = "https://html.duckduckgo.com/html/"
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle DuckDuckGo search commands.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (Bot): The bot object.
prefix (str): The command prefix.
config (dict): Configuration parameters.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("ddg"):
logging.info("Received !ddg command")
args = match.args()
if len(args) < 1:
await show_usage(room, bot)
return
subcommand = args[0].lower()
if subcommand == "search":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg search <query>")
return
query = ' '.join(args[1:])
await ddg_search(room, bot, query)
elif subcommand == "instant":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg instant <query>")
return
query = ' '.join(args[1:])
await ddg_instant_answer(room, bot, query)
elif subcommand == "image":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg image <query>")
return
query = ' '.join(args[1:])
await ddg_image_search(room, bot, query)
elif subcommand == "news":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg news <query>")
return
query = ' '.join(args[1:])
await ddg_news_search(room, bot, query)
elif subcommand == "video":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg video <query>")
return
query = ' '.join(args[1:])
await ddg_video_search(room, bot, query)
elif subcommand == "bang":
if len(args) < 2:
await show_bang_help(room, bot)
return
bang_query = ' '.join(args[1:])
await ddg_bang_search(room, bot, bang_query)
elif subcommand == "define":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg define <word>")
return
word = ' '.join(args[1:])
await ddg_definition(room, bot, word)
elif subcommand == "calc":
if len(args) < 2:
await bot.api.send_text_message(room.room_id, "Usage: !ddg calc <expression>")
return
expression = ' '.join(args[1:])
await ddg_calculator(room, bot, expression)
elif subcommand == "weather":
location = ' '.join(args[1:]) if len(args) > 1 else ""
await ddg_weather(room, bot, location)
elif subcommand == "help":
await show_usage(room, bot)
else:
# Default to instant answer search
query = ' '.join(args)
await ddg_instant_answer(room, bot, query)
async def show_usage(room, bot):
"""Display DuckDuckGo command usage."""
usage = """
<strong>🦆 DuckDuckGo Search Commands</strong>
<strong>!ddg &lt;query&gt;</strong> - Instant answer search (default)
<strong>!ddg search &lt;query&gt;</strong> - Web search with results
<strong>!ddg instant &lt;query&gt;</strong> - Instant answer with detailed info
<strong>!ddg image &lt;query&gt;</strong> - Image search
<strong>!ddg news &lt;query&gt;</strong> - News search
<strong>!ddg video &lt;query&gt;</strong> - Video search
<strong>!ddg bang &lt;!bang query&gt;</strong> - Use DuckDuckGo bangs
<strong>!ddg define &lt;word&gt;</strong> - Word definitions
<strong>!ddg calc &lt;expression&gt;</strong> - Calculator
<strong>!ddg weather [location]</strong> - Weather information
<strong>!ddg help</strong> - Show this help
<strong>Examples:</strong>
• <code>!ddg python programming</code>
• <code>!ddg search matrix protocol</code>
• <code>!ddg image cute cats</code>
• <code>!ddg bang !w matrix</code>
• <code>!ddg define serendipity</code>
• <code>!ddg calc 2+2*5</code>
• <code>!ddg weather London</code>
<strong>Popular Bangs:</strong>
• <code>!w</code> - Wikipedia
• <code>!g</code> - Google
• <code>!yt</code> - YouTube
• <code>!aw</code> - ArchWiki
• <code>!gh</code> - GitHub
• <code>!so</code> - Stack Overflow
"""
await bot.api.send_markdown_message(room.room_id, usage)
async def show_bang_help(room, bot):
"""Display DuckDuckGo bang help."""
bang_help = """
<strong>🦆 DuckDuckGo Bangs</strong>
<strong>Usage:</strong> <code>!ddg bang &lt;!bang query&gt;</code>
<strong>Popular Bangs:</strong>
• <code>!ddg bang !w matrix</code> - Search Wikipedia
• <code>!ddg bang !g python</code> - Search Google
• <code>!ddg bang !yt music</code> - Search YouTube
• <code>!ddg bang !aw arch</code> - Search ArchWiki
• <code>!ddg bang !gh repository</code> - Search GitHub
• <code>!ddg bang !so error</code> - Search Stack Overflow
• <code>!ddg bang !amazon book</code> - Search Amazon
• <code>!ddg bang !imdb movie</code> - Search IMDb
• <code>!ddg bang !reddit topic</code> - Search Reddit
• <code>!ddg bang !tw tweet</code> - Search Twitter
<strong>More Bangs:</strong>
• <code>!ddg</code> - DuckDuckGo
• <code>!bing</code> - Bing
• <code>!ddgimages</code> - DuckDuckGo Images
• <code>!npm</code> - npm packages
• <code>!cpp</code> - C++ reference
• <code>!python</code> - Python docs
• <code>!rust</code> - Rust docs
• <code>!mdn</code> - MDN Web Docs
<em>Thousands of bangs available! See: https://duckduckgo.com/bangs</em>
"""
await bot.api.send_markdown_message(room.room_id, bang_help)
async def ddg_instant_answer(room, bot, query):
"""Get DuckDuckGo instant answer."""
try:
params = {
'q': query,
'format': 'json',
'no_html': '1',
'skip_disambig': '1',
'no_redirect': '1'
}
logging.info(f"Fetching DuckDuckGo instant answer for: {query}")
response = requests.get(DDG_API_URL, params=params, timeout=10)
if response.status_code != 200:
# If API fails, provide direct search link
search_url = f"https://duckduckgo.com/?q={quote(query)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🦆 DuckDuckGo: {html.escape(query)}</strong><br><br>"
f"API temporarily unavailable. <a href='{search_url}'>Search on DuckDuckGo</a>"
)
return
data = response.json()
output = f"<strong>🦆 DuckDuckGo: {html.escape(query)}</strong><br><br>"
# Handle different answer types
if data.get('AbstractText'):
# Wikipedia-style answer
output += f"<strong>📚 {data.get('Heading', 'Definition')}</strong><br>"
output += f"{html.escape(data['AbstractText'])}<br>"
if data.get('AbstractURL'):
output += f"<a href='{data['AbstractURL']}'>Read more on {data.get('AbstractSource', 'Wikipedia')}</a><br>"
elif data.get('Answer'):
# Direct answer
output += f"<strong>💡 Answer</strong><br>"
output += f"{html.escape(data['Answer'])}<br>"
elif data.get('Definition'):
# Definition
output += f"<strong>📖 Definition</strong><br>"
output += f"{html.escape(data['Definition'])}<br>"
if data.get('DefinitionSource'):
output += f"<em>Source: {data['DefinitionSource']}</em><br>"
elif data.get('Results'):
# List of results
output += f"<strong>🔍 Results</strong><br>"
for result in data['Results'][:3]:
output += f"• <a href='{result.get('FirstURL', '#')}'>{html.escape(result.get('Text', 'Result'))}</a><br>"
elif data.get('RelatedTopics'):
# Related topics
output += f"<strong>🔗 Related Topics</strong><br>"
for topic in data['RelatedTopics'][:3]:
if isinstance(topic, dict) and topic.get('FirstURL'):
output += f"• <a href='{topic['FirstURL']}'>{html.escape(topic.get('Text', 'Topic'))}</a><br>"
elif isinstance(topic, dict) and topic.get('Name'):
output += f"{html.escape(topic['Name'])}<br>"
else:
# No instant answer found, show search results
output += "<strong>🔍 No instant answer found.</strong><br>"
# Add search link
search_url = f"https://duckduckgo.com/?q={quote(query)}"
output += f"<br><a href='{search_url}'>View all results on DuckDuckGo</a>"
await bot.api.send_markdown_message(room.room_id, output)
except Exception as e:
# Fallback to direct search link
search_url = f"https://duckduckgo.com/?q={quote(query)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🦆 DuckDuckGo: {html.escape(query)}</strong><br><br>"
f"Error accessing API. <a href='{search_url}'>Search on DuckDuckGo</a>"
)
logging.error(f"Error in ddg_instant_answer: {e}")
async def ddg_search(room, bot, query):
"""Perform web search with multiple results."""
try:
await ddg_web_search(room, bot, query, limit=5)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error performing search: {str(e)}")
async def ddg_web_search(room, bot, query, limit=5):
"""Perform web search and return results."""
try:
params = {
'q': query,
'format': 'json'
}
response = requests.get(DDG_API_URL, params=params, timeout=10)
if response.status_code != 200:
# Fallback to direct search
search_url = f"https://duckduckgo.com/?q={quote(query)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🔍 DuckDuckGo Search: {html.escape(query)}</strong><br><br>"
f"API temporarily unavailable. <a href='{search_url}'>Search on DuckDuckGo</a>"
)
return
data = response.json()
output = f"<strong>🔍 DuckDuckGo Search: {html.escape(query)}</strong><br><br>"
results_shown = 0
# Show instant answer if available
if data.get('AbstractText') and results_shown < limit:
output += f"<strong>💡 {data.get('Heading', 'Instant Answer')}</strong><br>"
abstract = data['AbstractText'][:200] + "..." if len(data['AbstractText']) > 200 else data['AbstractText']
output += f"{html.escape(abstract)}<br>"
if data.get('AbstractURL'):
output += f"<a href='{data['AbstractURL']}'>Read more</a><br>"
output += "<br>"
results_shown += 1
# Show web results
if data.get('Results') and results_shown < limit:
output += "<strong>🌐 Web Results</strong><br>"
for result in data['Results'][:limit - results_shown]:
output += f"• <a href='{result.get('FirstURL', '#')}'>{html.escape(result.get('Text', 'Result'))}</a><br>"
results_shown += 1
# Show related topics
if data.get('RelatedTopics') and results_shown < limit:
output += "<strong>🔗 Related Topics</strong><br>"
for topic in data['RelatedTopics'][:limit - results_shown]:
if isinstance(topic, dict) and topic.get('FirstURL'):
output += f"• <a href='{topic['FirstURL']}'>{html.escape(topic.get('Text', 'Topic'))}</a><br>"
results_shown += 1
# Add search link
search_url = f"https://duckduckgo.com/?q={quote(query)}"
output += f"<br><a href='{search_url}'>View all results on DuckDuckGo</a>"
await bot.api.send_markdown_message(room.room_id, output)
except Exception as e:
# Fallback to direct search
search_url = f"https://duckduckgo.com/?q={quote(query)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🔍 DuckDuckGo Search: {html.escape(query)}</strong><br><br>"
f"Error accessing API. <a href='{search_url}'>Search on DuckDuckGo</a>"
)
logging.error(f"Error in ddg_web_search: {e}")
async def ddg_image_search(room, bot, query):
"""Perform image search."""
try:
params = {
'q': query,
'format': 'json',
'iax': 'images',
'ia': 'images'
}
response = requests.get(DDG_API_URL, params=params, timeout=10)
if response.status_code != 200:
search_url = f"https://duckduckgo.com/?q={quote(query)}&iax=images&ia=images"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🖼️ DuckDuckGo Images: {html.escape(query)}</strong><br><br>"
f"API temporarily unavailable. <a href='{search_url}'>Search images on DuckDuckGo</a>"
)
return
data = response.json()
output = f"<strong>🖼️ DuckDuckGo Images: {html.escape(query)}</strong><br><br>"
if data.get('Results'):
output += "<strong>📸 Image Results</strong><br>"
for image in data['Results'][:3]:
output += f"• <a href='{image.get('Image', '#')}'>{html.escape(image.get('Title', 'Image'))}</a><br>"
if image.get('Width') and image.get('Height'):
output += f" Size: {image['Width']}×{image['Height']}<br>"
else:
output += "No image results found.<br>"
# Add search link
search_url = f"https://duckduckgo.com/?q={quote(query)}&iax=images&ia=images"
output += f"<br><a href='{search_url}'>View all images on DuckDuckGo</a>"
await bot.api.send_markdown_message(room.room_id, output)
except Exception as e:
search_url = f"https://duckduckgo.com/?q={quote(query)}&iax=images&ia=images"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🖼️ DuckDuckGo Images: {html.escape(query)}</strong><br><br>"
f"Error accessing API. <a href='{search_url}'>Search images on DuckDuckGo</a>"
)
async def ddg_news_search(room, bot, query):
"""Perform news search."""
try:
search_url = f"https://duckduckgo.com/?q={quote(query)}&iar=news"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>📰 DuckDuckGo News: {html.escape(query)}</strong><br><br>"
f"<a href='{search_url}'>View news on DuckDuckGo</a>"
)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error performing news search: {str(e)}")
async def ddg_video_search(room, bot, query):
"""Perform video search."""
try:
search_url = f"https://duckduckgo.com/?q={quote(query)}&iar=videos"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🎬 DuckDuckGo Videos: {html.escape(query)}</strong><br><br>"
f"<a href='{search_url}'>View videos on DuckDuckGo</a>"
)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error performing video search: {str(e)}")
async def ddg_bang_search(room, bot, bang_query):
"""Perform search using DuckDuckGo bangs."""
try:
# Create search URL directly - this is more reliable than API for bangs
search_url = f"https://duckduckgo.com/?q={quote(bang_query)}"
# Common bangs with descriptions
bang_descriptions = {
'!w': 'Wikipedia',
'!g': 'Google',
'!yt': 'YouTube',
'!aw': 'ArchWiki',
'!gh': 'GitHub',
'!so': 'Stack Overflow',
'!amazon': 'Amazon',
'!imdb': 'IMDb',
'!reddit': 'Reddit',
'!tw': 'Twitter'
}
# Extract bang for description
bang = bang_query.split(' ')[0] if ' ' in bang_query else bang_query
description = bang_descriptions.get(bang, 'Site-specific search')
output = f"<strong>🎯 DuckDuckGo Bang: {html.escape(bang)}</strong><br>"
output += f"<strong>Description:</strong> {description}<br>"
if ' ' in bang_query:
output += f"<strong>Query:</strong> {html.escape(bang_query.split(' ', 1)[1])}<br><br>"
output += f"<a href='{search_url}'>Search with {html.escape(bang)} on DuckDuckGo</a>"
await bot.api.send_markdown_message(room.room_id, output)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error with bang search: {str(e)}")
async def ddg_definition(room, bot, word):
"""Get word definition."""
try:
search_url = f"https://duckduckgo.com/?q=define+{quote(word)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>📖 Definition: {html.escape(word)}</strong><br><br>"
f"<a href='{search_url}'>Get definition on DuckDuckGo</a>"
)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error getting definition: {str(e)}")
async def ddg_calculator(room, bot, expression):
"""Use DuckDuckGo as a calculator."""
try:
search_url = f"https://duckduckgo.com/?q={quote(expression)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🧮 Calculator: {html.escape(expression)}</strong><br><br>"
f"<a href='{search_url}'>Calculate on DuckDuckGo</a>"
)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error with calculator: {str(e)}")
async def ddg_weather(room, bot, location):
"""Get weather information."""
try:
if not location:
location = "current location"
search_url = f"https://duckduckgo.com/?q=weather+{quote(location)}"
await bot.api.send_markdown_message(
room.room_id,
f"<strong>🌤️ Weather: {html.escape(location)}</strong><br><br>"
f"<a href='{search_url}'>Get weather on DuckDuckGo</a>"
)
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error getting weather: {str(e)}")

View File

@@ -11,11 +11,6 @@ import sys # Import sys module for unloading plugins
# Dictionary to store loaded plugins
PLUGINS = {}
# Whitelist of allowed plugins to prevent arbitrary code execution
ALLOWED_PLUGINS = {'ai', 'config', 'cron', 'date', 'fortune', 'help', 'isup', 'karma',
'loadplugin', 'plugins', 'proxy', 'sd_text', 'stable-diffusion',
'xkcd', 'youtube-preview', 'youtube-search'}
async def load_plugin(plugin_name):
"""
Asynchronously loads a plugin.
@@ -26,56 +21,9 @@ async def load_plugin(plugin_name):
Returns:
bool: True if the plugin is loaded successfully, False otherwise.
"""
# Validate plugin name against whitelist
if plugin_name not in ALLOWED_PLUGINS:
logging.error(f"Plugin '{plugin_name}' is not whitelisted")
return False
# Verify that the plugin file exists in the plugins directory
plugin_path = os.path.join("plugins", f"{plugin_name}.py")
if not os.path.isfile(plugin_path):
logging.error(f"Plugin file not found: {plugin_path}")
return False
try:
# Create a mapping of whitelisted plugins to their module paths
plugin_modules = {
'ai': 'plugins.ai',
'config': 'plugins.config',
'cron': 'plugins.cron',
'date': 'plugins.date',
'fortune': 'plugins.fortune',
'help': 'plugins.help',
'isup': 'plugins.isup',
'karma': 'plugins.karma',
'loadplugin': 'plugins.loadplugin',
'plugins': 'plugins.plugins',
'proxy': 'plugins.proxy',
'sd_text': 'plugins.sd_text',
'stable-diffusion': 'plugins.stable-diffusion',
'xkcd': 'plugins.xkcd',
'youtube-preview': 'plugins.youtube-preview',
'youtube-search': 'plugins.youtube-search',
'weather': 'plugins.weather',
'urbandictionary': 'plugins.urbandictionary',
'bitcoin':'plugins.bitcoin',
'dns':'plugins.dns',
'shodan':'plugins.shodan',
'dnsdumpster': 'plugins.dnsdumpster',
'exploitdb': 'plugins.exploitdb',
'headers': 'plugins.headers',
'hashid': 'plugins.hashid',
'sslscan': 'plugins.sslscan'
}
# Get the module path from the mapping
module_path = plugin_modules.get(plugin_name)
if not module_path:
logging.error(f"Plugin '{plugin_name}' not found in plugin mapping")
return False
# Import the plugin module using the validated module path
module = importlib.import_module(module_path)
# Import the plugin module
module = importlib.import_module(f"plugins.{plugin_name}")
# Add the plugin module to the PLUGINS dictionary
PLUGINS[plugin_name] = module
logging.info(f"Loaded plugin: {plugin_name}")
@@ -164,3 +112,4 @@ async def handle_command(room, message, bot, prefix, config):
else:
# Send unauthorized message if the sender is not the admin
await bot.api.send_text_message(room.room_id, "You are not authorized to unload plugins.")

442
plugins/sysinfo.py Normal file
View File

@@ -0,0 +1,442 @@
"""
This plugin provides comprehensive system information and resource monitoring.
"""
import logging
import platform
import os
import psutil
import socket
import datetime
import simplematrixbotlib as botlib
import subprocess
import sys
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle !sysinfo command for system information.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (Bot): The bot object.
prefix (str): The command prefix.
config (dict): Configuration parameters.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("sysinfo"):
logging.info("Received !sysinfo command")
args = match.args()
if len(args) > 0 and args[0].lower() == 'help':
await show_usage(room, bot)
return
await get_system_info(room, bot)
async def show_usage(room, bot):
"""Display sysinfo command usage."""
usage = """
<strong>💻 System Information Plugin</strong>
<strong>!sysinfo</strong> - Display comprehensive system information
<strong>!sysinfo help</strong> - Show this help message
<strong>Information Provided:</strong>
• System hardware (CPU, RAM, storage, GPU)
• Operating system and kernel details
• Network configuration and interfaces
• Running processes and resource usage
• Temperature and hardware sensors
• System load and performance metrics
• Docker container status (if available)
"""
await bot.api.send_markdown_message(room.room_id, usage)
async def get_system_info(room, bot):
"""Collect and display comprehensive system information."""
try:
await bot.api.send_text_message(room.room_id, "🔍 Gathering system information...")
sysinfo = {
'system': await get_system_info_basic(),
'cpu': await get_cpu_info(),
'memory': await get_memory_info(),
'storage': await get_storage_info(),
'network': await get_network_info(),
'processes': await get_process_info(),
'docker': await get_docker_info(),
'sensors': await get_sensor_info(),
'gpu': await get_gpu_info()
}
output = await format_system_info(sysinfo)
await bot.api.send_markdown_message(room.room_id, output)
logging.info("Sent system information")
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error gathering system info: {str(e)}")
logging.error(f"Error in get_system_info: {e}")
async def get_system_info_basic():
"""Get basic system information."""
try:
return {
'hostname': socket.gethostname(),
'os': platform.system(),
'os_release': platform.release(),
'os_version': platform.version(),
'architecture': platform.architecture()[0],
'machine': platform.machine(),
'processor': platform.processor(),
'boot_time': datetime.datetime.fromtimestamp(psutil.boot_time()).strftime("%Y-%m-%d %H:%M:%S"),
'uptime': str(datetime.timedelta(seconds=psutil.boot_time() - datetime.datetime.now().timestamp())).split('.')[0],
'users': len(psutil.users())
}
except Exception as e:
return {'error': str(e)}
async def get_cpu_info():
"""Get CPU information and usage."""
try:
cpu_times = psutil.cpu_times_percent(interval=1)
cpu_freq = psutil.cpu_freq()
return {
'physical_cores': psutil.cpu_count(logical=False),
'total_cores': psutil.cpu_count(logical=True),
'max_frequency': f"{cpu_freq.max:.1f} MHz" if cpu_freq else "N/A",
'current_frequency': f"{cpu_freq.current:.1f} MHz" if cpu_freq else "N/A",
'usage_percent': psutil.cpu_percent(interval=1),
'user_time': cpu_times.user,
'system_time': cpu_times.system,
'idle_time': cpu_times.idle,
'load_avg': os.getloadavg() if hasattr(os, 'getloadavg') else "N/A"
}
except Exception as e:
return {'error': str(e)}
async def get_memory_info():
"""Get memory and swap information."""
try:
memory = psutil.virtual_memory()
swap = psutil.swap_memory()
return {
'total': f"{memory.total / (1024**3):.2f} GB",
'available': f"{memory.available / (1024**3):.2f} GB",
'used': f"{memory.used / (1024**3):.2f} GB",
'usage_percent': memory.percent,
'swap_total': f"{swap.total / (1024**3):.2f} GB",
'swap_used': f"{swap.used / (1024**3):.2f} GB",
'swap_free': f"{swap.free / (1024**3):.2f} GB",
'swap_percent': swap.percent
}
except Exception as e:
return {'error': str(e)}
async def get_storage_info():
"""Get storage device information."""
try:
partitions = psutil.disk_partitions()
storage_info = []
for partition in partitions:
try:
usage = psutil.disk_usage(partition.mountpoint)
storage_info.append({
'device': partition.device,
'mountpoint': partition.mountpoint,
'fstype': partition.fstype,
'total': f"{usage.total / (1024**3):.2f} GB",
'used': f"{usage.used / (1024**3):.2f} GB",
'free': f"{usage.free / (1024**3):.2f} GB",
'percent': usage.percent
})
except:
continue
# Get disk I/O statistics
disk_io = psutil.disk_io_counters()
io_info = {
'read_count': disk_io.read_count if disk_io else 0,
'write_count': disk_io.write_count if disk_io else 0,
'read_bytes': f"{disk_io.read_bytes / (1024**3):.2f} GB" if disk_io else "0 GB",
'write_bytes': f"{disk_io.write_bytes / (1024**3):.2f} GB" if disk_io else "0 GB"
}
return {
'partitions': storage_info,
'io_stats': io_info
}
except Exception as e:
return {'error': str(e)}
async def get_network_info():
"""Get network interface information."""
try:
interfaces = psutil.net_if_addrs()
io_counters = psutil.net_io_counters(pernic=True)
network_info = []
for interface, addrs in interfaces.items():
if interface not in ['lo']: # Skip loopback
interface_io = io_counters.get(interface, None)
network_info.append({
'interface': interface,
'ipv4': next((addr.address for addr in addrs if addr.family == socket.AF_INET), 'N/A'),
'ipv6': next((addr.address for addr in addrs if addr.family == socket.AF_INET6), 'N/A'),
'mac': next((addr.address for addr in addrs if addr.family == psutil.AF_LINK), 'N/A'),
'bytes_sent': f"{interface_io.bytes_sent / (1024**2):.2f} MB" if interface_io else "N/A",
'bytes_recv': f"{interface_io.bytes_recv / (1024**2):.2f} MB" if interface_io else "N/A"
})
return network_info
except Exception as e:
return {'error': str(e)}
async def get_process_info():
"""Get process and system load information."""
try:
processes = []
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent']):
try:
processes.append(proc.info)
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
# Sort by CPU usage and get top 5
top_processes = sorted(processes, key=lambda x: x['cpu_percent'] or 0, reverse=True)[:5]
return {
'total_processes': len(processes),
'top_cpu': top_processes
}
except Exception as e:
return {'error': str(e)}
async def get_docker_info():
"""Get Docker container information if available."""
try:
# Check if docker is available
result = subprocess.run(['docker', '--version'], capture_output=True, text=True)
if result.returncode != 0:
return {'available': False}
# Get running containers
result = subprocess.run(['docker', 'ps', '--format', '{{.Names}}|{{.Status}}|{{.Ports}}'],
capture_output=True, text=True)
containers = []
for line in result.stdout.strip().split('\n'):
if line:
parts = line.split('|')
if len(parts) >= 2:
containers.append({
'name': parts[0],
'status': parts[1],
'ports': parts[2] if len(parts) > 2 else 'N/A'
})
return {
'available': True,
'containers': containers,
'total_running': len(containers)
}
except Exception as e:
return {'available': False, 'error': str(e)}
async def get_sensor_info():
"""Get hardware sensor information."""
try:
temperatures = psutil.sensors_temperatures()
fans = psutil.sensors_fans()
battery = psutil.sensors_battery()
sensor_info = {
'temperatures': {},
'fans': {},
'battery': {}
}
# Temperature sensors
if temperatures:
for name, entries in temperatures.items():
sensor_info['temperatures'][name] = [
f"{entry.current}°C" for entry in entries[:2] # Show first 2 sensors per type
]
# Fan speeds
if fans:
for name, entries in fans.items():
sensor_info['fans'][name] = [
f"{entry.current} RPM" for entry in entries[:2]
]
# Battery information
if battery:
sensor_info['battery'] = {
'percent': battery.percent,
'power_plugged': battery.power_plugged,
'time_left': f"{battery.secsleft // 3600}h {(battery.secsleft % 3600) // 60}m" if battery.secsleft != psutil.POWER_TIME_UNLIMITED else "Unknown"
}
return sensor_info
except Exception as e:
return {'error': str(e)}
async def get_gpu_info():
"""Get GPU information using various methods."""
try:
gpu_info = {}
# Try nvidia-smi first
try:
result = subprocess.run(['nvidia-smi', '--query-gpu=name,memory.total,memory.used,memory.free,temperature.gpu,utilization.gpu',
'--format=csv,noheader,nounits'], capture_output=True, text=True)
if result.returncode == 0:
nvidia_gpus = []
for line in result.stdout.strip().split('\n'):
if line:
parts = [part.strip() for part in line.split(',')]
if len(parts) >= 6:
nvidia_gpus.append({
'name': parts[0],
'memory_total': f"{parts[1]} MB",
'memory_used': f"{parts[2]} MB",
'memory_free': f"{parts[3]} MB",
'temperature': f"{parts[4]}°C",
'utilization': f"{parts[5]}%"
})
gpu_info['nvidia'] = nvidia_gpus
except:
pass
# Try lspci for generic GPU detection
try:
result = subprocess.run(['lspci'], capture_output=True, text=True)
if result.returncode == 0:
gpu_lines = [line for line in result.stdout.split('\n') if 'VGA' in line or '3D' in line]
gpu_info['detected'] = gpu_lines[:3] # Show first 3 GPUs
except:
pass
return gpu_info
except Exception as e:
return {'error': str(e)}
async def format_system_info(sysinfo):
"""Format system information for display."""
output = "<strong>💻 System Information</strong><br><br>"
# System Overview
system = sysinfo.get('system', {})
output += "<strong>🖥️ System Overview</strong><br>"
output += f" • <strong>Hostname:</strong> {system.get('hostname', 'N/A')}<br>"
output += f" • <strong>OS:</strong> {system.get('os', 'N/A')} {system.get('os_release', '')}<br>"
output += f" • <strong>Architecture:</strong> {system.get('architecture', 'N/A')}<br>"
output += f" • <strong>Uptime:</strong> {system.get('uptime', 'N/A')}<br>"
output += f" • <strong>Boot Time:</strong> {system.get('boot_time', 'N/A')}<br>"
output += f" • <strong>Users:</strong> {system.get('users', 'N/A')}<br>"
output += "<br>"
# CPU Information
cpu = sysinfo.get('cpu', {})
if 'error' not in cpu:
output += "<strong>⚡ CPU Information</strong><br>"
output += f" • <strong>Cores:</strong> {cpu.get('physical_cores', 'N/A')} physical, {cpu.get('total_cores', 'N/A')} logical<br>"
output += f" • <strong>Frequency:</strong> {cpu.get('current_frequency', 'N/A')} (max: {cpu.get('max_frequency', 'N/A')})<br>"
output += f" • <strong>Usage:</strong> {cpu.get('usage_percent', 'N/A')}%<br>"
if cpu.get('load_avg') != "N/A":
output += f" • <strong>Load Average:</strong> {', '.join([f'{load:.2f}' for load in cpu.get('load_avg', [0,0,0])])}<br>"
output += "<br>"
# Memory Information
memory = sysinfo.get('memory', {})
if 'error' not in memory:
output += "<strong>🧠 Memory Information</strong><br>"
output += f" • <strong>Total:</strong> {memory.get('total', 'N/A')}<br>"
output += f" • <strong>Used:</strong> {memory.get('used', 'N/A')} ({memory.get('usage_percent', 'N/A')}%)<br>"
output += f" • <strong>Available:</strong> {memory.get('available', 'N/A')}<br>"
output += f" • <strong>Swap:</strong> {memory.get('swap_used', 'N/A')} / {memory.get('swap_total', 'N/A')} ({memory.get('swap_percent', 'N/A')}%)<br>"
output += "<br>"
# Storage Information
storage = sysinfo.get('storage', {})
if 'error' not in storage:
output += "<strong>💾 Storage Information</strong><br>"
partitions = storage.get('partitions', [])
for partition in partitions[:3]: # Show first 3 partitions
output += f" • <strong>{partition.get('device', 'N/A')}:</strong> {partition.get('used', 'N/A')} / {partition.get('total', 'N/A')} ({partition.get('percent', 'N/A')}%)<br>"
output += "<br>"
# GPU Information
gpu = sysinfo.get('gpu', {})
if gpu.get('nvidia'):
output += "<strong>🎮 GPU Information (NVIDIA)</strong><br>"
for gpu_info in gpu['nvidia']:
output += f" • <strong>{gpu_info.get('name', 'N/A')}:</strong> {gpu_info.get('utilization', 'N/A')} usage, {gpu_info.get('temperature', 'N/A')}<br>"
output += "<br>"
elif gpu.get('detected'):
output += "<strong>🎮 GPU Information</strong><br>"
for gpu_line in gpu['detected'][:2]:
output += f"{gpu_line}<br>"
output += "<br>"
# Network Information
network = sysinfo.get('network', [])
if network and 'error' not in network:
output += "<strong>🌐 Network Information</strong><br>"
for interface in network[:2]: # Show first 2 interfaces
output += f" • <strong>{interface.get('interface', 'N/A')}:</strong> {interface.get('ipv4', 'N/A')}<br>"
output += "<br>"
# Process Information
processes = sysinfo.get('processes', {})
if 'error' not in processes:
output += "<strong>🔄 Top Processes (by CPU)</strong><br>"
for proc in processes.get('top_cpu', [])[:3]:
output += f" • <strong>{proc.get('name', 'N/A')}:</strong> {proc.get('cpu_percent', 0):.1f}% CPU, {proc.get('memory_percent', 0):.1f}% RAM<br>"
output += f" • <strong>Total Processes:</strong> {processes.get('total_processes', 'N/A')}<br>"
output += "<br>"
# Docker Information
docker = sysinfo.get('docker', {})
if docker.get('available'):
output += "<strong>🐳 Docker Containers</strong><br>"
for container in docker.get('containers', [])[:3]:
output += f" • <strong>{container.get('name', 'N/A')}:</strong> {container.get('status', 'N/A')}<br>"
output += f" • <strong>Total Running:</strong> {docker.get('total_running', 'N/A')}<br>"
output += "<br>"
# Sensor Information
sensors = sysinfo.get('sensors', {})
if 'error' not in sensors:
if sensors.get('temperatures'):
output += "<strong>🌡️ Temperature Sensors</strong><br>"
for sensor, temps in list(sensors['temperatures'].items())[:2]:
output += f" • <strong>{sensor}:</strong> {', '.join(temps[:2])}<br>"
output += "<br>"
if sensors.get('battery'):
battery = sensors['battery']
output += "<strong>🔋 Battery Information</strong><br>"
output += f" • <strong>Charge:</strong> {battery.get('percent', 'N/A')}%<br>"
output += f" • <strong>Plugged In:</strong> {'Yes' if battery.get('power_plugged') else 'No'}<br>"
if battery.get('time_left'):
output += f" • <strong>Time Left:</strong> {battery.get('time_left', 'N/A')}<br>"
output += "<br>"
# Add timestamp
output += f"<em>Last updated: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</em>"
# Wrap in collapsible due to comprehensive output
output = f"<details><summary><strong>💻 System Information - {system.get('hostname', 'Unknown')}</strong></summary>{output}</details>"
return output

View File

@@ -12,3 +12,4 @@ croniter
schedule
yt-dlp
pyopenssl
psutil