From dba205685b1beb8580702c368755acf375b04838 Mon Sep 17 00:00:00 2001 From: Hash Borgir Date: Thu, 7 May 2026 02:27:27 -0500 Subject: [PATCH] Refactor help plugin, and included metadata to all plugins --- funguy.py | 5 + plugins/arxiv.py | 14 + plugins/bitcoin.py | 15 + plugins/config.py | 22 ++ plugins/cron.py | 17 + plugins/date.py | 14 + plugins/ddg.py | 28 ++ plugins/dns.py | 15 + plugins/dnsdumpster.py | 19 + plugins/exploitdb.py | 16 + plugins/fortune.py | 15 + plugins/geo.py | 21 +- plugins/hackernews.py | 13 + plugins/hashid.py | 15 + plugins/headers.py | 16 + plugins/help.py | 710 +++--------------------------------- plugins/imdb.py | 21 ++ plugins/infermatic-text.py | 22 ++ plugins/isup.py | 13 + plugins/karma.py | 39 +- plugins/lastfm.py | 28 ++ plugins/loadplugin.py | 13 + plugins/news.py | 13 +- plugins/plugins.py | 44 +-- plugins/proxy.py | 13 + plugins/shodan.py | 20 + plugins/sslscan.py | 16 + plugins/stable-diffusion.py | 23 ++ plugins/subdomains.py | 16 +- plugins/sysinfo.py | 13 + plugins/timezone.py | 18 + plugins/urbandictionary.py | 19 + plugins/weather.py | 402 ++++++++++++-------- plugins/welcome.py | 14 + plugins/whois.py | 14 + plugins/wikipedia.py | 16 + plugins/xkcd.py | 15 + plugins/youtube-search.py | 15 + 38 files changed, 935 insertions(+), 827 deletions(-) diff --git a/funguy.py b/funguy.py index e4e2155..7b44368 100755 --- a/funguy.py +++ b/funguy.py @@ -391,6 +391,11 @@ class FunguyBot: self.setup_plugins() logging.info("✓ Plugin setup complete") + # ----- NEW: Expose plugins dictionary on bot object ----- + self.bot.plugins = self.PLUGINS + logging.info("✓ Plugin dictionary exposed on bot.plugins") + # -------------------------------------------------------- + # Defining listener for message events @self.bot.listener.on_message_event async def wrapper_handle_commands(room, message): diff --git a/plugins/arxiv.py b/plugins/arxiv.py index 859247d..120e2ce 100644 --- a/plugins/arxiv.py +++ b/plugins/arxiv.py @@ -320,3 +320,17 @@ def setup(bot): __version__ = "1.0.0" __author__ = "Funguy Bot" __description__ = "arXiv academic paper search" +__help__ = """ +
+!arxiv – Search academic papers on arXiv + +

Categories: ai, ml, security, crypto, cv, nlp, math, physics, quantum, bio, software

+
+""" diff --git a/plugins/bitcoin.py b/plugins/bitcoin.py index 3af9a99..48418e3 100644 --- a/plugins/bitcoin.py +++ b/plugins/bitcoin.py @@ -107,3 +107,18 @@ async def handle_command(room, message, bot, prefix, config): "An unexpected error occurred while fetching Bitcoin price." ) logging.error(f"Unexpected error in Bitcoin plugin: {e}", exc_info=True) + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Current Bitcoin price" +__help__ = """ +
+!btc – Current Bitcoin price +

Fetches the latest BTC/USD price from bitcointicker.co.

+
+""" diff --git a/plugins/config.py b/plugins/config.py index 144f038..1ff1d6a 100644 --- a/plugins/config.py +++ b/plugins/config.py @@ -152,3 +152,25 @@ async def handle_command(room, message, bot, prefix, config): config.admin_user = "" config.prefix = "!" await bot.api.send_text_message(room.room_id, "Configuration reset") + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Admin configuration commands" +__help__ = """ +
+Admin Config (!set, !get, !saveconf, …) + +

Admin only.

+
+""" diff --git a/plugins/cron.py b/plugins/cron.py index 3f224f7..2838a25 100644 --- a/plugins/cron.py +++ b/plugins/cron.py @@ -62,3 +62,20 @@ async def run_cron_jobs(bot): if plugin_module: await plugin_module.handle_command(room, None, bot, prefix, config) +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Cron job scheduler" +__help__ = """ +
+!cron – Schedule commands via cron syntax + +

Admin only.

+
+""" diff --git a/plugins/date.py b/plugins/date.py index b473fe1..2ff1603 100644 --- a/plugins/date.py +++ b/plugins/date.py @@ -59,3 +59,17 @@ def get_ordinal_suffix(day_of_month): return "nd" else: return "th" + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Show current date and time" +__help__ = """ +
+!date – Current date and time +

Displays the day of the week, ordinal date, and AM/PM time.

+
+""" diff --git a/plugins/ddg.py b/plugins/ddg.py index 641eb31..068bb6d 100644 --- a/plugins/ddg.py +++ b/plugins/ddg.py @@ -480,3 +480,31 @@ async def ddg_weather(room, bot, location): ) except Exception as e: await bot.api.send_text_message(room.room_id, f"Error getting weather: {str(e)}") + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "DuckDuckGo search" +__help__ = """ +
+!ddg – DuckDuckGo search and instant answers + +

No API key required.

+
+""" diff --git a/plugins/dns.py b/plugins/dns.py index f158f4f..5bf3c79 100644 --- a/plugins/dns.py +++ b/plugins/dns.py @@ -207,3 +207,18 @@ async def handle_command(room, message, bot, prefix, config): f"An error occurred while performing DNS lookup: {str(e)}" ) logging.error(f"Error in DNS plugin for {domain}: {e}", exc_info=True) + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "DNS reconnaissance" +__help__ = """ +
+!dns – DNS reconnaissance +

!dns <domain> – Queries A, AAAA, MX, NS, TXT, CNAME, SOA, SRV, PTR records.

+
+""" diff --git a/plugins/dnsdumpster.py b/plugins/dnsdumpster.py index 01b7f56..97cc951 100644 --- a/plugins/dnsdumpster.py +++ b/plugins/dnsdumpster.py @@ -268,3 +268,22 @@ async def format_dnsdumpster_report(domain, data): output = f"
🔍 DNSDumpster Report: {domain} (Click to expand){output}
" return output + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "DNSDumpster domain reconnaissance" +__help__ = """ +
+!dnsdumpster – Comprehensive DNS mapping via DNSDumpster + +

Requires DNSDUMPSTER_KEY env var. Rate limit: 1 req/2 sec.

+
+""" diff --git a/plugins/exploitdb.py b/plugins/exploitdb.py index 6b47770..0b632f2 100644 --- a/plugins/exploitdb.py +++ b/plugins/exploitdb.py @@ -236,3 +236,19 @@ async def handle_command(room, message, bot, prefix, config): f"An error occurred while searching Exploit-DB: {str(e)}" ) logging.error(f"Error in exploitdb plugin: {e}", exc_info=True) + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Exploit-DB search" +__help__ = """ +
+!exploitdb – Search Exploit Database +

!exploitdb <search term> [max_results] – Search for exploits (title, EDB-ID, type, platform, author, link).
+Example: !exploitdb wordpress 5

+

Fetches from the official Exploit-DB CSV. Falls back to search links if unavailable.

+
+""" diff --git a/plugins/fortune.py b/plugins/fortune.py index 3f63499..ea0c360 100644 --- a/plugins/fortune.py +++ b/plugins/fortune.py @@ -24,3 +24,18 @@ async def handle_command(room, message, bot, prefix, config): fortune_output = "🃏 " + subprocess.run(['/usr/games/fortune'], capture_output=True).stdout.decode('UTF-8') await bot.api.send_markdown_message(room.room_id, fortune_output) logging.info("Sent fortune to the room") + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Random fortune message" +__help__ = """ +
+!fortune – Random fortune +

Runs the /usr/games/fortune utility and posts a random quote.

+
+""" diff --git a/plugins/geo.py b/plugins/geo.py index 171ca6c..210eb9b 100644 --- a/plugins/geo.py +++ b/plugins/geo.py @@ -266,4 +266,23 @@ async def handle_command(room, message, bot, prefix, config): room.room_id, f"An error occurred during geolocation lookup for {query}. Please try again later." ) - logging.error(f"Error in geo plugin for {query}: {e}", exc_info=True) \ No newline at end of file + logging.error(f"Error in geo plugin for {query}: {e}", exc_info=True) + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "IP geolocation lookup" +__help__ = """ +
+!geo – IP / domain geolocation + +

Shows country, region, city, coordinates, ISP, ASN. Uses ip-api.com / ipapi.co.

+
+""" diff --git a/plugins/hackernews.py b/plugins/hackernews.py index 338b0a6..5425274 100644 --- a/plugins/hackernews.py +++ b/plugins/hackernews.py @@ -388,3 +388,16 @@ def setup(bot): __version__ = "1.0.0" __author__ = "Funguy Bot" __description__ = "Hacker News integration" +__help__ = """ +
+!hn – Hacker News stories + +

Free, no API key needed.

+
+""" diff --git a/plugins/hashid.py b/plugins/hashid.py index 76029a5..bb25e82 100644 --- a/plugins/hashid.py +++ b/plugins/hashid.py @@ -384,3 +384,18 @@ async def handle_command(room, message, bot, prefix, config): f"Error identifying hash: {str(e)}" ) logging.error(f"Error in hashid command: {e}", exc_info=True) + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Hash type identifier" +__help__ = """ +
+!hashid – Identify hash type +

!hashid <hash> – Recognises 100+ hash formats (MD5, SHA, bcrypt, etc.).
+Shows confidence level, Hashcat mode, and John the Ripper format.

+
+""" diff --git a/plugins/headers.py b/plugins/headers.py index 819fca6..7536af0 100644 --- a/plugins/headers.py +++ b/plugins/headers.py @@ -385,3 +385,19 @@ async def format_header_analysis(results): output = f"
🔒 Security Headers Analysis: {results['url']}{output}
" return output + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "HTTP security header analysis" +__help__ = """ +
+!headers – HTTP security header scanner +

!headers <url> – Checks HSTS, CSP, X-Frame-Options, etc.
+Provides security score (0-100) and recommendations. Also shows SSL certificate info.

+
+""" diff --git a/plugins/help.py b/plugins/help.py index 7d871e7..3b10e54 100644 --- a/plugins/help.py +++ b/plugins/help.py @@ -1,673 +1,75 @@ """ -Plugin for providing a command to display the list of available commands and their descriptions. +Plugin for dynamically aggregating help from all loaded plugins. """ import logging import simplematrixbotlib as botlib -async def handle_command(room, message, bot, prefix, config): - """ - Function to handle the !help command. +async def handle_command(room, message, bot, prefix, config): + match = botlib.MessageMatch(room, message, bot, prefix) + if not (match.is_not_from_this_bot() and match.prefix() and match.command("help")): + return - Args: - room (Room): The Matrix room where the command was invoked. - message (RoomMessage): The message object containing the command. - bot (MatrixBot): The Matrix bot instance. - prefix (str): The command prefix. - config (dict): The bot's configuration. + args = match.args() + plugins = getattr(bot, "plugins", {}) - Returns: - None - """ - match = botlib.MessageMatch(room, message, bot, prefix) - if match.is_not_from_this_bot() and match.prefix() and match.command("help"): - logging.info("Fetching command help documentation") - commands_message = """ -
🍄 Funguy Bot Commands 🍄 -

-

📖 !help -

Displays comprehensive help documentation for all available commands with usage examples.

-
+ if not plugins: + await bot.api.send_text_message(room.room_id, "No plugins are currently loaded.") + return -
🔌 !plugins -

Lists all loaded plugins along with their descriptions in alphabetical order.

-
+ # If a specific plugin is requested + if args: + plugin_name = args[0].strip() + if plugin_name in plugins: + plugin = plugins[plugin_name] + help_html = getattr(plugin, "__help__", None) + if help_html: + await bot.api.send_markdown_message(room.room_id, help_html) + else: + # Fallback: docstring first line + doc = getattr(plugin, "__doc__", "No description available.") + first_line = doc.strip().split("\n")[0] if doc else "No description available." + await bot.api.send_text_message( + room.room_id, f"No detailed help for '{plugin_name}'. {first_line}" + ) + return + else: + await bot.api.send_text_message( + room.room_id, f"Plugin '{plugin_name}' not found. Available: {', '.join(sorted(plugins.keys()))}" + ) + return -
🃏 !fortune -

Returns a random fortune message. Executes the `/usr/games/fortune` utility and sends the output as a message to the chat room.

-
+ # Aggregate help from all plugins + help_parts = [] + for pname in sorted(plugins.keys()): + plugin = plugins[pname] + help_html = getattr(plugin, "__help__", None) + if help_html: + help_parts.append(help_html) + else: + # Minimal fallback using docstring + doc = getattr(plugin, "__doc__", "No description.") + first_line = doc.strip().split("\n")[0] if doc else "No description." + help_parts.append( + f"
!{pname}

{first_line}

" + ) -
!date -

Displays the current date and time. Fetches the current date and time using Python's `datetime` module and sends it in a formatted message with proper ordinal suffixes to the chat room.

-
- -
💻 !proxy -

Retrieves a tested/working random SOCKS5 proxy. Fetches a list of SOCKS5 proxies from public sources, tests their availability, and sends the first working proxy with latency information to the chat room. Caches working proxies for faster access.

-
- -
📶 !isup [domain/ip] -

Checks if the specified domain or IP address is reachable. Performs DNS resolution and checks HTTP/HTTPS service availability. Reports successful DNS resolution and service status.

-
- -
!karma [user] -

Retrieves the karma points for the specified user. Retrieves the karma points for the specified user from a SQLite database and sends them as a message to the chat room.

-
- -
!karma [user] up -

Increases the karma points for the specified user by 1. Increases the karma points for the specified user by 1 in the database and sends the updated points as a message to the chat room. Users cannot modify their own karma.

-
- -
!karma [user] down -

Decreases the karma points for the specified user by 1. Decreases the karma points for the specified user by 1 in the database and sends the updated points as a message to the chat room. Users cannot modify their own karma.

-
- -
🌧️ !weather [location] -

Fetches current weather information for any location using OpenWeatherMap API. Shows temperature (Celsius/Fahrenheit), conditions, humidity, wind speed, and weather emojis. Requires OPENWEATHER_API_KEY environment variable.

-
- -
📖 !ud [term] [index] -

Fetches definitions from Urban Dictionary. Use without arguments for random definition, or specify term and optional index number. Shows definition, example, author, votes, and permalink.

-
- -
🔍 !dns [domain] -

Performs comprehensive DNS reconnaissance on a domain. Queries multiple DNS record types including A, AAAA, MX, NS, TXT, CNAME, SOA, and SRV records. Validates domain format and provides formatted results.

-
- -
💰 !btc -

Fetches the current Bitcoin price in USD from bitcointicker.co API. Shows real-time BTC/USD price with proper formatting. Includes error handling for API timeouts and data parsing issues.

-
- -
🌐 !whois <domain/ip> -

Perform comprehensive WHOIS lookups for domains and IP addresses. Retrieves registrar information, registration dates, name servers, and contact details from WHOIS databases.

-

Usage:

-
    -
  • !whois <domain> - Query domain registration information
  • -
  • !whois <ip> - Query IP address allocation details
  • -
-

Examples:

-
    -
  • !whois example.com
  • -
  • !whois google.com
  • -
  • !whois 8.8.8.8
  • -
  • !whois 1.1.1.1
  • -
-

Output includes: Domain/IP information, registrar, WHOIS server, creation/expiration dates, name servers, and contact details.

-
- -
🔍 !subdomains -

Enumerate subdomains using SSL certificate transparency logs. Discovers associated subdomains by querying the CertSpotter API for SSL certificates issued for a domain.

-

Usage:

-
    -
  • !subdomains - Enumerate subdomains for any domain
  • -
  • !subdomains example.com - Example subdomain enumeration
  • -
-

Features:

-
    -
  • Discovers subdomains through SSL certificate transparency logs
  • -
  • Uses the free CertSpotter API for enumeration
  • -
  • No rate limiting or API key required
  • -
  • Identifies subdomains through certificate SAN (Subject Alternative Name) enumeration
  • -
-

Examples:

-
    -
  • !subdomains google.com
  • -
  • !subdomains github.com
  • -
  • !subdomains example.com
  • -
-

Essential for reconnaissance and subdomain enumeration in penetration testing

-
- -
📍 !geo [ip/domain] -

Perform IP geolocation lookups with detailed geographic information. Resolves domains to IP addresses and provides location data including country, region, city, coordinates, and ISP information.

-

Usage:

-
    -
  • !geo - Geolocate an IP address
  • -
  • !geo - Geolocate a domain (automatically resolves to IP)
  • -
-

Features:

-
    -
  • Uses ip-api.com as primary geolocation service with ipapi.co fallback
  • -
  • Automatic domain to IP resolution
  • -
  • Comprehensive geographic information
  • -
  • No API key required for basic usage
  • -
  • Supports both IPv4 and IPv6 addresses
  • -
-

Examples:

-
    -
  • !geo 8.8.8.8
  • -
  • !geo example.com
  • -
  • !geo google.com
  • -
-

Information provided:

-
    -
  • Country and country code
  • -
  • Region/State
  • -
  • City
  • -
  • Postal code
  • -
  • Latitude/Longitude coordinates
  • -
  • Timezone
  • -
  • ISP/Organization
  • -
  • Autonomous System Number (ASN)
  • -
-

Essential for network reconnaissance and IP investigation

-
- -
🔍 !shodan [command] [query] -

Shodan.io integration for security reconnaissance and threat intelligence.

-

Commands:

-
    -
  • !shodan ip <ip_address> - Detailed IP information (services, ports, banners)
  • -
  • !shodan search <query> - Search Shodan database with filters
  • -
  • !shodan host <domain> - Host information and subdomain enumeration
  • -
  • !shodan count <query> - Count results with geographic/organization breakdown
  • -
  • !shodan test - Test API connection and debug queries
  • -
-

Search Examples:

-
    -
  • !shodan search apache
  • -
  • !shodan search "port:22 country:US"
  • -
  • !shodan search "product:nginx"
  • -
  • !shodan search "net:192.168.1.0/24"
  • -
  • !shodan search "http.title:'admin'"
  • -
-

Common Filters: country, city, port, product, os, org, net, has_ssl, http.title

-

Requires SHODAN_KEY environment variable

-
- -
🌐 !dnsdumpster [domain] -

Comprehensive DNS reconnaissance and attack surface mapping using DNSDumpster.com API.

-

Commands:

-
    -
  • !dnsdumpster <domain> - Complete DNS reconnaissance for any domain
  • -
  • !dnsdumpster test - Test API connection and key validity
  • -
-

Features:

-
    -
  • A Records - All IPv4 addresses with geographic and ASN information
  • -
  • NS Records - Complete name server information with IP locations
  • -
  • MX Records - All mail servers with geographic data
  • -
  • CNAME Records - Full alias chain mappings
  • -
  • TXT Records - All text records including SPF, DKIM, verification
  • -
  • Additional Records - AAAA, SRV, SOA, PTR records when available
  • -
  • Web Services - HTTP/HTTPS service detection with banner information
  • -
-

Examples:

-
    -
  • !dnsdumpster google.com
  • -
  • !dnsdumpster github.com
  • -
  • !dnsdumpster example.com
  • -
-

Requires DNSDUMPSTER_KEY environment variable
-Rate Limit: 1 request per 2 seconds

-
- -
-💣 !exploitdb - Search Exploit Database -
-Description:
-Search Exploit-DB for security vulnerabilities and exploits. Returns detailed information about exploits including EDB-ID, type, platform, author, and direct links to exploit code.
-
-Usage:
-!exploitdb <search_term> [max_results]
-
-Parameters:
-• search_term (required) - Software name, CVE number, or vulnerability type
-• max_results (optional) - Number of results to return (1-10, default: 5)
-
-Examples:
-!exploitdb wordpress - Search for WordPress exploits
-!exploitdb apache 3 - Get 3 Apache exploits
-!exploitdb windows privilege escalation - Search for Windows privesc
-!exploitdb CVE-2021-44228 - Search by CVE number
-!exploitdb linux kernel 10 - Get 10 Linux kernel exploits
-!exploitdb sql injection - Search for SQL injection exploits
-
-Output Includes:
-• Exploit title and description
-• EDB-ID (Exploit Database ID)
-• Exploit type (webapps, local, remote, etc.)
-• Platform/OS (PHP, Linux, Windows, etc.)
-• Author name
-• Publication date
-• Direct link to full exploit code
-
-Notes:
-• Searches the official Exploit-DB CSV database
-• May take a few seconds on first use (downloads database)
-• Falls back to search links if database unavailable
-
-⚠️ Use responsibly and only on systems you have permission to test. -
- -
🛡️ !headers <url> -

Comprehensive HTTP security header analysis with security scoring and recommendations.

-

Features:

-
    -
  • Security scoring (0-100) with color-coded ratings
  • -
  • Critical security header validation and configuration checking
  • -
  • HTTP to HTTPS redirect chain analysis
  • -
  • SSL certificate information for HTTPS sites
  • -
  • Information disclosure header detection
  • -
  • Actionable security recommendations
  • -
-

Security Headers Analyzed:

-
    -
  • Strict-Transport-Security - HSTS enforcement
  • -
  • Content-Security-Policy - XSS protection
  • -
  • X-Frame-Options - Clickjacking protection
  • -
  • X-Content-Type-Options - MIME sniffing prevention
  • -
  • Referrer-Policy - Referrer control
  • -
  • Feature-Policy - Browser feature restrictions
  • -
  • Server information headers
  • -
-

Security Ratings:

-
    -
  • 🟢 Excellent (80-100) - Strong configuration
  • -
  • 🟡 Good (60-79) - Moderate, needs improvement
  • -
  • 🟠 Fair (40-59) - Basic, significant improvements needed
  • -
  • 🔴 Poor (0-39) - Weak configuration
  • -
-

Examples:

-
    -
  • !headers example.com
  • -
  • !headers https://github.com
  • -
  • !headers localhost:3000
  • -
  • !headers subdomain.target.com
  • -
-

Provides enterprise-grade security analysis for penetration testers and developers

-
- -
🔄 !hashid <hash> -

Advanced hash type identification with confidence scoring and tool recommendations.

-

Features:

-
    -
  • 100+ hash types including modern, legacy, and exotic algorithms
  • -
  • Color-coded confidence scoring (🟢 Very High to 🔴 Low)
  • -
  • Hashcat mode numbers and John the Ripper format names
  • -
  • Context-aware parsing for various hash formats
  • -
-

Supported Categories:

-
    -
  • Modern: yescrypt, scrypt, Argon2, bcrypt
  • -
  • Unix/Linux: SHA-512/256 Crypt, MD5 Crypt, apr1
  • -
  • Raw Hashes: MD5, SHA family, SHA-3, NTLM, LM
  • -
  • Databases: MySQL, PostgreSQL, Oracle, MSSQL
  • -
  • Web/CMS: WordPress, Drupal, phpBB3, Django
  • -
  • LDAP: SSHA, SMD5, LDAP crypt formats
  • -
  • Network: NetNTLMv1/v2, Kerberos
  • -
  • Exotic: Whirlpool, RIPEMD, GOST, BLAKE2
  • -
-

Tool Integration:

-
    -
  • Hashcat: Mode numbers for -m parameter
  • -
  • John: Format names for --format= parameter
  • -
  • Multi-tool compatibility
  • -
-

Examples:

-
    -
  • !hashid 5d41402abc4b2a76b9719d911017c592 (MD5)
  • -
  • !hashid aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d (SHA-1)
  • -
  • !hashid $6$rounds=5000$salt$hash... (SHA-512 Crypt)
  • -
  • !hashid $y$j9T$... (yescrypt - modern Linux)
  • -
  • !hashid 8846f7eaee8fb117ad06bdd830b7586c (NTLM)
  • -
  • !hashid *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19 (MySQL)
  • -
-

Confidence Legend:

-
    -
  • 🟢 Very High (90-100%) - Single definitive match
  • -
  • 🟡 High (80-89%) - Strong match with minor alternatives
  • -
  • 🟠 Medium (60-79%) - Multiple plausible matches
  • -
  • 🔴 Low (0-59%) - Uncertain, needs context
  • -
-

Essential for penetration testers, forensic analysts, and password cracking

-
- -
🔐 !sslscan <domain[:port]> -

Comprehensive SSL/TLS security scanning and analysis with vulnerability detection.

-

Features:

-
    -
  • TLS 1.0-1.3 protocol support testing with security scoring
  • -
  • Certificate chain validation, expiration, and signature analysis
  • -
  • 25+ cipher suite testing with strength classification
  • -
  • Vulnerability detection (POODLE, weak ciphers, protocol issues)
  • -
  • 0-100 security rating with color-coded assessment
  • -
  • PCI DSS and modern security standards compliance checking
  • -
-

Security Checks:

-
    -
  • Protocol Security: TLS 1.2/1.3 enforcement, insecure protocol detection
  • -
  • Certificate Health: Expiration monitoring, signature validation
  • -
  • Cipher Security: RC4, DES, 3DES, NULL cipher detection
  • -
  • Modern Standards: Forward Secrecy, strong encryption
  • -
-

Security Ratings:

-
    -
  • 🟢 Excellent (90-100) - Modern TLS with strong security
  • -
  • 🟡 Good (80-89) - Good security, minor improvements needed
  • -
  • 🟠 Fair (60-79) - Moderate security, significant improvements
  • -
  • 🔴 Poor (0-59) - Critical issues requiring immediate attention
  • -
-

Examples:

-
    -
  • !sslscan example.com
  • -
  • !sslscan github.com:443
  • -
  • !sslscan localhost:8443
  • -
  • !sslscan 192.168.1.1:443
  • -
-

Essential for security teams, system administrators, and developers ensuring TLS compliance
-Note: SSLv2/SSLv3 testing limited by Python security features

-
- -
🎵 Last.fm Integration -

Comprehensive Last.fm integration with 30+ commands for music analytics and social features.

-

Commands:

-
    -
  • !register - Register your Last.fm username
  • -
  • !np [user] - Show currently playing track
  • -
  • !recent [user] [limit] - Show recent tracks (default 10, max 50)
  • -
  • !toptracks [user] [period] - Show top tracks (overall/7day/1month/3month/6month/12month)
  • -
  • !topartists [user] [period] - Show top artists
  • -
  • !topalbums [user] [period] - Show top albums
  • -
  • !loved [user] - Show recently loved tracks
  • -
  • !profile [user] - Detailed user profile
  • -
  • !playcount [user] - Total scrobbles
  • -
  • !scrobbles [user] - Detailed scrobbling statistics
  • -
  • !compare - Compare musical tastes
  • -
  • !taste [user] - Top artists with taste-o-meter
  • -
  • !friends [user] - Show Last.fm friends
  • -
  • !recommend [user] - Artist recommendations
  • -
  • !similar - Find similar artists
  • -
  • !tag - Top artists for a tag/genre
  • -
  • !charts - Global top tracks chart
  • -
  • !tagcloud [user] - Top genre tags
  • -
  • !now - What are registered users playing?
  • -
  • !decades [user] - Favorite decades analysis
  • -
  • !genres [user] - Top genres/tags
  • -
  • !era - Popular tracks from a year
  • -
  • !weekly [user] - Weekly listening report
  • -
  • !monthly [user] - Monthly listening report
  • -
  • !yearly [user] [year] - Yearly listening report
  • -
  • !first [user] - Find first scrobble of an artist
  • -
  • !concerts [user] - Upcoming concerts for top artists
  • -
  • !radio - Generate playlist based on artist
  • -
  • !mashup - Musical connections between artists
  • -
  • !collage [user] [size] - Top album art URLs
  • -
  • !listening [user] - Currently listening with album art
  • -
  • !awards [user] - Milestone achievements
  • -
-

Features:

-
    -
  • Register your Matrix ID with your Last.fm username
  • -
  • Display currently playing tracks with artist and album information
  • -
  • Compare musical tastes between users
  • -
  • Discover similar artists and genres
  • -
  • Get personalized artist recommendations
  • -
  • View detailed listening statistics and reports
  • -
  • Find upcoming concerts for your favorite artists
  • -
  • Generate playlists based on your musical preferences
  • -
  • View milestone achievements and listening habits
  • -
  • Uses SQLite database to store user associations
  • -
  • Requires LASTFM_API_KEY environment variable
  • -
-

Examples:

-
    -
  • !register your_lastfm_username - Register your Last.fm username
  • -
  • !np - Show your currently playing track
  • -
  • !recent 20 - Show your 20 most recent tracks
  • -
  • !topartists 7day - Show your top artists from the last 7 days
  • -
  • !compare user1 user2 - Compare musical tastes between two users
  • -
  • !similar radiohead - Find artists similar to Radiohead
  • -
  • !tag electronic - Show top electronic artists
  • -
  • !era 1994 - Show popular tracks from 1994
  • -
  • !radio metallica - Generate a playlist based on Metallica
  • -
  • !mashup metallica megadeth - Find musical connections between Metallica and Megadeth
  • -
-

Requirements:

-
  • Last.fm account at last.fm
  • -
  • LASTFM_API_KEY in .env file
  • -
  • YOUTUBE_API_KEY in .env file (for YouTube integration)
  • - -
    - - - -
    📸 !sd [prompt] -

    Generates images using self-hosted Stable Diffusion. Supports options: --steps, --cfg, --h, --w, --neg, --sampler. Uses queuing system to handle multiple requests. See available options using just '!sd'.

    -
    - -
    📄 !text [prompt] -

    Generates text using the Infermatic AI API. Supports multiple models, configurable parameters, and model listing. Uses queuing system for sequential processing.

    -

    Usage:

    -
      -
    • !text <prompt> - Generate text using the default model
    • -
    • !text --list-models - List all available models from Infermatic AI
    • -
    • !text --use-model <model_name> <prompt> - Use a specific model instead of the default
    • -
    • !text --temperature <value> <prompt> - Set temperature (0.0-1.0, default: 0.9)
    • -
    • !text --max-tokens <value> <prompt> - Set maximum tokens to generate (default: 2048)
    • -
    -

    Configuration:

    -
      -
    • Requires INFERMATIC_API environment variable set to your API key
    • -
    • Requires INFERMATIC_MODEL environment variable for default model (default: Sao10K-L3.1-70B-Hanami-x1)
    • -
    -

    Model Management:

    -
      -
    • Use !text --list-models to see all available models
    • -
    • Models support different capabilities and context lengths
    • -
    • Costs and token limits vary by model
    • -
    -

    Examples:

    -
      -
    • !text write a python function to calculate fibonacci
    • -
    • !text --list-models
    • -
    • !text --use-model llama-v3-8b-instruct explain quantum computing
    • -
    • !text --temperature 0.7 --max-tokens 500 write a haiku about AI
    • -
    -
    - -
    📰 !xkcd -

    Fetches and displays a random XKCD comic. Downloads comic image and sends it directly to the chat room.

    -
    - -
    🎬 YouTube Features -

    Automatic preview when YouTube links are posted. Shows video info, description, and attempts to fetch lyrics. Also supports !yt [search terms] for direct YouTube searching.

    -
    - -
    📘 !wp -

    Fetches Wikipedia summaries and main images for search terms using MediaWiki APIs. No HTML scraping or BeautifulSoup required.
    -Usage:
    -!wp - Fetch Wikipedia summary for any search term
    -!wp help - Show usage instructions
    -
    -Examples:
    -!wp artificial intelligence
    -!wp machine learning
    -!wp python programming -

    -
    - -
    📘 !time -

    Fetches current time information for locations using the TimeAPI.io service.
    -Usage:
    -!time - Get time for a location
    -!time help - Show usage instructions
    -
    -Examples:
    -!time London - Get time in London
    -!time Tokyo - Get time in Tokyo
    -!time New York - Get time in New York
    -
    -Supported locations: Major cities worldwide including New York, London, Tokyo, Sydney, Paris, Berlin, etc. -

    -
    - -
    📚 !arxiv [query] -

    Search academic papers on arXiv.org. Categories include AI, ML, Security, Physics, Math, and more. No API key required.

    -

    Commands:

    -
      -
    • !arxiv - Search for papers (shows abstracts)
    • -
    • !arxiv list - List papers without abstracts
    • -
    • !arxiv category - Browse recent papers by category
    • -
    • !ar0; 10px; color: #3b3a30; font-family: monospace; font-size: 12px; background: #f8f8f8; border: 1px solid #c8c8c8; border-radius: 3px; padding: 0 5px; }" - } - }, - "code": { - "background": "white", - "color": "#3b3a30", - "font-family": "monospace", - "font-size": "12px", - "border": "1px solid #c8c8c8", - "border-radius": "3px", - "padding": "0 5px" - } -} - -
      - -
      -📚 !arxiv [query] -

      Search academic papers on arXiv.org. Categories include AI, ML, Security, Physics, Math, and more. No API key required.

      -

      Commands:

      -
        -
      • !arxiv - Search for papers (shows abstracts)
      • -
      • !arxiv list - List papers without abstracts
      • -
      • !arxiv category - Browse recent papers by category
      • -
      • !arxiv recent - Most recent papers in category
      • -
      • !arxiv random - Get a random paper
      • -
      • !arxiv - Get paper by arXiv ID (e.g., 2101.00101)
      • -
      -

      Categories: ai, ml, security, crypto, cv, nlp, math, physics, quantum, bio, software

      -
    - -
    📰 !news [category/query] -

    Fetch latest headlines from various news categories using GNews API. Requires GNEWS_API_KEY environment variable.

    -

    Commands:

    -
      -
    • !news - Get top headlines (default)
    • -
    • !news top - Top headlines
    • -
    • !news world - World news
    • -
    • !news tech - Technology news
    • -
    • !news business - Business news
    • -
    • !news science - Science news
    • -
    • !news health - Health news
    • -
    • !news crypto - Cryptocurrency news
    • -
    • !news search - Search for specific news
    • -
    -
    - -
    🔥 !hn [command] -

    Fetch top stories from Hacker News using Firebase API. No API key required.

    -

    Commands:

    -
      -
    • !hn - Show top 5 stories (default)
    • -
    • !hn top - Top stories
    • -
    • !hn new - Newest stories
    • -
    • !hn best - Best stories
    • -
    • !hn ask - Ask HN threads
    • -
    • !hn show - Show HN posts
    • -
    • !hn job - Job postings
    • -
    • !hn story - Get details of a specific story
    • -
    • !hn comments - Show comments for a story
    • -
    • !hn search - Search stories (via Algolia)
    • -
    -
    - -
    !karma [user] -

    Track karma points for users with leaderboards and statistics. Supports display names and Matrix IDs.

    -

    Commands:

    -
      -
    • !karma - Show karma for a user
    • -
    • !karma++ - Give +1 karma to a user
    • -
    • !karma-- - Give -1 karma to a user
    • -
    • !karma top [n] - Show top karma entries
    • -
    • !karma bottom [n] - Show bottom karma entries
    • -
    • !karma rank - Show rank of user
    • -
    • !karma stats - Show overall statistics
    • -
    • !karma history - Show recent karma history
    • -
    • !++ - Shortcut for !karma++
    • -
    • !-- - Shortcut for !karma--
    • -
    • ++ - Inline karma (message contains ++)
    • -
    • -- - Inline karma (message contains --)
    • -
    -

    Features:

    -
      -
    • Supports display names and Matrix IDs
    • -
    • Room-specific karma tracking
    • -
    • Rate limiting to prevent spam
    • -
    • Karma history tracking
    • -
    • Leaderboards and statistics
    • -
    -
    - -
    🔥 !hn [command] -

    Fetch top stories from Hacker News using Firebase API. No API key required.

    -

    Commands:

    -
      -
    • !hn - Show top 5 stories (default)
    • -
    • !hn top - Top stories
    • -
    • !hn new - Newest stories
    • -
    • !hn best - Best stories
    • -
    • !hn ask - Ask HN threads
    • -
    • !hn show - Show HN posts
    • -
    • !hn job - Job postings
    • -
    • !hn story - Get details of a specific story
    • -
    • !hn comments - Show comments for a story
    • -
    • !hn search - Search stories (via Algolia)
    • -
    -
    - -
    ⏱️ !cron [add|remove] [room_id] [cron_entry] [command] -

    Schedule automated commands using cron syntax. Add or remove cron jobs for specific rooms and commands.

    -
    - -
    🔧 Admin Commands -

    -!set [option] [value] - Set configuration options (admin_user, prefix)
    -!get [option] - Get configuration values
    -!saveconf - Save current configuration
    -!loadconf - Load saved configuration
    -!show - Display current configuration
    -!reset - Reset configuration to defaults
    -!load [plugin] - Load a plugin
    -!unload [plugin] - Unload a plugin
    -!reload - Reload all plugins
    -!disable [plugin] [room_id] - Disable a plugin for specific room
    -!enable [plugin] [room_id] - Enable a plugin for specific room
    -!rehash - Reload configuration
    -Note: Admin commands require admin_user privileges -

    -
    -

    -
    - - - - - -
    🌟 Funguy Bot Credits + # Append bot credits + credits = """ +
    🌟 Funguy Bot Credits

    🧙‍♂️ Creator & Developer: HB is the author of 🍄Funguy Bot🍄. (@hashborgir:mozilla.org)
    🚀 Development Context: Created during recovery from two-level cervical spinal surgery (CDA Cervical Discectomy and Disc Arthroplasty)

    -Join our Matrix Room: Self-hosting | Security | Sysadmin | Homelab | Programming +Join our Matrix Room: Self‑hosting | Security | Sysadmin | Homelab | Programming

    """ + help_parts.append(credits) - await bot.api.send_markdown_message(room.room_id, commands_message) - logging.info("Sent help documentation to the room") + master = "
    🍄 Funguy Bot Commands (click to expand)
    " + master += "\n".join(help_parts) + master += "
    " + + await bot.api.send_markdown_message(room.room_id, master) + logging.info("Sent dynamic help from %d plugins", len(plugins)) diff --git a/plugins/imdb.py b/plugins/imdb.py index c8dd2c8..cd9649e 100644 --- a/plugins/imdb.py +++ b/plugins/imdb.py @@ -388,3 +388,24 @@ async def handle_episode(room, bot, args): await bot.api.send_image_message(room_id=room.room_id, image_filepath=poster_path) finally: os.unlink(poster_path) + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "IMDb lookup via OMDb API" +__help__ = """ +
    +!imdb – Movie/series details via OMDb +
      +
    • !imdb <title> – Full details (poster sent separately)
    • +
    • !imdb id <tt1234567> – Lookup by IMDb ID
    • +
    • !imdb search <query> – Search titles
    • +
    • !imdb episode <series> -s N -e N – Episode info
    • +
    +

    Optional flags: -y year, -t movie|series|episode, --short-plot
    +Requires OMDB_API_KEY env var.

    +
    +""" diff --git a/plugins/infermatic-text.py b/plugins/infermatic-text.py index 2dc2218..348f3ab 100644 --- a/plugins/infermatic-text.py +++ b/plugins/infermatic-text.py @@ -231,3 +231,25 @@ async def generate_text(room, bot, prompt, model, temperature, max_tokens): if not command_queue.empty(): next_command = await command_queue.get() await handle_command(*next_command) + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "AI text generation via Infermatic API" +__help__ = """ +
    +!text – AI text generation (Infermatic) +
      +
    • !text <prompt> – Generate text using default model
    • +
    • !text --list-models – List available models
    • +
    • !text --use-model <model> <prompt> – Specific model
    • +
    • --temperature <0.0-1.0> – Set creativity (default 0.9)
    • +
    • --max-tokens <number> – Max output length (default 2048)
    • +
    +

    Requires INFERMATIC_API env var.

    +
    +""" diff --git a/plugins/isup.py b/plugins/isup.py index 236ac04..18df6a3 100644 --- a/plugins/isup.py +++ b/plugins/isup.py @@ -100,3 +100,16 @@ async def handle_command(room, message, bot, prefix, config): await bot.api.send_markdown_message(room.room_id, f"😕 **{target}** HTTP/HTTPS services are down") logging.info(f"{target} HTTP/HTTPS services are down") +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Check if a site is up" +__help__ = """ +
    +!isup – Is it up? +

    !isup <domain or IP> – Performs DNS resolution and checks HTTP/HTTPS availability.

    +
    +""" diff --git a/plugins/karma.py b/plugins/karma.py index 833a823..019a904 100644 --- a/plugins/karma.py +++ b/plugins/karma.py @@ -54,6 +54,14 @@ display_name_cache = {} cache_timestamp = {} +# --------------------------------------------------------------------------- +# Helper: pluralize "point" vs "points" +# --------------------------------------------------------------------------- +def pluralize_points(amount): + """Return 'point' if amount is 1 or -1, else 'points'.""" + return "point" if abs(amount) == 1 else "points" + + # --------------------------------------------------------------------------- # Database Setup with Room Support # --------------------------------------------------------------------------- @@ -625,7 +633,8 @@ async def handle_karma_command(room, message, bot, config): response = f"🏆 **Top {len(leaderboard)} Karma Leaders**\n\n" for i, entry in enumerate(leaderboard, 1): medal = "🥇" if i == 1 else "🥈" if i == 2 else "🥉" if i == 3 else "📌" - response += f"{medal} **{i}.** {entry['display_name']}: {entry['points']} points\n" + pts = entry['points'] + response += f"{medal} **{i}.** {entry['display_name']}: {pts} {pluralize_points(pts)}\n" await bot.api.send_markdown_message(room.room_id, response) else: await bot.api.send_markdown_message(room.room_id, "No karma entries found in this room.") @@ -641,7 +650,8 @@ async def handle_karma_command(room, message, bot, config): if leaderboard: response = f"📉 **Bottom {len(leaderboard)} Karma (Needs Love)**\n\n" for i, entry in enumerate(leaderboard, 1): - response += f"⚠️ **{i}.** {entry['display_name']}: {entry['points']} points\n" + pts = entry['points'] + response += f"⚠️ **{i}.** {entry['display_name']}: {pts} {pluralize_points(pts)}\n" await bot.api.send_markdown_message(room.room_id, response) else: await bot.api.send_markdown_message(room.room_id, "No karma entries found in this room.") @@ -661,7 +671,7 @@ async def handle_karma_command(room, message, bot, config): points = get_karma(room_id, user_id) percentile = round((1 - rank/total) * 100, 1) if total > 0 else 0 display_name_resolved = get_display_name_from_user_id(bot, room_id, user_id) - response = f"📊 **{display_name_resolved}** is ranked #{rank} out of {total} (top {percentile}%)\n💗 Karma: {points} points" + response = f"📊 **{display_name_resolved}** is ranked #{rank} out of {total} (top {percentile}%)\n💗 Karma: {points} {pluralize_points(points)}" await bot.api.send_markdown_message(room.room_id, response) else: display_name_resolved = get_display_name_from_user_id(bot, room_id, user_id) @@ -801,3 +811,26 @@ def setup(bot): logging.info("Supports display names (e.g., 'Nexilva' or '🍄 HB🍄') and Matrix IDs") logging.info("Commands: !karma, !karma++, !karma--, !++, !--, and inline ++/--") logging.info("=" * 50) + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Room karma tracking system" +__help__ = """ +
    +!karma – Karma system +
      +
    • !karma <user> – Show points
    • +
    • !karma++ <user> / !-- <user> – Modify karma
    • +
    • !karma top [n] / !karma bottom [n] – Leaderboard
    • +
    • !karma rank <user> – Position
    • +
    • !karma stats – Room statistics
    • +
    • !karma history <user> – Recent votes
    • +
    +

    Shortcuts: !++ user, !-- user, and inline user++ / user--.

    +
    +""" diff --git a/plugins/lastfm.py b/plugins/lastfm.py index 20f2262..1e61ca6 100644 --- a/plugins/lastfm.py +++ b/plugins/lastfm.py @@ -2019,3 +2019,31 @@ async def handle_command(room, message, bot, prefix, config): await bot.api.send_text_message( room.room_id, f"❌ Error processing !{command}: {str(e)}" ) + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Last.fm integration" +__help__ = """ +
    +!lastfm – Last.fm music stats (30+ commands) +
      +
    • !register <username> – Connect account
    • +
    • !np [user] – Now playing
    • +
    • !recent [user] [limit] – Recent tracks
    • +
    • !toptracks, !topartists, !topalbums
    • +
    • !loved, !profile, !playcount, !scrobbles
    • +
    • !compare <user1> <user2> – Taste comparison
    • +
    • !recommend, !similar <artist>, !tag <genre>
    • +
    • !charts, !now, !decades, !genres, !tagcloud
    • +
    • !era <year>, !weekly, !monthly, !yearly
    • +
    • !first <artist>, !concerts, !radio <artist>
    • +
    • !collage [user] [size], !listening, !awards
    • +
    +

    For full details: !lastfm
    Requires LASTFM_API_KEY env var.

    +
    +""" diff --git a/plugins/loadplugin.py b/plugins/loadplugin.py index fdaf95f..acaf1ed 100644 --- a/plugins/loadplugin.py +++ b/plugins/loadplugin.py @@ -113,3 +113,16 @@ async def handle_command(room, message, bot, prefix, config): # 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.") +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Load/unload plugins at runtime" +__help__ = """ +
    +Admin: !load / !unload +

    !load <plugin> / !unload <plugin> – Dynamically load or unload a plugin module. Admin only.

    +
    +""" diff --git a/plugins/news.py b/plugins/news.py index a5cc614..36c7784 100644 --- a/plugins/news.py +++ b/plugins/news.py @@ -231,4 +231,15 @@ async def handle_command(room, message, bot, prefix, config): __version__ = "1.0.0" __author__ = "Funguy Bot" -__description__ = "News aggregator using GNews API" +__description__ = "News headlines via GNews API" +__help__ = """ +
    +!news – Latest news headlines +
      +
    • !news [top|world|tech|business|science|health|sports|crypto]
    • +
    • !news search <query>
    • +
    • You can append a number: !news tech 8
    • +
    +

    Requires GNEWS_API_KEY env var.

    +
    +""" diff --git a/plugins/plugins.py b/plugins/plugins.py index a407b8f..1b2bd17 100644 --- a/plugins/plugins.py +++ b/plugins/plugins.py @@ -10,49 +10,49 @@ import logging import simplematrixbotlib as botlib async def handle_command(room, message, bot, prefix, config): - """ - Function to handle the !plugins command. - - Args: - room (Room): The Matrix room where the command was invoked. - message (RoomMessage): The message object containing the command. - - Returns: - None - """ match = botlib.MessageMatch(room, message, bot, prefix) if match.is_not_from_this_bot() and match.prefix() and match.command("plugins"): logging.info("Received !plugins command") plugin_descriptions = get_plugin_descriptions() - # Prepend custom string before the output plugin_descriptions.insert(0, "
    🔌Plugins List🔌
    ⤵︎Click Here to Expand⤵︎
    ") - plugins_message = "
    ".join(plugin_descriptions) plugins_message += "
    " + await bot.api.send_markdown_message(room.room_id, plugins_message) logging.info("Sent plugin list to the room") def get_plugin_descriptions(): - """ - Function to get descriptions of all loaded plugin modules. - - Returns: - list: A list of plugin descriptions sorted alphabetically. - """ plugin_descriptions = [] for module_name, module in sys.modules.items(): if module_name.startswith("plugins.") and hasattr(module, "__file__"): plugin_path = module.__file__ plugin_name = os.path.basename(plugin_path).split(".")[0] - try: + + if hasattr(module, "__description__"): + description = module.__description__ + elif module.__doc__: description = module.__doc__.strip().split("\n")[0] - except AttributeError: + else: description = "No description available" + plugin_descriptions.append(f"[{plugin_name}.py]: {description}") - # Sort the plugin descriptions alphabetically by plugin name plugin_descriptions.sort() - return plugin_descriptions + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "List all loaded plugins" +__help__ = """ +
    +!plugins – List loaded plugins +

    Displays all currently loaded plugins and their descriptions.

    +
    +""" diff --git a/plugins/proxy.py b/plugins/proxy.py index e29b7ff..d611813 100644 --- a/plugins/proxy.py +++ b/plugins/proxy.py @@ -211,3 +211,16 @@ async def handle_command(room, message, bot, prefix, config): await bot.api.send_markdown_message(room.room_id, "❌ Error handling !proxy command") +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Working SOCKS5 proxy finder" +__help__ = """ +
    +!proxy – Random working SOCKS5 proxy +

    Fetches, tests, and returns a random working SOCKS5 proxy with latency. Caches good proxies in SQLite.

    +
    +""" diff --git a/plugins/shodan.py b/plugins/shodan.py index 569eb45..2a64d82 100644 --- a/plugins/shodan.py +++ b/plugins/shodan.py @@ -328,3 +328,23 @@ async def handle_shodan_error(room, bot, status_code): message = error_messages.get(status_code, f"Shodan API error: {status_code}") await bot.api.send_text_message(room.room_id, message) logging.error(f"Shodan API error: {status_code}") + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Shodan.io reconnaissance" +__help__ = """ +
    +!shodan – Shodan search +
      +
    • !shodan ip <ip> – IP info with open ports
    • +
    • !shodan search <query> – Search internet devices
    • +
    • !shodan host <domain> – Host & subdomain enumeration
    • +
    • !shodan count <query> – Result counts
    • +
    +

    Requires SHODAN_KEY env var.

    +
    +""" diff --git a/plugins/sslscan.py b/plugins/sslscan.py index 6bf1076..c0b1bc1 100644 --- a/plugins/sslscan.py +++ b/plugins/sslscan.py @@ -592,3 +592,19 @@ def format_cert_date(date_str): except: pass return date_str + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "SSL/TLS security scanner" +__help__ = """ +
    +!sslscan – SSL/TLS analysis +

    !sslscan <domain[:port]> – Tests protocols, cipher suites, certificate validity, vulnerabilities.
    +Provides a security score (0-100) and actionable recommendations.

    +
    +""" diff --git a/plugins/stable-diffusion.py b/plugins/stable-diffusion.py index bbbde04..a334f48 100644 --- a/plugins/stable-diffusion.py +++ b/plugins/stable-diffusion.py @@ -185,3 +185,26 @@ def print_help():
  • <lora:al3xxl:1> alexpainting, alexhuman, alexentity, alexthirdeye, alexforeheads, alexgalactic, spiraling, alexmirror, alexangel, alexkissing, alexthirdeye2, alexthirdeye3, alexhofmann, wearing, glasses, alexgalactic3, alexthirdeye4, alexhuman3, alexgalactic2, alexhuman2, alexbeing2, alexfractal2, alexfractal3
  • """ + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Stable Diffusion image generation" +__help__ = """ +
    +!sd – Generate images via Stable Diffusion +

    !sd [options] <prompt>

    +
      +
    • --steps N – Sampling steps (default 4)
    • +
    • --cfg scale – CFG scale (default 2)
    • +
    • --h H --w W – Image dimensions (default 512)
    • +
    • --neg <negative prompt>
    • +
    • --sampler SAMPLER – Sampler name (default DPM++ SDE)
    • +
    +

    Requires a locally running Stable Diffusion API.

    +
    +""" diff --git a/plugins/subdomains.py b/plugins/subdomains.py index 36c625e..51861b1 100644 --- a/plugins/subdomains.py +++ b/plugins/subdomains.py @@ -124,4 +124,18 @@ async def handle_command(room, message, bot, prefix, config): room.room_id, f"An error occurred during subdomain enumeration for {domain}. Please try again later." ) - logging.error(f"Error in subdomains plugin for {domain}: {e}", exc_info=True) \ No newline at end of file + logging.error(f"Error in subdomains plugin for {domain}: {e}", exc_info=True) + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Subdomain enumeration via CertSpotter" +__help__ = """ +
    +!subdomains – Enumerate subdomains +

    !subdomains <domain> – Finds subdomains using SSL certificate transparency logs (CertSpotter API).

    +
    +""" diff --git a/plugins/sysinfo.py b/plugins/sysinfo.py index 7cb331b..7e285f2 100644 --- a/plugins/sysinfo.py +++ b/plugins/sysinfo.py @@ -440,3 +440,16 @@ async def format_system_info(sysinfo): return output +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "System information and monitoring" +__help__ = """ +
    +!sysinfo – System information +

    Displays CPU, RAM, storage, network, Docker, GPU, sensors, and top processes.

    +
    +""" diff --git a/plugins/timezone.py b/plugins/timezone.py index 2435a56..41ca687 100644 --- a/plugins/timezone.py +++ b/plugins/timezone.py @@ -190,3 +190,21 @@ async def handle_command(room, message, bot, prefix, config): return await bot.api.send_markdown_message(room.room_id, format_response(data, display)) logging.info(f"Time sent for {query}") + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "World clock (no hardcoded cities)" +__help__ = """ +
    +!time – Current time for any city +
      +
    • !time <city> – Geocode any city (free Open-Meteo API)
    • +
    • !time <IANA zone> – e.g., Europe/London
    • +
    +

    Also shows current temperature if available.

    +
    +""" diff --git a/plugins/urbandictionary.py b/plugins/urbandictionary.py index 08b364d..526b6dd 100644 --- a/plugins/urbandictionary.py +++ b/plugins/urbandictionary.py @@ -191,3 +191,22 @@ async def handle_command(room, message, bot, prefix, config): "An error occurred while processing the Urban Dictionary request." ) logging.error(f"Unexpected error in Urban Dictionary plugin: {e}", exc_info=True) + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Urban Dictionary definitions" +__help__ = """ +
    +!ud – Urban Dictionary +
      +
    • !ud – Random definition
    • +
    • !ud <term> – Top definition
    • +
    • !ud <term> <index> – Nth definition
    • +
    +
    +""" diff --git a/plugins/weather.py b/plugins/weather.py index 1ab3900..950bbd6 100644 --- a/plugins/weather.py +++ b/plugins/weather.py @@ -1,162 +1,274 @@ """ -This plugin provides a command to get weather information for a location. +Weather plugin – primary: OpenWeatherMap, fallback: Open‑Meteo. + +Uses OpenWeatherMap when a valid API key is present and the request succeeds. +Falls back to Open‑Meteo (no key required) otherwise. + +Commands: + !weather e.g. !weather London or !weather "New York,US" """ import logging -import requests import os +import aiohttp import simplematrixbotlib as botlib from dotenv import load_dotenv +from urllib.parse import quote -# Load environment variables from .env file in the parent directory -# Get the directory where this plugin file is located +# --------------------------------------------------------------------------- +# Load .env (for OPENWEATHER_API_KEY) +# --------------------------------------------------------------------------- plugin_dir = os.path.dirname(os.path.abspath(__file__)) -# Get the parent directory (main bot directory) parent_dir = os.path.dirname(plugin_dir) -# Load .env from parent directory -dotenv_path = os.path.join(parent_dir, '.env') +dotenv_path = os.path.join(parent_dir, ".env") load_dotenv(dotenv_path) -# OpenWeatherMap API configuration -# Get your free API key from: https://openweathermap.org/api -WEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY", "") -WEATHER_API_URL = "https://api.openweathermap.org/data/2.5/weather" +OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY", "") -async def handle_command(room, message, bot, prefix, config): - """ - Function to handle the !weather command. +# --------------------------------------------------------------------------- +# WMO codes → description + emoji (for Open‑Meteo) +# --------------------------------------------------------------------------- +WMO_CODES = { + 0: ("Clear sky", "☀️"), + 1: ("Mainly clear", "🌤️"), + 2: ("Partly cloudy", "⛅"), + 3: ("Overcast", "☁️"), + 45: ("Fog", "🌫️"), + 48: ("Depositing rime fog", "🌫️"), + 51: ("Light drizzle", "🌦️"), + 53: ("Moderate drizzle", "🌦️"), + 55: ("Dense drizzle", "🌧️"), + 56: ("Light freezing drizzle", "🌧️"), + 57: ("Dense freezing drizzle", "🌧️"), + 61: ("Slight rain", "🌧️"), + 63: ("Moderate rain", "🌧️"), + 65: ("Heavy rain", "🌧️"), + 66: ("Light freezing rain", "🌧️"), + 67: ("Heavy freezing rain", "🌧️"), + 71: ("Slight snow fall", "❄️"), + 73: ("Moderate snow fall", "❄️"), + 75: ("Heavy snow fall", "❄️"), + 77: ("Snow grains", "❄️"), + 80: ("Slight rain showers", "🌦️"), + 81: ("Moderate rain showers", "🌧️"), + 82: ("Violent rain showers", "🌧️"), + 85: ("Slight snow showers", "🌨️"), + 86: ("Heavy snow showers", "🌨️"), + 95: ("Thunderstorm", "⛈️"), + 96: ("Thunderstorm with slight hail", "⛈️"), + 99: ("Thunderstorm with heavy hail", "⛈️"), +} - 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. +# --------------------------------------------------------------------------- +# Primary: OpenWeatherMap +# --------------------------------------------------------------------------- +async def openweathermap_get(session: aiohttp.ClientSession, location: str) -> dict | None: + """Fetch current weather from OpenWeatherMap. Returns None on failure.""" + if not OPENWEATHER_API_KEY: + logging.info("OpenWeatherMap key missing, skipping primary") + return None - Returns: - None - """ - match = botlib.MessageMatch(room, message, bot, prefix) - if match.is_not_from_this_bot() and match.prefix() and match.command("weather"): - logging.info("Received !weather command") - - # Check if API key is configured - if not WEATHER_API_KEY: - await bot.api.send_text_message( - room.room_id, - "Weather API key not configured. Please set OPENWEATHER_API_KEY environment variable." - ) - return - - args = match.args() - - # Check if location was provided - if len(args) < 1: - await bot.api.send_text_message( - room.room_id, - "Usage: !weather \nExample: !weather London or !weather New York,US" - ) - logging.info("Sent usage message for !weather") - return - - location = ' '.join(args) - - try: - # Make API request to OpenWeatherMap - params = { - 'q': location, - 'appid': WEATHER_API_KEY, - 'units': 'metric' # Use metric units (Celsius) - } - - response = requests.get(WEATHER_API_URL, params=params, timeout=10) - response.raise_for_status() - - weather_data = response.json() - - # Extract relevant weather information - city_name = weather_data['name'] - country = weather_data['sys']['country'] - temp = weather_data['main']['temp'] - feels_like = weather_data['main']['feels_like'] - humidity = weather_data['main']['humidity'] - description = weather_data['weather'][0]['description'].capitalize() - wind_speed = weather_data['wind'].get('speed', 0) - - # Convert temperature to Fahrenheit for display - temp_f = (temp * 9/5) + 32 - feels_like_f = (feels_like * 9/5) + 32 - - # Get weather emoji based on condition - weather_emoji = get_weather_emoji(weather_data['weather'][0]['main']) - - # Format the weather message - weather_message = f""" -[{weather_emoji} Weather for {city_name}, {country}]: Condition: {description} | Temperature: {temp:.1f}°C ({temp_f:.1f}°F) | Feels like: {feels_like:.1f}°C ({feels_like_f:.1f}°F) | Humidity: {humidity}% | Wind Speed: {wind_speed} m/s -""".strip() - - await bot.api.send_markdown_message(room.room_id, weather_message) - logging.info(f"Sent weather information for {city_name}") - - except requests.exceptions.HTTPError as e: - if e.response.status_code == 404: - await bot.api.send_text_message( - room.room_id, - f"Location '{location}' not found. Please check the spelling and try again." - ) - elif e.response.status_code == 401: - await bot.api.send_text_message( - room.room_id, - "Weather API authentication failed. Please check the API key configuration." - ) - else: - await bot.api.send_text_message( - room.room_id, - f"Error fetching weather data: HTTP {e.response.status_code}" - ) - logging.error(f"HTTP error fetching weather for '{location}': {e}") - - except requests.exceptions.RequestException as e: - await bot.api.send_text_message( - room.room_id, - f"Error connecting to weather service: {e}" - ) - logging.error(f"Request error fetching weather for '{location}': {e}") - - except (KeyError, ValueError) as e: - await bot.api.send_text_message( - room.room_id, - "Error parsing weather data. Please try again later." - ) - logging.error(f"Error parsing weather data for '{location}': {e}") - - -def get_weather_emoji(condition): - """ - Get an emoji based on weather condition. - - Args: - condition (str): Weather condition from API. - - Returns: - str: Weather emoji. - """ - weather_emojis = { - 'Clear': '☀️', - 'Clouds': '☁️', - 'Rain': '🌧️', - 'Drizzle': '🌦️', - 'Thunderstorm': '⛈️', - 'Snow': '❄️', - 'Mist': '🌫️', - 'Fog': '🌫️', - 'Haze': '🌫️', - 'Smoke': '🌫️', - 'Dust': '🌫️', - 'Sand': '🌫️', - 'Ash': '🌫️', - 'Squall': '💨', - 'Tornado': '🌪️' + url = "https://api.openweathermap.org/data/2.5/weather" + params = { + "q": location, + "appid": OPENWEATHER_API_KEY, + "units": "metric", # Celsius } + try: + async with session.get(url, params=params, timeout=10) as resp: + if resp.status == 200: + return await resp.json() + logging.info(f"OpenWeatherMap HTTP {resp.status}, falling back") + except Exception as e: + logging.warning(f"OpenWeatherMap request error: {e}") + return None - return weather_emojis.get(condition, '🌡️') + +def format_openweathermap(data: dict) -> str: + """Build the one-line weather message from OpenWeatherMap data.""" + city = data.get("name", "Unknown") + sys_data = data.get("sys", {}) + country = sys_data.get("country", "") + + main_data = data.get("main", {}) + temp_c = main_data.get("temp", 0) + temp_f = round(temp_c * 9 / 5 + 32, 1) + humidity = main_data.get("humidity", 0) + + weather_list = data.get("weather", []) + description = weather_list[0]["description"].capitalize() if weather_list else "Unknown" + emoji = "🌡️" + if weather_list: + wmain = weather_list[0].get("main", "") + emoji = { + "Clear": "☀️", "Clouds": "☁️", "Rain": "🌧️", "Drizzle": "🌦️", + "Thunderstorm": "⛈️", "Snow": "❄️", "Mist": "🌫️", "Fog": "🌫️", + "Haze": "🌫️", "Smoke": "🌫️", "Dust": "🌫️", "Sand": "🌫️", + "Ash": "🌫️", "Squall": "💨", "Tornado": "🌪️", + }.get(wmain, "🌡️") + + wind = data.get("wind", {}).get("speed", 0) + + return ( + f"[{emoji} Weather for {city}, {country}]: " + f"Condition: {description} | " + f"Temperature: {temp_c:.1f}°C ({temp_f:.1f}°F) | " + f"Humidity: {humidity}% | " + f"Wind Speed: {wind} m/s" + ) + + +# --------------------------------------------------------------------------- +# Fallback: Open‑Meteo (no key, free) +# --------------------------------------------------------------------------- +async def meteo_geocode(session: aiohttp.ClientSession, location: str) -> dict | None: + """Geocode a city name via Open‑Meteo. Returns location info dict or None.""" + url = "https://geocoding-api.open-meteo.com/v1/search" + params = {"name": location, "count": 1, "language": "en"} + try: + async with session.get(url, params=params, timeout=10) as resp: + if resp.status == 200: + data = await resp.json() + results = data.get("results", []) + if results: + r = results[0] + return { + "name": r["name"], + "latitude": r["latitude"], + "longitude": r["longitude"], + "country": r.get("country", ""), + "state": r.get("admin1", ""), + "timezone": r.get("timezone", "UTC"), + } + except Exception as e: + logging.warning(f"Open‑Meteo geocode error: {e}") + return None + + +async def meteo_weather(session: aiohttp.ClientSession, lat: float, lon: float, + timezone: str = "auto") -> dict | None: + """Fetch current weather from Open‑Meteo. Returns JSON or None.""" + url = "https://api.open-meteo.com/v1/forecast" + params = { + "latitude": lat, + "longitude": lon, + "current_weather": "true", + "temperature_unit": "fahrenheit", + "windspeed_unit": "mph", + "timezone": timezone, + } + try: + async with session.get(url, params=params, timeout=10) as resp: + if resp.status == 200: + return await resp.json() + except Exception as e: + logging.warning(f"Open‑Meteo weather error: {e}") + return None + + +def format_meteo(loc_info: dict, weather_data: dict) -> str: + """Format Open‑Meteo result into the same one‑line style.""" + c = weather_data["current_weather"] + code = c["weathercode"] + desc, emoji = WMO_CODES.get(code, ("Unknown", "🌡️")) + + city = loc_info["name"] + country = loc_info.get("country", "") + state = loc_info.get("state", "") + + # Build location string + parts = [city] + if state and state != city: + parts.append(state) + if country: + parts.append(country) + loc_str = ", ".join(parts) + + temp_f = c["temperature"] + temp_c = round((temp_f - 32) * 5 / 9, 1) + wind = c["windspeed"] + + return ( + f"[{emoji} Weather for {loc_str}]: " + f"Condition: {desc} | " + f"Temperature: {temp_c}°C ({temp_f}°F) | " + f"Wind Speed: {wind} mph" + ) + + +# --------------------------------------------------------------------------- +# Plugin entry point +# --------------------------------------------------------------------------- +async def handle_command(room, message, bot, prefix, config): + match = botlib.MessageMatch(room, message, bot, prefix) + if not (match.is_not_from_this_bot() and match.prefix() and match.command("weather")): + return + + args = match.args() + if not args: + await bot.api.send_text_message( + room.room_id, + "Usage: !weather \nExample: !weather London or !weather New York,US" + ) + return + + location = " ".join(args) + logging.info("Received !weather command for '%s'", location) + + async with aiohttp.ClientSession() as session: + # 1. Try OpenWeatherMap + owm_data = await openweathermap_get(session, location) + if owm_data: + if owm_data.get("cod") == 200: + msg = format_openweathermap(owm_data) + await bot.api.send_markdown_message(room.room_id, msg) + logging.info("Sent weather via OpenWeatherMap") + return + # OpenWeatherMap returned an error status inside JSON (e.g., 401, 404) + logging.info("OpenWeatherMap returned error code %s, falling back", owm_data.get("cod")) + + # 2. Fallback: Open‑Meteo + logging.info("Falling back to Open‑Meteo") + loc_info = await meteo_geocode(session, location) + if not loc_info: + await bot.api.send_text_message( + room.room_id, + f"Location '{location}' not found." + ) + return + + wdata = await meteo_weather(session, loc_info["latitude"], + loc_info["longitude"], + loc_info.get("timezone", "auto")) + if not wdata: + await bot.api.send_text_message( + room.room_id, + "Could not fetch weather data from any provider." + ) + return + + msg = format_meteo(loc_info, wdata) + await bot.api.send_markdown_message(room.room_id, msg) + logging.info("Sent weather via Open‑Meteo (fallback)") + + +# --------------------------------------------------------------------------- +# Plugin setup +# --------------------------------------------------------------------------- +def setup(bot): + logging.info("Weather plugin loaded (OpenWeatherMap + Open‑Meteo fallback)") + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Weather forecast (OWM primary, Open‑Meteo fallback)" +__help__ = """ +
    +!weather – Current weather +

    !weather <location> – Shows temperature, conditions, humidity, wind.
    +Uses OpenWeatherMap if a valid API key is present; falls back to free Open‑Meteo otherwise.

    +
    +""" diff --git a/plugins/welcome.py b/plugins/welcome.py index 1973ee3..a4b2be7 100644 --- a/plugins/welcome.py +++ b/plugins/welcome.py @@ -168,3 +168,17 @@ async def handle_command(room, message, bot, prefix, config): room.room_id, _build_welcome_message(display_name) ) logging.info("Sent manual !welcome to %s", sender) + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Room welcome message" +__help__ = """ +
    +!welcome – Receive welcome message +

    Manually triggers the room's welcome message for yourself.

    +
    +""" diff --git a/plugins/whois.py b/plugins/whois.py index a5ff539..059794f 100644 --- a/plugins/whois.py +++ b/plugins/whois.py @@ -203,3 +203,17 @@ async def handle_command(room, message, bot, prefix, config): f"An unexpected error occurred during WHOIS lookup for {query}. Please try again later." ) logging.error(f"Unexpected error in WHOIS plugin for {query}: {e}", exc_info=True) + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "WHOIS lookup" +__help__ = """ +
    +!whois – WHOIS lookup +

    !whois <domain or IP> – Shows registrar, creation/expiry dates, nameservers, contacts.

    +
    +""" diff --git a/plugins/wikipedia.py b/plugins/wikipedia.py index d3fab9f..453de7f 100644 --- a/plugins/wikipedia.py +++ b/plugins/wikipedia.py @@ -280,3 +280,19 @@ async def handle_command(room, message, bot, prefix, config): os.unlink(image_path) except OSError: pass + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Wikipedia article summary" +__help__ = """ +
    +!wp – Wikipedia summary +

    !wp <search term> – Returns the lead section and main image from Wikipedia.
    +Uses MediaWiki APIs, no scraping.

    +
    +""" diff --git a/plugins/xkcd.py b/plugins/xkcd.py index 3c9d1fb..b1a3f3f 100644 --- a/plugins/xkcd.py +++ b/plugins/xkcd.py @@ -43,3 +43,18 @@ async def handle_command(room, message, bot, prefix, config): os.remove(image_path) except Exception as e: await bot.api.send_text_message(room.room_id, f"Error fetching XKCD comic: {str(e)}") + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "Random XKCD comic" +__help__ = """ +
    +!xkcd – Random XKCD comic +

    Posts a random XKCD comic image.

    +
    +""" diff --git a/plugins/youtube-search.py b/plugins/youtube-search.py index 21b3624..6152f8f 100644 --- a/plugins/youtube-search.py +++ b/plugins/youtube-search.py @@ -71,3 +71,18 @@ async def send_collapsible_message(room, bot, content): """ message = f'
    🍄Funguy ▶YouTube Search🍄
    ⤵︎Click Here To See Results⤵︎
    {content}
    ' await bot.api.send_markdown_message(room.room_id, message) + + +# --------------------------------------------------------------------------- +# Plugin Metadata +# --------------------------------------------------------------------------- + +__version__ = "1.0.0" +__author__ = "Funguy Bot" +__description__ = "YouTube video search" +__help__ = """ +
    +!yt – Search YouTube +

    !yt <search terms> – Returns top 3 results with thumbnails and descriptions.

    +
    +"""