Reverted to 5d746027e2 for funguy.py and loadplugin.py
				
					
				
			This commit is contained in:
		
							
								
								
									
										482
									
								
								plugins/ddg.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										482
									
								
								plugins/ddg.py
									
									
									
									
									
										Normal 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 <query></strong> - Instant answer search (default)
 | 
			
		||||
<strong>!ddg search <query></strong> - Web search with results
 | 
			
		||||
<strong>!ddg instant <query></strong> - Instant answer with detailed info
 | 
			
		||||
<strong>!ddg image <query></strong> - Image search
 | 
			
		||||
<strong>!ddg news <query></strong> - News search
 | 
			
		||||
<strong>!ddg video <query></strong> - Video search
 | 
			
		||||
<strong>!ddg bang <!bang query></strong> - Use DuckDuckGo bangs
 | 
			
		||||
<strong>!ddg define <word></strong> - Word definitions
 | 
			
		||||
<strong>!ddg calc <expression></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 <!bang query></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)}")
 | 
			
		||||
@@ -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
									
								
							
							
						
						
									
										442
									
								
								plugins/sysinfo.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user