Refactor help plugin, and included metadata to all plugins
This commit is contained in:
@@ -391,6 +391,11 @@ class FunguyBot:
|
|||||||
self.setup_plugins()
|
self.setup_plugins()
|
||||||
logging.info("✓ Plugin setup complete")
|
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
|
# Defining listener for message events
|
||||||
@self.bot.listener.on_message_event
|
@self.bot.listener.on_message_event
|
||||||
async def wrapper_handle_commands(room, message):
|
async def wrapper_handle_commands(room, message):
|
||||||
|
|||||||
@@ -320,3 +320,17 @@ def setup(bot):
|
|||||||
__version__ = "1.0.0"
|
__version__ = "1.0.0"
|
||||||
__author__ = "Funguy Bot"
|
__author__ = "Funguy Bot"
|
||||||
__description__ = "arXiv academic paper search"
|
__description__ = "arXiv academic paper search"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!arxiv</strong> – Search academic papers on arXiv</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!arxiv <query></code> – Search papers (shows abstracts)</li>
|
||||||
|
<li><code>!arxiv list <query></code> – List without abstracts</li>
|
||||||
|
<li><code>!arxiv category <category></code> – Browse recent papers by category</li>
|
||||||
|
<li><code>!arxiv recent [category]</code> – Most recent papers (7 days)</li>
|
||||||
|
<li><code>!arxiv random</code> – Random paper</li>
|
||||||
|
<li><code>!arxiv <id></code> – Get paper by arXiv ID (e.g., 2101.00101)</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Categories:</strong> ai, ml, security, crypto, cv, nlp, math, physics, quantum, bio, software</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -107,3 +107,18 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
"An unexpected error occurred while fetching Bitcoin price."
|
"An unexpected error occurred while fetching Bitcoin price."
|
||||||
)
|
)
|
||||||
logging.error(f"Unexpected error in Bitcoin plugin: {e}", exc_info=True)
|
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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!btc</strong> – Current Bitcoin price</summary>
|
||||||
|
<p>Fetches the latest BTC/USD price from bitcointicker.co.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -152,3 +152,25 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
config.admin_user = ""
|
config.admin_user = ""
|
||||||
config.prefix = "!"
|
config.prefix = "!"
|
||||||
await bot.api.send_text_message(room.room_id, "Configuration reset")
|
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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>Admin Config</strong> (!set, !get, !saveconf, …)</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!set <option> <value></code> – Set admin_user/prefix</li>
|
||||||
|
<li><code>!get <option></code> – Display config value</li>
|
||||||
|
<li><code>!show</code> – Show current settings</li>
|
||||||
|
<li><code>!saveconf</code> / <code>!loadconf</code> – Save/load config</li>
|
||||||
|
<li><code>!rehash</code> – Reload configuration</li>
|
||||||
|
</ul>
|
||||||
|
<p>Admin only.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -62,3 +62,20 @@ async def run_cron_jobs(bot):
|
|||||||
if plugin_module:
|
if plugin_module:
|
||||||
await plugin_module.handle_command(room, None, bot, prefix, config)
|
await plugin_module.handle_command(room, None, bot, prefix, config)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "Cron job scheduler"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!cron</strong> – Schedule commands via cron syntax</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!cron add <room_id> <cron_entry> <command></code> – Add job</li>
|
||||||
|
<li><code>!cron remove <room_id> <command></code> – Remove job</li>
|
||||||
|
</ul>
|
||||||
|
<p>Admin only.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -59,3 +59,17 @@ def get_ordinal_suffix(day_of_month):
|
|||||||
return "nd"
|
return "nd"
|
||||||
else:
|
else:
|
||||||
return "th"
|
return "th"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "Show current date and time"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!date</strong> – Current date and time</summary>
|
||||||
|
<p>Displays the day of the week, ordinal date, and AM/PM time.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -480,3 +480,31 @@ async def ddg_weather(room, bot, location):
|
|||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await bot.api.send_text_message(room.room_id, f"Error getting weather: {str(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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!ddg</strong> – DuckDuckGo search and instant answers</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!ddg <query></code> – Instant answer (default)</li>
|
||||||
|
<li><code>!ddg search <query></code> – Web search results</li>
|
||||||
|
<li><code>!ddg instant <query></code> – Detailed instant answer</li>
|
||||||
|
<li><code>!ddg image <query></code> – Image search</li>
|
||||||
|
<li><code>!ddg news <query></code> – News search</li>
|
||||||
|
<li><code>!ddg video <query></code> – Video search</li>
|
||||||
|
<li><code>!ddg bang <!bang query></code> – Use DuckDuckGo bangs</li>
|
||||||
|
<li><code>!ddg define <word></code> – Word definition</li>
|
||||||
|
<li><code>!ddg calc <expression></code> – Calculator</li>
|
||||||
|
<li><code>!ddg weather [location]</code> – Weather information</li>
|
||||||
|
<li><code>!ddg help</code> – Show detailed help</li>
|
||||||
|
</ul>
|
||||||
|
<p>No API key required.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -207,3 +207,18 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
f"An error occurred while performing DNS lookup: {str(e)}"
|
f"An error occurred while performing DNS lookup: {str(e)}"
|
||||||
)
|
)
|
||||||
logging.error(f"Error in DNS plugin for {domain}: {e}", exc_info=True)
|
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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!dns</strong> – DNS reconnaissance</summary>
|
||||||
|
<p><code>!dns <domain></code> – Queries A, AAAA, MX, NS, TXT, CNAME, SOA, SRV, PTR records.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -268,3 +268,22 @@ async def format_dnsdumpster_report(domain, data):
|
|||||||
output = f"<details><summary><strong>🔍 DNSDumpster Report: {domain} (Click to expand)</strong></summary>{output}</details>"
|
output = f"<details><summary><strong>🔍 DNSDumpster Report: {domain} (Click to expand)</strong></summary>{output}</details>"
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "DNSDumpster domain reconnaissance"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!dnsdumpster</strong> – Comprehensive DNS mapping via DNSDumpster</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!dnsdumpster <domain></code> – Full recon (A, NS, MX, CNAME, TXT, etc.)</li>
|
||||||
|
<li><code>!dnsdumpster test</code> – Test API connection</li>
|
||||||
|
</ul>
|
||||||
|
<p>Requires <strong>DNSDUMPSTER_KEY</strong> env var. Rate limit: 1 req/2 sec.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -236,3 +236,19 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
f"An error occurred while searching Exploit-DB: {str(e)}"
|
f"An error occurred while searching Exploit-DB: {str(e)}"
|
||||||
)
|
)
|
||||||
logging.error(f"Error in exploitdb plugin: {e}", exc_info=True)
|
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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!exploitdb</strong> – Search Exploit Database</summary>
|
||||||
|
<p><code>!exploitdb <search term> [max_results]</code> – Search for exploits (title, EDB-ID, type, platform, author, link).<br>
|
||||||
|
Example: <code>!exploitdb wordpress 5</code></p>
|
||||||
|
<p>Fetches from the official Exploit-DB CSV. Falls back to search links if unavailable.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -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')
|
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)
|
await bot.api.send_markdown_message(room.room_id, fortune_output)
|
||||||
logging.info("Sent fortune to the room")
|
logging.info("Sent fortune to the room")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "Random fortune message"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!fortune</strong> – Random fortune</summary>
|
||||||
|
<p>Runs the <code>/usr/games/fortune</code> utility and posts a random quote.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -267,3 +267,22 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
f"An error occurred during geolocation lookup for {query}. Please try again later."
|
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)
|
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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!geo</strong> – IP / domain geolocation</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!geo <ip></code> – Locate an IP address</li>
|
||||||
|
<li><code>!geo <domain></code> – Resolves domain then locates</li>
|
||||||
|
</ul>
|
||||||
|
<p>Shows country, region, city, coordinates, ISP, ASN. Uses ip-api.com / ipapi.co.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -388,3 +388,16 @@ def setup(bot):
|
|||||||
__version__ = "1.0.0"
|
__version__ = "1.0.0"
|
||||||
__author__ = "Funguy Bot"
|
__author__ = "Funguy Bot"
|
||||||
__description__ = "Hacker News integration"
|
__description__ = "Hacker News integration"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!hn</strong> – Hacker News stories</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!hn</code> – Top 5 stories</li>
|
||||||
|
<li><code>!hn top|new|best|ask|show|job</code> – Story type</li>
|
||||||
|
<li><code>!hn story <id></code> – Story details</li>
|
||||||
|
<li><code>!hn comments <id></code> – Show comments</li>
|
||||||
|
<li><code>!hn search <query></code> – Search via Algolia</li>
|
||||||
|
</ul>
|
||||||
|
<p>Free, no API key needed.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -384,3 +384,18 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
f"Error identifying hash: {str(e)}"
|
f"Error identifying hash: {str(e)}"
|
||||||
)
|
)
|
||||||
logging.error(f"Error in hashid command: {e}", exc_info=True)
|
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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!hashid</strong> – Identify hash type</summary>
|
||||||
|
<p><code>!hashid <hash></code> – Recognises 100+ hash formats (MD5, SHA, bcrypt, etc.).<br>
|
||||||
|
Shows confidence level, Hashcat mode, and John the Ripper format.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -385,3 +385,19 @@ async def format_header_analysis(results):
|
|||||||
output = f"<details><summary><strong>🔒 Security Headers Analysis: {results['url']}</strong></summary>{output}</details>"
|
output = f"<details><summary><strong>🔒 Security Headers Analysis: {results['url']}</strong></summary>{output}</details>"
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "HTTP security header analysis"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!headers</strong> – HTTP security header scanner</summary>
|
||||||
|
<p><code>!headers <url></code> – Checks HSTS, CSP, X-Frame-Options, etc.<br>
|
||||||
|
Provides security score (0-100) and recommendations. Also shows SSL certificate info.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
+54
-652
@@ -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 logging
|
||||||
import simplematrixbotlib as botlib
|
import simplematrixbotlib as botlib
|
||||||
|
|
||||||
async def handle_command(room, message, bot, prefix, config):
|
async def handle_command(room, message, bot, prefix, config):
|
||||||
"""
|
|
||||||
Function to handle the !help command.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
None
|
|
||||||
"""
|
|
||||||
match = botlib.MessageMatch(room, message, bot, prefix)
|
match = botlib.MessageMatch(room, message, bot, prefix)
|
||||||
if match.is_not_from_this_bot() and match.prefix() and match.command("help"):
|
if not (match.is_not_from_this_bot() and match.prefix() and match.command("help")):
|
||||||
logging.info("Fetching command help documentation")
|
return
|
||||||
commands_message = """
|
|
||||||
<details><summary><strong>🍄 Funguy Bot Commands 🍄</strong></summary>
|
|
||||||
<p>
|
|
||||||
<details><summary>📖 <strong>!help</strong></summary>
|
|
||||||
<p>Displays comprehensive help documentation for all available commands with usage examples.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🔌 <strong>!plugins</strong></summary>
|
args = match.args()
|
||||||
<p>Lists all loaded plugins along with their descriptions in alphabetical order.</p>
|
plugins = getattr(bot, "plugins", {})
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🃏 <strong>!fortune</strong></summary>
|
if not plugins:
|
||||||
<p>Returns a random fortune message. Executes the `/usr/games/fortune` utility and sends the output as a message to the chat room.</p>
|
await bot.api.send_text_message(room.room_id, "No plugins are currently loaded.")
|
||||||
</details>
|
return
|
||||||
|
|
||||||
<details><summary>⏰ <strong>!date</strong></summary>
|
# If a specific plugin is requested
|
||||||
<p>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.</p>
|
if args:
|
||||||
</details>
|
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
|
||||||
|
|
||||||
<details><summary>💻 <strong>!proxy</strong></summary>
|
# Aggregate help from all plugins
|
||||||
<p>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.</p>
|
help_parts = []
|
||||||
</details>
|
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"<details><summary><strong>!{pname}</strong></summary><p>{first_line}</p></details>"
|
||||||
|
)
|
||||||
|
|
||||||
<details><summary>📶 <strong>!isup [domain/ip]</strong></summary>
|
# Append bot credits
|
||||||
<p>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.</p>
|
credits = """
|
||||||
</details>
|
<details><summary><strong>🌟 Funguy Bot Credits</strong></summary>
|
||||||
|
|
||||||
<details><summary>☯ <strong>!karma [user]</strong></summary>
|
|
||||||
<p>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.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>⇧ <strong>!karma [user] up</strong></summary>
|
|
||||||
<p>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.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>⇩ <strong>!karma [user] down</strong></summary>
|
|
||||||
<p>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.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🌧️ <strong>!weather [location]</strong></summary>
|
|
||||||
<p>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.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>📖 <strong>!ud [term] [index]</strong></summary>
|
|
||||||
<p>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.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🔍 <strong>!dns [domain]</strong></summary>
|
|
||||||
<p>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.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>💰 <strong>!btc</strong></summary>
|
|
||||||
<p>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.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🌐 <strong>!whois <domain/ip></strong></summary>
|
|
||||||
<p>Perform comprehensive WHOIS lookups for domains and IP addresses. Retrieves registrar information, registration dates, name servers, and contact details from WHOIS databases.</p>
|
|
||||||
<p><strong>Usage:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!whois <domain></code> - Query domain registration information</li>
|
|
||||||
<li><code>!whois <ip></code> - Query IP address allocation details</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Examples:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!whois example.com</code></li>
|
|
||||||
<li><code>!whois google.com</code></li>
|
|
||||||
<li><code>!whois 8.8.8.8</code></li>
|
|
||||||
<li><code>!whois 1.1.1.1</code></li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Output includes:</strong> Domain/IP information, registrar, WHOIS server, creation/expiration dates, name servers, and contact details.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🔍 <strong>!subdomains <domain></strong></summary>
|
|
||||||
<p>Enumerate subdomains using SSL certificate transparency logs. Discovers associated subdomains by querying the CertSpotter API for SSL certificates issued for a domain.</p>
|
|
||||||
<p><strong>Usage:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!subdomains <domain></code> - Enumerate subdomains for any domain</li>
|
|
||||||
<li><code>!subdomains example.com</code> - Example subdomain enumeration</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Features:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>Discovers subdomains through SSL certificate transparency logs</li>
|
|
||||||
<li>Uses the free CertSpotter API for enumeration</li>
|
|
||||||
<li>No rate limiting or API key required</li>
|
|
||||||
<li>Identifies subdomains through certificate SAN (Subject Alternative Name) enumeration</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Examples:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!subdomains google.com</code></li>
|
|
||||||
<li><code>!subdomains github.com</code></li>
|
|
||||||
<li><code>!subdomains example.com</code></li>
|
|
||||||
</ul>
|
|
||||||
<p><em>Essential for reconnaissance and subdomain enumeration in penetration testing</em></p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>📍 <strong>!geo [ip/domain]</strong></summary>
|
|
||||||
<p>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.</p>
|
|
||||||
<p><strong>Usage:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!geo <ip_address></code> - Geolocate an IP address</li>
|
|
||||||
<li><code>!geo <domain></code> - Geolocate a domain (automatically resolves to IP)</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Features:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>Uses ip-api.com as primary geolocation service with ipapi.co fallback</li>
|
|
||||||
<li>Automatic domain to IP resolution</li>
|
|
||||||
<li>Comprehensive geographic information</li>
|
|
||||||
<li>No API key required for basic usage</li>
|
|
||||||
<li>Supports both IPv4 and IPv6 addresses</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Examples:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!geo 8.8.8.8</code></li>
|
|
||||||
<li><code>!geo example.com</code></li>
|
|
||||||
<li><code>!geo google.com</code></li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Information provided:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>Country and country code</li>
|
|
||||||
<li>Region/State</li>
|
|
||||||
<li>City</li>
|
|
||||||
<li>Postal code</li>
|
|
||||||
<li>Latitude/Longitude coordinates</li>
|
|
||||||
<li>Timezone</li>
|
|
||||||
<li>ISP/Organization</li>
|
|
||||||
<li>Autonomous System Number (ASN)</li>
|
|
||||||
</ul>
|
|
||||||
<p><em>Essential for network reconnaissance and IP investigation</em></p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🔍 <strong>!shodan [command] [query]</strong></summary>
|
|
||||||
<p>Shodan.io integration for security reconnaissance and threat intelligence.</p>
|
|
||||||
<p><strong>Commands:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!shodan ip <ip_address></code> - Detailed IP information (services, ports, banners)</li>
|
|
||||||
<li><code>!shodan search <query></code> - Search Shodan database with filters</li>
|
|
||||||
<li><code>!shodan host <domain></code> - Host information and subdomain enumeration</li>
|
|
||||||
<li><code>!shodan count <query></code> - Count results with geographic/organization breakdown</li>
|
|
||||||
<li><code>!shodan test</code> - Test API connection and debug queries</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Search Examples:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!shodan search apache</code></li>
|
|
||||||
<li><code>!shodan search "port:22 country:US"</code></li>
|
|
||||||
<li><code>!shodan search "product:nginx"</code></li>
|
|
||||||
<li><code>!shodan search "net:192.168.1.0/24"</code></li>
|
|
||||||
<li><code>!shodan search "http.title:'admin'"</code></li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Common Filters:</strong> country, city, port, product, os, org, net, has_ssl, http.title</p>
|
|
||||||
<p><em>Requires SHODAN_KEY environment variable</em></p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🌐 <strong>!dnsdumpster [domain]</strong></summary>
|
|
||||||
<p>Comprehensive DNS reconnaissance and attack surface mapping using DNSDumpster.com API.</p>
|
|
||||||
<p><strong>Commands:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!dnsdumpster <domain></code> - Complete DNS reconnaissance for any domain</li>
|
|
||||||
<li><code>!dnsdumpster test</code> - Test API connection and key validity</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Features:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>A Records - All IPv4 addresses with geographic and ASN information</li>
|
|
||||||
<li>NS Records - Complete name server information with IP locations</li>
|
|
||||||
<li>MX Records - All mail servers with geographic data</li>
|
|
||||||
<li>CNAME Records - Full alias chain mappings</li>
|
|
||||||
<li>TXT Records - All text records including SPF, DKIM, verification</li>
|
|
||||||
<li>Additional Records - AAAA, SRV, SOA, PTR records when available</li>
|
|
||||||
<li>Web Services - HTTP/HTTPS service detection with banner information</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Examples:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!dnsdumpster google.com</code></li>
|
|
||||||
<li><code>!dnsdumpster github.com</code></li>
|
|
||||||
<li><code>!dnsdumpster example.com</code></li>
|
|
||||||
</ul>
|
|
||||||
<p><em>Requires DNSDUMPSTER_KEY environment variable</em><br>
|
|
||||||
<em>Rate Limit: 1 request per 2 seconds</em></p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><strong>💣 !exploitdb - Search Exploit Database</strong></summary>
|
|
||||||
<br>
|
|
||||||
<strong>Description:</strong><br>
|
|
||||||
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.<br>
|
|
||||||
<br>
|
|
||||||
<strong>Usage:</strong><br>
|
|
||||||
<code>!exploitdb <search_term> [max_results]</code><br>
|
|
||||||
<br>
|
|
||||||
<strong>Parameters:</strong><br>
|
|
||||||
• <strong>search_term</strong> (required) - Software name, CVE number, or vulnerability type<br>
|
|
||||||
• <strong>max_results</strong> (optional) - Number of results to return (1-10, default: 5)<br>
|
|
||||||
<br>
|
|
||||||
<strong>Examples:</strong><br>
|
|
||||||
<code>!exploitdb wordpress</code> - Search for WordPress exploits<br>
|
|
||||||
<code>!exploitdb apache 3</code> - Get 3 Apache exploits<br>
|
|
||||||
<code>!exploitdb windows privilege escalation</code> - Search for Windows privesc<br>
|
|
||||||
<code>!exploitdb CVE-2021-44228</code> - Search by CVE number<br>
|
|
||||||
<code>!exploitdb linux kernel 10</code> - Get 10 Linux kernel exploits<br>
|
|
||||||
<code>!exploitdb sql injection</code> - Search for SQL injection exploits<br>
|
|
||||||
<br>
|
|
||||||
<strong>Output Includes:</strong><br>
|
|
||||||
• Exploit title and description<br>
|
|
||||||
• EDB-ID (Exploit Database ID)<br>
|
|
||||||
• Exploit type (webapps, local, remote, etc.)<br>
|
|
||||||
• Platform/OS (PHP, Linux, Windows, etc.)<br>
|
|
||||||
• Author name<br>
|
|
||||||
• Publication date<br>
|
|
||||||
• Direct link to full exploit code<br>
|
|
||||||
<br>
|
|
||||||
<strong>Notes:</strong><br>
|
|
||||||
• Searches the official Exploit-DB CSV database<br>
|
|
||||||
• May take a few seconds on first use (downloads database)<br>
|
|
||||||
• Falls back to search links if database unavailable<br>
|
|
||||||
<br>
|
|
||||||
<em>⚠️ Use responsibly and only on systems you have permission to test.</em>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🛡️ <strong>!headers <url></strong></summary>
|
|
||||||
<p>Comprehensive HTTP security header analysis with security scoring and recommendations.</p>
|
|
||||||
<p><strong>Features:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>Security scoring (0-100) with color-coded ratings</li>
|
|
||||||
<li>Critical security header validation and configuration checking</li>
|
|
||||||
<li>HTTP to HTTPS redirect chain analysis</li>
|
|
||||||
<li>SSL certificate information for HTTPS sites</li>
|
|
||||||
<li>Information disclosure header detection</li>
|
|
||||||
<li>Actionable security recommendations</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Security Headers Analyzed:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>Strict-Transport-Security</code> - HSTS enforcement</li>
|
|
||||||
<li><code>Content-Security-Policy</code> - XSS protection</li>
|
|
||||||
<li><code>X-Frame-Options</code> - Clickjacking protection</li>
|
|
||||||
<li><code>X-Content-Type-Options</code> - MIME sniffing prevention</li>
|
|
||||||
<li><code>Referrer-Policy</code> - Referrer control</li>
|
|
||||||
<li><code>Feature-Policy</code> - Browser feature restrictions</li>
|
|
||||||
<li>Server information headers</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Security Ratings:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>🟢 <strong>Excellent (80-100)</strong> - Strong configuration</li>
|
|
||||||
<li>🟡 <strong>Good (60-79)</strong> - Moderate, needs improvement</li>
|
|
||||||
<li>🟠 <strong>Fair (40-59)</strong> - Basic, significant improvements needed</li>
|
|
||||||
<li>🔴 <strong>Poor (0-39)</strong> - Weak configuration</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Examples:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!headers example.com</code></li>
|
|
||||||
<li><code>!headers https://github.com</code></li>
|
|
||||||
<li><code>!headers localhost:3000</code></li>
|
|
||||||
<li><code>!headers subdomain.target.com</code></li>
|
|
||||||
</ul>
|
|
||||||
<p><em>Provides enterprise-grade security analysis for penetration testers and developers</em></p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🔄 <strong>!hashid <hash></strong></summary>
|
|
||||||
<p>Advanced hash type identification with confidence scoring and tool recommendations.</p>
|
|
||||||
<p><strong>Features:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>100+ hash types including modern, legacy, and exotic algorithms</li>
|
|
||||||
<li>Color-coded confidence scoring (🟢 Very High to 🔴 Low)</li>
|
|
||||||
<li>Hashcat mode numbers and John the Ripper format names</li>
|
|
||||||
<li>Context-aware parsing for various hash formats</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Supported Categories:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Modern:</strong> yescrypt, scrypt, Argon2, bcrypt</li>
|
|
||||||
<li><strong>Unix/Linux:</strong> SHA-512/256 Crypt, MD5 Crypt, apr1</li>
|
|
||||||
<li><strong>Raw Hashes:</strong> MD5, SHA family, SHA-3, NTLM, LM</li>
|
|
||||||
<li><strong>Databases:</strong> MySQL, PostgreSQL, Oracle, MSSQL</li>
|
|
||||||
<li><strong>Web/CMS:</strong> WordPress, Drupal, phpBB3, Django</li>
|
|
||||||
<li><strong>LDAP:</strong> SSHA, SMD5, LDAP crypt formats</li>
|
|
||||||
<li><strong>Network:</strong> NetNTLMv1/v2, Kerberos</li>
|
|
||||||
<li><strong>Exotic:</strong> Whirlpool, RIPEMD, GOST, BLAKE2</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Tool Integration:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Hashcat:</strong> Mode numbers for <code>-m</code> parameter</li>
|
|
||||||
<li><strong>John:</strong> Format names for <code>--format=</code> parameter</li>
|
|
||||||
<li>Multi-tool compatibility</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Examples:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!hashid 5d41402abc4b2a76b9719d911017c592</code> (MD5)</li>
|
|
||||||
<li><code>!hashid aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d</code> (SHA-1)</li>
|
|
||||||
<li><code>!hashid $6$rounds=5000$salt$hash...</code> (SHA-512 Crypt)</li>
|
|
||||||
<li><code>!hashid $y$j9T$...</code> (yescrypt - modern Linux)</li>
|
|
||||||
<li><code>!hashid 8846f7eaee8fb117ad06bdd830b7586c</code> (NTLM)</li>
|
|
||||||
<li><code>!hashid *2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19</code> (MySQL)</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Confidence Legend:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>🟢 Very High (90-100%) - Single definitive match</li>
|
|
||||||
<li>🟡 High (80-89%) - Strong match with minor alternatives</li>
|
|
||||||
<li>🟠 Medium (60-79%) - Multiple plausible matches</li>
|
|
||||||
<li>🔴 Low (0-59%) - Uncertain, needs context</li>
|
|
||||||
</ul>
|
|
||||||
<p><em>Essential for penetration testers, forensic analysts, and password cracking</em></p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🔐 <strong>!sslscan <domain[:port]></strong></summary>
|
|
||||||
<p>Comprehensive SSL/TLS security scanning and analysis with vulnerability detection.</p>
|
|
||||||
<p><strong>Features:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>TLS 1.0-1.3 protocol support testing with security scoring</li>
|
|
||||||
<li>Certificate chain validation, expiration, and signature analysis</li>
|
|
||||||
<li>25+ cipher suite testing with strength classification</li>
|
|
||||||
<li>Vulnerability detection (POODLE, weak ciphers, protocol issues)</li>
|
|
||||||
<li>0-100 security rating with color-coded assessment</li>
|
|
||||||
<li>PCI DSS and modern security standards compliance checking</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Security Checks:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Protocol Security:</strong> TLS 1.2/1.3 enforcement, insecure protocol detection</li>
|
|
||||||
<li><strong>Certificate Health:</strong> Expiration monitoring, signature validation</li>
|
|
||||||
<li><strong>Cipher Security:</strong> RC4, DES, 3DES, NULL cipher detection</li>
|
|
||||||
<li><strong>Modern Standards:</strong> Forward Secrecy, strong encryption</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Security Ratings:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>🟢 <strong>Excellent (90-100)</strong> - Modern TLS with strong security</li>
|
|
||||||
<li>🟡 <strong>Good (80-89)</strong> - Good security, minor improvements needed</li>
|
|
||||||
<li>🟠 <strong>Fair (60-79)</strong> - Moderate security, significant improvements</li>
|
|
||||||
<li>🔴 <strong>Poor (0-59)</strong> - Critical issues requiring immediate attention</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Examples:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!sslscan example.com</code></li>
|
|
||||||
<li><code>!sslscan github.com:443</code></li>
|
|
||||||
<li><code>!sslscan localhost:8443</code></li>
|
|
||||||
<li><code>!sslscan 192.168.1.1:443</code></li>
|
|
||||||
</ul>
|
|
||||||
<p><em>Essential for security teams, system administrators, and developers ensuring TLS compliance</em><br>
|
|
||||||
<em>Note: SSLv2/SSLv3 testing limited by Python security features</em></p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🎵 <strong>Last.fm Integration</strong></summary>
|
|
||||||
<p>Comprehensive Last.fm integration with 30+ commands for music analytics and social features.</p>
|
|
||||||
<p><strong>Commands:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!register <username></code> - Register your Last.fm username</li>
|
|
||||||
<li><code>!np [user]</code> - Show currently playing track</li>
|
|
||||||
<li><code>!recent [user] [limit]</code> - Show recent tracks (default 10, max 50)</li>
|
|
||||||
<li><code>!toptracks [user] [period]</code> - Show top tracks (overall/7day/1month/3month/6month/12month)</li>
|
|
||||||
<li><code>!topartists [user] [period]</code> - Show top artists</li>
|
|
||||||
<li><code>!topalbums [user] [period]</code> - Show top albums</li>
|
|
||||||
<li><code>!loved [user]</code> - Show recently loved tracks</li>
|
|
||||||
<li><code>!profile [user]</code> - Detailed user profile</li>
|
|
||||||
<li><code>!playcount [user]</code> - Total scrobbles</li>
|
|
||||||
<li><code>!scrobbles [user]</code> - Detailed scrobbling statistics</li>
|
|
||||||
<li><code>!compare <user1> <user2></code> - Compare musical tastes</li>
|
|
||||||
<li><code>!taste [user]</code> - Top artists with taste-o-meter</li>
|
|
||||||
<li><code>!friends [user]</code> - Show Last.fm friends</li>
|
|
||||||
<li><code>!recommend [user]</code> - Artist recommendations</li>
|
|
||||||
<li><code>!similar <artist></code> - Find similar artists</li>
|
|
||||||
<li><code>!tag <tag></code> - Top artists for a tag/genre</li>
|
|
||||||
<li><code>!charts</code> - Global top tracks chart</li>
|
|
||||||
<li><code>!tagcloud [user]</code> - Top genre tags</li>
|
|
||||||
<li><code>!now</code> - What are registered users playing?</li>
|
|
||||||
<li><code>!decades [user]</code> - Favorite decades analysis</li>
|
|
||||||
<li><code>!genres [user]</code> - Top genres/tags</li>
|
|
||||||
<li><code>!era <year></code> - Popular tracks from a year</li>
|
|
||||||
<li><code>!weekly [user]</code> - Weekly listening report</li>
|
|
||||||
<li><code>!monthly [user]</code> - Monthly listening report</li>
|
|
||||||
<li><code>!yearly [user] [year]</code> - Yearly listening report</li>
|
|
||||||
<li><code>!first <artist> [user]</code> - Find first scrobble of an artist</li>
|
|
||||||
<li><code>!concerts [user]</code> - Upcoming concerts for top artists</li>
|
|
||||||
<li><code>!radio <artist></code> - Generate playlist based on artist</li>
|
|
||||||
<li><code>!mashup <artist1> <artist2></code> - Musical connections between artists</li>
|
|
||||||
<li><code>!collage [user] [size]</code> - Top album art URLs</li>
|
|
||||||
<li><code>!listening [user]</code> - Currently listening with album art</li>
|
|
||||||
<li><code>!awards [user]</code> - Milestone achievements</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Features:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>Register your Matrix ID with your Last.fm username</li>
|
|
||||||
<li>Display currently playing tracks with artist and album information</li>
|
|
||||||
<li>Compare musical tastes between users</li>
|
|
||||||
<li>Discover similar artists and genres</li>
|
|
||||||
<li>Get personalized artist recommendations</li>
|
|
||||||
<li>View detailed listening statistics and reports</li>
|
|
||||||
<li>Find upcoming concerts for your favorite artists</li>
|
|
||||||
<li>Generate playlists based on your musical preferences</li>
|
|
||||||
<li>View milestone achievements and listening habits</li>
|
|
||||||
<li>Uses SQLite database to store user associations</li>
|
|
||||||
<li>Requires LASTFM_API_KEY environment variable</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Examples:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!register your_lastfm_username</code> - Register your Last.fm username</li>
|
|
||||||
<li><code>!np</code> - Show your currently playing track</li>
|
|
||||||
<li><code>!recent 20</code> - Show your 20 most recent tracks</li>
|
|
||||||
<li><code>!topartists 7day</code> - Show your top artists from the last 7 days</li>
|
|
||||||
<li><code>!compare user1 user2</code> - Compare musical tastes between two users</li>
|
|
||||||
<li><code>!similar radiohead</code> - Find artists similar to Radiohead</li>
|
|
||||||
<li><code>!tag electronic</code> - Show top electronic artists</li>
|
|
||||||
<li><code>!era 1994</code> - Show popular tracks from 1994</li>
|
|
||||||
<li><code>!radio metallica</code> - Generate a playlist based on Metallica</li>
|
|
||||||
<li><code>!mashup metallica megadeth</code> - Find musical connections between Metallica and Megadeth</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Requirements:</strong></p>
|
|
||||||
<li>Last.fm account at last.fm</li>
|
|
||||||
<li>LASTFM_API_KEY in .env file</li>
|
|
||||||
<li>YOUTUBE_API_KEY in .env file (for YouTube integration)</li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<details><summary>📸 <strong>!sd [prompt]</strong></summary>
|
|
||||||
<p>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'.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>📄 <strong>!text [prompt]</strong></summary>
|
|
||||||
<p>Generates text using the Infermatic AI API. Supports multiple models, configurable parameters, and model listing. Uses queuing system for sequential processing.</p>
|
|
||||||
<p><strong>Usage:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!text <prompt></code> - Generate text using the default model</li>
|
|
||||||
<li><code>!text --list-models</code> - List all available models from Infermatic AI</li>
|
|
||||||
<li><code>!text --use-model <model_name> <prompt></code> - Use a specific model instead of the default</li>
|
|
||||||
<li><code>!text --temperature <value> <prompt></code> - Set temperature (0.0-1.0, default: 0.9)</li>
|
|
||||||
<li><code>!text --max-tokens <value> <prompt></code> - Set maximum tokens to generate (default: 2048)</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Configuration:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>Requires <code>INFERMATIC_API</code> environment variable set to your API key</li>
|
|
||||||
<li>Requires <code>INFERMATIC_MODEL</code> environment variable for default model (default: Sao10K-L3.1-70B-Hanami-x1)</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Model Management:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>Use <code>!text --list-models</code> to see all available models</li>
|
|
||||||
<li>Models support different capabilities and context lengths</li>
|
|
||||||
<li>Costs and token limits vary by model</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Examples:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!text write a python function to calculate fibonacci</code></li>
|
|
||||||
<li><code>!text --list-models</code></li>
|
|
||||||
<li><code>!text --use-model llama-v3-8b-instruct explain quantum computing</code></li>
|
|
||||||
<li><code>!text --temperature 0.7 --max-tokens 500 write a haiku about AI</code></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>📰 <strong>!xkcd</strong></summary>
|
|
||||||
<p>Fetches and displays a random XKCD comic. Downloads comic image and sends it directly to the chat room.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🎬 <strong>YouTube Features</strong></summary>
|
|
||||||
<p>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.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>📘 <strong>!wp <search term></strong></summary>
|
|
||||||
<p>Fetches Wikipedia summaries and main images for search terms using MediaWiki APIs. No HTML scraping or BeautifulSoup required.<br>
|
|
||||||
<strong>Usage:</strong><br>
|
|
||||||
<code>!wp <search term></code> - Fetch Wikipedia summary for any search term<br>
|
|
||||||
<code>!wp help</code> - Show usage instructions<br>
|
|
||||||
<br>
|
|
||||||
<strong>Examples:</strong><br>
|
|
||||||
<code>!wp artificial intelligence</code><br>
|
|
||||||
<code>!wp machine learning</code><br>
|
|
||||||
<code>!wp python programming</code>
|
|
||||||
</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>📘 <strong>!time <location></strong></summary>
|
|
||||||
<p>Fetches current time information for locations using the TimeAPI.io service.<br>
|
|
||||||
<strong>Usage:</strong><br>
|
|
||||||
<code>!time <location></code> - Get time for a location<br>
|
|
||||||
<code>!time help</code> - Show usage instructions<br>
|
|
||||||
<br>
|
|
||||||
<strong>Examples:</strong><br>
|
|
||||||
<code>!time London</code> - Get time in London<br>
|
|
||||||
<code>!time Tokyo</code> - Get time in Tokyo<br>
|
|
||||||
<code>!time New York</code> - Get time in New York<br>
|
|
||||||
<br>
|
|
||||||
<strong>Supported locations:</strong> Major cities worldwide including New York, London, Tokyo, Sydney, Paris, Berlin, etc.
|
|
||||||
</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>📚 <strong>!arxiv [query]</strong></summary>
|
|
||||||
<p>Search academic papers on arXiv.org. Categories include AI, ML, Security, Physics, Math, and more. No API key required.</p>
|
|
||||||
<p><strong>Commands:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!arxiv <query></code> - Search for papers (shows abstracts)</li>
|
|
||||||
<li><code>!arxiv list <query></code> - List papers without abstracts</li>
|
|
||||||
<li><code>!arxiv category <category></code> - Browse recent papers by category</li>
|
|
||||||
<li><code>!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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="codeblock" style="white-space: pre-line; padding: 10px; border: 1px solid #c8c8c8; border-radius: 3px; background: #f8f8f8; color: #3b3a30; font-family: monospace; font-size: 12px;">
|
|
||||||
</style>
|
|
||||||
<div class="codeblock">
|
|
||||||
<summary>📚 <strong>!arxiv [query]</strong></summary>
|
|
||||||
<p>Search academic papers on arXiv.org. Categories include AI, ML, Security, Physics, Math, and more. No API key required.</p>
|
|
||||||
<p><strong>Commands:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!arxiv <query></code> - Search for papers (shows abstracts)</li>
|
|
||||||
<li><code>!arxiv list <query></code> - List papers without abstracts</li>
|
|
||||||
<li><code>!arxiv category <category></code> - Browse recent papers by category</li>
|
|
||||||
<li><code>!arxiv recent <category></code> - Most recent papers in category</li>
|
|
||||||
<li><code>!arxiv random</code> - Get a random paper</li>
|
|
||||||
<li><code>!arxiv <id></code> - Get paper by arXiv ID (e.g., 2101.00101)</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Categories:</strong> ai, ml, security, crypto, cv, nlp, math, physics, quantum, bio, software</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>📰 <strong>!news [category/query]</strong></summary>
|
|
||||||
<p>Fetch latest headlines from various news categories using GNews API. Requires GNEWS_API_KEY environment variable.</p>
|
|
||||||
<p><strong>Commands:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!news</code> - Get top headlines (default)</li>
|
|
||||||
<li><code>!news top</code> - Top headlines</li>
|
|
||||||
<li><code>!news world</code> - World news</li>
|
|
||||||
<li><code>!news tech</code> - Technology news</li>
|
|
||||||
<li><code>!news business</code> - Business news</li>
|
|
||||||
<li><code>!news science</code> - Science news</li>
|
|
||||||
<li><code>!news health</code> - Health news</li>
|
|
||||||
<li><code>!news crypto</code> - Cryptocurrency news</li>
|
|
||||||
<li><code>!news search <query></code> - Search for specific news</li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🔥 <strong>!hn [command]</strong></summary>
|
|
||||||
<p>Fetch top stories from Hacker News using Firebase API. No API key required.</p>
|
|
||||||
<p><strong>Commands:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!hn</code> - Show top 5 stories (default)</li>
|
|
||||||
<li><code>!hn top</code> - Top stories</li>
|
|
||||||
<li><code>!hn new</code> - Newest stories</li>
|
|
||||||
<li><code>!hn best</code> - Best stories</li>
|
|
||||||
<li><code>!hn ask</code> - Ask HN threads</li>
|
|
||||||
<li><code>!hn show</code> - Show HN posts</li>
|
|
||||||
<li><code>!hn job</code> - Job postings</li>
|
|
||||||
<li><code>!hn story <id></code> - Get details of a specific story</li>
|
|
||||||
<li><code>!hn comments <id></code> - Show comments for a story</li>
|
|
||||||
<li><code>!hn search <query></code> - Search stories (via Algolia)</li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>☯ <strong>!karma [user]</strong></summary>
|
|
||||||
<p>Track karma points for users with leaderboards and statistics. Supports display names and Matrix IDs.</p>
|
|
||||||
<p><strong>Commands:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!karma <user></code> - Show karma for a user</li>
|
|
||||||
<li><code>!karma++ <user></code> - Give +1 karma to a user</li>
|
|
||||||
<li><code>!karma-- <user></code> - Give -1 karma to a user</li>
|
|
||||||
<li><code>!karma top [n]</code> - Show top karma entries</li>
|
|
||||||
<li><code>!karma bottom [n]</code> - Show bottom karma entries</li>
|
|
||||||
<li><code>!karma rank <user></code> - Show rank of user</li>
|
|
||||||
<li><code>!karma stats</code> - Show overall statistics</li>
|
|
||||||
<li><code>!karma history <user></code> - Show recent karma history</li>
|
|
||||||
<li><code>!++ <user></code> - Shortcut for !karma++</li>
|
|
||||||
<li><code>!-- <user></code> - Shortcut for !karma--</li>
|
|
||||||
<li><code><user>++</code> - Inline karma (message contains ++)</li>
|
|
||||||
<li><code><user>--</code> - Inline karma (message contains --)</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Features:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li>Supports display names and Matrix IDs</li>
|
|
||||||
<li>Room-specific karma tracking</li>
|
|
||||||
<li>Rate limiting to prevent spam</li>
|
|
||||||
<li>Karma history tracking</li>
|
|
||||||
<li>Leaderboards and statistics</li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🔥 <strong>!hn [command]</strong></summary>
|
|
||||||
<p>Fetch top stories from Hacker News using Firebase API. No API key required.</p>
|
|
||||||
<p><strong>Commands:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><code>!hn</code> - Show top 5 stories (default)</li>
|
|
||||||
<li><code>!hn top</code> - Top stories</li>
|
|
||||||
<li><code>!hn new</code> - Newest stories</li>
|
|
||||||
<li><code>!hn best</code> - Best stories</li>
|
|
||||||
<li><code>!hn ask</code> - Ask HN threads</li>
|
|
||||||
<li><code>!hn show</code> - Show HN posts</li>
|
|
||||||
<li><code>!hn job</code> - Job postings</li>
|
|
||||||
<li><code>!hn story <id></code> - Get details of a specific story</li>
|
|
||||||
<li><code>!hn comments <id></code> - Show comments for a story</li>
|
|
||||||
<li><code>!hn search <query></code> - Search stories (via Algolia)</li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>⏱️ <strong>!cron [add|remove] [room_id] [cron_entry] [command]</strong></summary>
|
|
||||||
<p>Schedule automated commands using cron syntax. Add or remove cron jobs for specific rooms and commands.</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details><summary>🔧 <strong>Admin Commands</strong></summary>
|
|
||||||
<p>
|
|
||||||
<strong>!set [option] [value]</strong> - Set configuration options (admin_user, prefix)<br>
|
|
||||||
<strong>!get [option]</strong> - Get configuration values<br>
|
|
||||||
<strong>!saveconf</strong> - Save current configuration<br>
|
|
||||||
<strong>!loadconf</strong> - Load saved configuration<br>
|
|
||||||
<strong>!show</strong> - Display current configuration<br>
|
|
||||||
<strong>!reset</strong> - Reset configuration to defaults<br>
|
|
||||||
<strong>!load [plugin]</strong> - Load a plugin<br>
|
|
||||||
<strong>!unload [plugin]</strong> - Unload a plugin<br>
|
|
||||||
<strong>!reload</strong> - Reload all plugins<br>
|
|
||||||
<strong>!disable [plugin] [room_id]</strong> - Disable a plugin for specific room<br>
|
|
||||||
<strong>!enable [plugin] [room_id]</strong> - Enable a plugin for specific room<br>
|
|
||||||
<strong>!rehash</strong> - Reload configuration<br>
|
|
||||||
<em>Note: Admin commands require admin_user privileges</em>
|
|
||||||
</p>
|
|
||||||
</details>
|
|
||||||
</p>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<!--<details><summary><strong>🤖 Funguy Bot AI Commands</strong></summary>
|
|
||||||
<p>
|
|
||||||
<strong>Creative & Writing</strong>: !write, !script, !author, !poem, !rap, !story, !comic, !motiv, !debate, !crit, !litcrit<br>
|
|
||||||
<strong>Technical</strong>: !tech, !dev, !py, !php, !regex, !math, !web, !it, !security, !ai, !ml, !data, !game, !gaming<br>
|
|
||||||
<strong>Professional</strong>: !seo, !recruit, !coach, !devrel, !sales, !ceo, !mgmt, !startup, !invest, !fin, !acad<br>
|
|
||||||
<strong>Educational</strong>: !tutor, !teach, !edu, !hist, !astro, !chem, !psych, !meditate, !socrat, !philos<br>
|
|
||||||
<strong>Lifestyle</strong>: !fit, !health, !diet, !cook, !travel, !art, !music, !film, !selfhelp<br>
|
|
||||||
<strong>Specialized</strong>: !legal, !medical, !realest, !auto, !fashion, !design, !interior, !florist<br>
|
|
||||||
<strong>Communication</strong>: !pron, !spk, !speak, !eloc, !comm, !msg, !langdet<br>
|
|
||||||
<strong>Business</strong>: !eth, !browse, !search, !create, !review, !curation, !domain<br>
|
|
||||||
<strong>Entertainment</strong>: !char, !adv, !advgame, !esc, !title, !stats, !prompt<br>
|
|
||||||
<strong>Technical Specialties</strong>: !intv, !plag, !trv, !foot, !rel, !etymo, !magic, !counsel, !behavior, !mh, !log, !dental, !acc, !chef, !tea, !telemed, !law, !trans, !chess, !time, !dream, !r, !emergency, !worksheet, !test, !create, !guide, !diag, !therapy, !gen, !drunk, !rec, !techtrans, !proof, !spirit, !friend, !chat, !wiki, !kanji, !note, !enhance, !nav, !hypno, !critic, !comp, !journo, !pscoach, !makeup, !childcare, !writing, !syn, !shop, !dining<br>
|
|
||||||
|
|
||||||
<em>Each AI command uses specialized prompts optimized for different domains and interfaces with local AI models. Consult ai.json</em>
|
|
||||||
</p>
|
|
||||||
</details>-->
|
|
||||||
|
|
||||||
<details><summary>🌟 <strong>Funguy Bot Credits</strong></summary>
|
|
||||||
<p>
|
<p>
|
||||||
<strong>🧙♂️ Creator & Developer</strong>: HB is the author of 🍄Funguy Bot🍄. (@hashborgir:mozilla.org)<br>
|
<strong>🧙♂️ Creator & Developer</strong>: HB is the author of 🍄Funguy Bot🍄. (@hashborgir:mozilla.org)<br>
|
||||||
<strong>🚀 Development Context</strong>: Created during recovery from two-level cervical spinal surgery (CDA Cervical Discectomy and Disc Arthroplasty)<br>
|
<strong>🚀 Development Context</strong>: Created during recovery from two-level cervical spinal surgery (CDA Cervical Discectomy and Disc Arthroplasty)<br>
|
||||||
<br>
|
<br>
|
||||||
<strong>Join our Matrix Room</strong>: <a href="https://matrix.to/#/#selfhosting:mozilla.org">Self-hosting | Security | Sysadmin | Homelab | Programming</a>
|
<strong>Join our Matrix Room</strong>: <a href="https://matrix.to/#/#selfhosting:mozilla.org">Self‑hosting | Security | Sysadmin | Homelab | Programming</a>
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
|
help_parts.append(credits)
|
||||||
|
|
||||||
await bot.api.send_markdown_message(room.room_id, commands_message)
|
master = "<details><summary><strong>🍄 Funguy Bot Commands</strong> (click to expand)</summary><br>"
|
||||||
logging.info("Sent help documentation to the room")
|
master += "\n".join(help_parts)
|
||||||
|
master += "</details>"
|
||||||
|
|
||||||
|
await bot.api.send_markdown_message(room.room_id, master)
|
||||||
|
logging.info("Sent dynamic help from %d plugins", len(plugins))
|
||||||
|
|||||||
@@ -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)
|
await bot.api.send_image_message(room_id=room.room_id, image_filepath=poster_path)
|
||||||
finally:
|
finally:
|
||||||
os.unlink(poster_path)
|
os.unlink(poster_path)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "IMDb lookup via OMDb API"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!imdb</strong> – Movie/series details via OMDb</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!imdb <title></code> – Full details (poster sent separately)</li>
|
||||||
|
<li><code>!imdb id <tt1234567></code> – Lookup by IMDb ID</li>
|
||||||
|
<li><code>!imdb search <query></code> – Search titles</li>
|
||||||
|
<li><code>!imdb episode <series> -s N -e N</code> – Episode info</li>
|
||||||
|
</ul>
|
||||||
|
<p>Optional flags: <code>-y year</code>, <code>-t movie|series|episode</code>, <code>--short-plot</code><br>
|
||||||
|
Requires <strong>OMDB_API_KEY</strong> env var.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -231,3 +231,25 @@ async def generate_text(room, bot, prompt, model, temperature, max_tokens):
|
|||||||
if not command_queue.empty():
|
if not command_queue.empty():
|
||||||
next_command = await command_queue.get()
|
next_command = await command_queue.get()
|
||||||
await handle_command(*next_command)
|
await handle_command(*next_command)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "AI text generation via Infermatic API"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!text</strong> – AI text generation (Infermatic)</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!text <prompt></code> – Generate text using default model</li>
|
||||||
|
<li><code>!text --list-models</code> – List available models</li>
|
||||||
|
<li><code>!text --use-model <model> <prompt></code> – Specific model</li>
|
||||||
|
<li><code>--temperature <0.0-1.0></code> – Set creativity (default 0.9)</li>
|
||||||
|
<li><code>--max-tokens <number></code> – Max output length (default 2048)</li>
|
||||||
|
</ul>
|
||||||
|
<p>Requires <strong>INFERMATIC_API</strong> env var.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -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")
|
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")
|
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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!isup</strong> – Is it up?</summary>
|
||||||
|
<p><code>!isup <domain or IP></code> – Performs DNS resolution and checks HTTP/HTTPS availability.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
+36
-3
@@ -54,6 +54,14 @@ display_name_cache = {}
|
|||||||
cache_timestamp = {}
|
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
|
# 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"
|
response = f"🏆 **Top {len(leaderboard)} Karma Leaders**\n\n"
|
||||||
for i, entry in enumerate(leaderboard, 1):
|
for i, entry in enumerate(leaderboard, 1):
|
||||||
medal = "🥇" if i == 1 else "🥈" if i == 2 else "🥉" if i == 3 else "📌"
|
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)
|
await bot.api.send_markdown_message(room.room_id, response)
|
||||||
else:
|
else:
|
||||||
await bot.api.send_markdown_message(room.room_id, "No karma entries found in this room.")
|
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:
|
if leaderboard:
|
||||||
response = f"📉 **Bottom {len(leaderboard)} Karma (Needs Love)**\n\n"
|
response = f"📉 **Bottom {len(leaderboard)} Karma (Needs Love)**\n\n"
|
||||||
for i, entry in enumerate(leaderboard, 1):
|
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)
|
await bot.api.send_markdown_message(room.room_id, response)
|
||||||
else:
|
else:
|
||||||
await bot.api.send_markdown_message(room.room_id, "No karma entries found in this room.")
|
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)
|
points = get_karma(room_id, user_id)
|
||||||
percentile = round((1 - rank/total) * 100, 1) if total > 0 else 0
|
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)
|
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)
|
await bot.api.send_markdown_message(room.room_id, response)
|
||||||
else:
|
else:
|
||||||
display_name_resolved = get_display_name_from_user_id(bot, room_id, user_id)
|
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("Supports display names (e.g., 'Nexilva' or '🍄 HB🍄') and Matrix IDs")
|
||||||
logging.info("Commands: !karma, !karma++, !karma--, !++, !--, and inline ++/--")
|
logging.info("Commands: !karma, !karma++, !karma--, !++, !--, and inline ++/--")
|
||||||
logging.info("=" * 50)
|
logging.info("=" * 50)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "Room karma tracking system"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!karma</strong> – Karma system</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!karma <user></code> – Show points</li>
|
||||||
|
<li><code>!karma++ <user></code> / <code>!-- <user></code> – Modify karma</li>
|
||||||
|
<li><code>!karma top [n]</code> / <code>!karma bottom [n]</code> – Leaderboard</li>
|
||||||
|
<li><code>!karma rank <user></code> – Position</li>
|
||||||
|
<li><code>!karma stats</code> – Room statistics</li>
|
||||||
|
<li><code>!karma history <user></code> – Recent votes</li>
|
||||||
|
</ul>
|
||||||
|
<p>Shortcuts: <code>!++ user</code>, <code>!-- user</code>, and inline <code>user++</code> / <code>user--</code>.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -2019,3 +2019,31 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
await bot.api.send_text_message(
|
await bot.api.send_text_message(
|
||||||
room.room_id, f"❌ Error processing !{command}: {str(e)}"
|
room.room_id, f"❌ Error processing !{command}: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "Last.fm integration"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!lastfm</strong> – Last.fm music stats (30+ commands)</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!register <username></code> – Connect account</li>
|
||||||
|
<li><code>!np [user]</code> – Now playing</li>
|
||||||
|
<li><code>!recent [user] [limit]</code> – Recent tracks</li>
|
||||||
|
<li><code>!toptracks</code>, <code>!topartists</code>, <code>!topalbums</code></li>
|
||||||
|
<li><code>!loved</code>, <code>!profile</code>, <code>!playcount</code>, <code>!scrobbles</code></li>
|
||||||
|
<li><code>!compare <user1> <user2></code> – Taste comparison</li>
|
||||||
|
<li><code>!recommend</code>, <code>!similar <artist></code>, <code>!tag <genre></code></li>
|
||||||
|
<li><code>!charts</code>, <code>!now</code>, <code>!decades</code>, <code>!genres</code>, <code>!tagcloud</code></li>
|
||||||
|
<li><code>!era <year></code>, <code>!weekly</code>, <code>!monthly</code>, <code>!yearly</code></li>
|
||||||
|
<li><code>!first <artist></code>, <code>!concerts</code>, <code>!radio <artist></code></li>
|
||||||
|
<li><code>!collage [user] [size]</code>, <code>!listening</code>, <code>!awards</code></li>
|
||||||
|
</ul>
|
||||||
|
<p>For full details: <code>!lastfm</code><br>Requires <strong>LASTFM_API_KEY</strong> env var.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -113,3 +113,16 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
# Send unauthorized message if the sender is not the admin
|
# 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.")
|
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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>Admin: !load / !unload</strong></summary>
|
||||||
|
<p><code>!load <plugin></code> / <code>!unload <plugin></code> – Dynamically load or unload a plugin module. Admin only.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
+12
-1
@@ -231,4 +231,15 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
|
|
||||||
__version__ = "1.0.0"
|
__version__ = "1.0.0"
|
||||||
__author__ = "Funguy Bot"
|
__author__ = "Funguy Bot"
|
||||||
__description__ = "News aggregator using GNews API"
|
__description__ = "News headlines via GNews API"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!news</strong> – Latest news headlines</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!news [top|world|tech|business|science|health|sports|crypto]</code></li>
|
||||||
|
<li><code>!news search <query></code></li>
|
||||||
|
<li>You can append a number: <code>!news tech 8</code></li>
|
||||||
|
</ul>
|
||||||
|
<p>Requires <strong>GNEWS_API_KEY</strong> env var.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
+22
-22
@@ -10,49 +10,49 @@ import logging
|
|||||||
import simplematrixbotlib as botlib
|
import simplematrixbotlib as botlib
|
||||||
|
|
||||||
async def handle_command(room, message, bot, prefix, config):
|
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)
|
match = botlib.MessageMatch(room, message, bot, prefix)
|
||||||
if match.is_not_from_this_bot() and match.prefix() and match.command("plugins"):
|
if match.is_not_from_this_bot() and match.prefix() and match.command("plugins"):
|
||||||
logging.info("Received !plugins command")
|
logging.info("Received !plugins command")
|
||||||
plugin_descriptions = get_plugin_descriptions()
|
plugin_descriptions = get_plugin_descriptions()
|
||||||
|
|
||||||
# Prepend custom string before the output
|
|
||||||
plugin_descriptions.insert(0, "<details><summary><strong>🔌Plugins List🔌<br>⤵︎Click Here to Expand⤵︎</strong></summary>")
|
plugin_descriptions.insert(0, "<details><summary><strong>🔌Plugins List🔌<br>⤵︎Click Here to Expand⤵︎</strong></summary>")
|
||||||
|
|
||||||
plugins_message = "<br>".join(plugin_descriptions)
|
plugins_message = "<br>".join(plugin_descriptions)
|
||||||
plugins_message += "</details>"
|
plugins_message += "</details>"
|
||||||
|
|
||||||
await bot.api.send_markdown_message(room.room_id, plugins_message)
|
await bot.api.send_markdown_message(room.room_id, plugins_message)
|
||||||
logging.info("Sent plugin list to the room")
|
logging.info("Sent plugin list to the room")
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_descriptions():
|
def get_plugin_descriptions():
|
||||||
"""
|
|
||||||
Function to get descriptions of all loaded plugin modules.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list: A list of plugin descriptions sorted alphabetically.
|
|
||||||
"""
|
|
||||||
plugin_descriptions = []
|
plugin_descriptions = []
|
||||||
for module_name, module in sys.modules.items():
|
for module_name, module in sys.modules.items():
|
||||||
if module_name.startswith("plugins.") and hasattr(module, "__file__"):
|
if module_name.startswith("plugins.") and hasattr(module, "__file__"):
|
||||||
plugin_path = module.__file__
|
plugin_path = module.__file__
|
||||||
plugin_name = os.path.basename(plugin_path).split(".")[0]
|
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]
|
description = module.__doc__.strip().split("\n")[0]
|
||||||
except AttributeError:
|
else:
|
||||||
description = "No description available"
|
description = "No description available"
|
||||||
|
|
||||||
plugin_descriptions.append(f"<strong>[{plugin_name}.py]:</strong> {description}")
|
plugin_descriptions.append(f"<strong>[{plugin_name}.py]:</strong> {description}")
|
||||||
|
|
||||||
# Sort the plugin descriptions alphabetically by plugin name
|
|
||||||
plugin_descriptions.sort()
|
plugin_descriptions.sort()
|
||||||
|
|
||||||
return plugin_descriptions
|
return plugin_descriptions
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "List all loaded plugins"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!plugins</strong> – List loaded plugins</summary>
|
||||||
|
<p>Displays all currently loaded plugins and their descriptions.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -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")
|
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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!proxy</strong> – Random working SOCKS5 proxy</summary>
|
||||||
|
<p>Fetches, tests, and returns a random working SOCKS5 proxy with latency. Caches good proxies in SQLite.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -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}")
|
message = error_messages.get(status_code, f"Shodan API error: {status_code}")
|
||||||
await bot.api.send_text_message(room.room_id, message)
|
await bot.api.send_text_message(room.room_id, message)
|
||||||
logging.error(f"Shodan API error: {status_code}")
|
logging.error(f"Shodan API error: {status_code}")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "Shodan.io reconnaissance"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!shodan</strong> – Shodan search</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!shodan ip <ip></code> – IP info with open ports</li>
|
||||||
|
<li><code>!shodan search <query></code> – Search internet devices</li>
|
||||||
|
<li><code>!shodan host <domain></code> – Host & subdomain enumeration</li>
|
||||||
|
<li><code>!shodan count <query></code> – Result counts</li>
|
||||||
|
</ul>
|
||||||
|
<p>Requires <strong>SHODAN_KEY</strong> env var.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -592,3 +592,19 @@ def format_cert_date(date_str):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return date_str
|
return date_str
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "SSL/TLS security scanner"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!sslscan</strong> – SSL/TLS analysis</summary>
|
||||||
|
<p><code>!sslscan <domain[:port]></code> – Tests protocols, cipher suites, certificate validity, vulnerabilities.<br>
|
||||||
|
Provides a security score (0-100) and actionable recommendations.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -185,3 +185,26 @@ def print_help():
|
|||||||
<li><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</li>
|
<li><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</li>
|
||||||
</ul>
|
</ul>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "Stable Diffusion image generation"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!sd</strong> – Generate images via Stable Diffusion</summary>
|
||||||
|
<p><code>!sd [options] <prompt></code></p>
|
||||||
|
<ul>
|
||||||
|
<li><code>--steps N</code> – Sampling steps (default 4)</li>
|
||||||
|
<li><code>--cfg scale</code> – CFG scale (default 2)</li>
|
||||||
|
<li><code>--h H --w W</code> – Image dimensions (default 512)</li>
|
||||||
|
<li><code>--neg <negative prompt></code></li>
|
||||||
|
<li><code>--sampler SAMPLER</code> – Sampler name (default DPM++ SDE)</li>
|
||||||
|
</ul>
|
||||||
|
<p>Requires a locally running Stable Diffusion API.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -125,3 +125,17 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
f"An error occurred during subdomain enumeration for {domain}. Please try again later."
|
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)
|
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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!subdomains</strong> – Enumerate subdomains</summary>
|
||||||
|
<p><code>!subdomains <domain></code> – Finds subdomains using SSL certificate transparency logs (CertSpotter API).</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -440,3 +440,16 @@ async def format_system_info(sysinfo):
|
|||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "System information and monitoring"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!sysinfo</strong> – System information</summary>
|
||||||
|
<p>Displays CPU, RAM, storage, network, Docker, GPU, sensors, and top processes.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -190,3 +190,21 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
return
|
return
|
||||||
await bot.api.send_markdown_message(room.room_id, format_response(data, display))
|
await bot.api.send_markdown_message(room.room_id, format_response(data, display))
|
||||||
logging.info(f"Time sent for {query}")
|
logging.info(f"Time sent for {query}")
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "World clock (no hardcoded cities)"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!time</strong> – Current time for any city</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!time <city></code> – Geocode any city (free Open-Meteo API)</li>
|
||||||
|
<li><code>!time <IANA zone></code> – e.g., <code>Europe/London</code></li>
|
||||||
|
</ul>
|
||||||
|
<p>Also shows current temperature if available.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -191,3 +191,22 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
"An error occurred while processing the Urban Dictionary request."
|
"An error occurred while processing the Urban Dictionary request."
|
||||||
)
|
)
|
||||||
logging.error(f"Unexpected error in Urban Dictionary plugin: {e}", exc_info=True)
|
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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!ud</strong> – Urban Dictionary</summary>
|
||||||
|
<ul>
|
||||||
|
<li><code>!ud</code> – Random definition</li>
|
||||||
|
<li><code>!ud <term></code> – Top definition</li>
|
||||||
|
<li><code>!ud <term> <index></code> – Nth definition</li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
+235
-123
@@ -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 <location> e.g. !weather London or !weather "New York,US"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import requests
|
|
||||||
import os
|
import os
|
||||||
|
import aiohttp
|
||||||
import simplematrixbotlib as botlib
|
import simplematrixbotlib as botlib
|
||||||
from dotenv import load_dotenv
|
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__))
|
plugin_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
# Get the parent directory (main bot directory)
|
|
||||||
parent_dir = os.path.dirname(plugin_dir)
|
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)
|
load_dotenv(dotenv_path)
|
||||||
|
|
||||||
# OpenWeatherMap API configuration
|
OPENWEATHER_API_KEY = os.getenv("OPENWEATHER_API_KEY", "")
|
||||||
# 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"
|
|
||||||
|
|
||||||
async def handle_command(room, message, bot, prefix, config):
|
# ---------------------------------------------------------------------------
|
||||||
"""
|
# WMO codes → description + emoji (for Open‑Meteo)
|
||||||
Function to handle the !weather command.
|
# ---------------------------------------------------------------------------
|
||||||
|
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.
|
# Primary: OpenWeatherMap
|
||||||
message (RoomMessage): The message object containing the command.
|
# ---------------------------------------------------------------------------
|
||||||
bot (Bot): The bot object.
|
async def openweathermap_get(session: aiohttp.ClientSession, location: str) -> dict | None:
|
||||||
prefix (str): The command prefix.
|
"""Fetch current weather from OpenWeatherMap. Returns None on failure."""
|
||||||
config (dict): Configuration parameters.
|
if not OPENWEATHER_API_KEY:
|
||||||
|
logging.info("OpenWeatherMap key missing, skipping primary")
|
||||||
|
return None
|
||||||
|
|
||||||
Returns:
|
url = "https://api.openweathermap.org/data/2.5/weather"
|
||||||
None
|
params = {
|
||||||
"""
|
"q": location,
|
||||||
match = botlib.MessageMatch(room, message, bot, prefix)
|
"appid": OPENWEATHER_API_KEY,
|
||||||
if match.is_not_from_this_bot() and match.prefix() and match.command("weather"):
|
"units": "metric", # Celsius
|
||||||
logging.info("Received !weather command")
|
}
|
||||||
|
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
|
||||||
|
|
||||||
# Check if API key is configured
|
|
||||||
if not WEATHER_API_KEY:
|
def format_openweathermap(data: dict) -> str:
|
||||||
await bot.api.send_text_message(
|
"""Build the one-line weather message from OpenWeatherMap data."""
|
||||||
room.room_id,
|
city = data.get("name", "Unknown")
|
||||||
"Weather API key not configured. Please set OPENWEATHER_API_KEY environment variable."
|
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"<strong>[{emoji} Weather for {city}, {country}]</strong>: "
|
||||||
|
f"<strong>Condition:</strong> {description} | "
|
||||||
|
f"<strong>Temperature:</strong> {temp_c:.1f}°C ({temp_f:.1f}°F) | "
|
||||||
|
f"<strong>Humidity:</strong> {humidity}% | "
|
||||||
|
f"<strong>Wind Speed:</strong> {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"<strong>[{emoji} Weather for {loc_str}]</strong>: "
|
||||||
|
f"<strong>Condition:</strong> {desc} | "
|
||||||
|
f"<strong>Temperature:</strong> {temp_c}°C ({temp_f}°F) | "
|
||||||
|
f"<strong>Wind Speed:</strong> {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
|
return
|
||||||
|
|
||||||
args = match.args()
|
args = match.args()
|
||||||
|
if not args:
|
||||||
# Check if location was provided
|
|
||||||
if len(args) < 1:
|
|
||||||
await bot.api.send_text_message(
|
await bot.api.send_text_message(
|
||||||
room.room_id,
|
room.room_id,
|
||||||
"Usage: !weather <location>\nExample: !weather London or !weather New York,US"
|
"Usage: !weather <location>\nExample: !weather London or !weather New York,US"
|
||||||
)
|
)
|
||||||
logging.info("Sent usage message for !weather")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
location = ' '.join(args)
|
location = " ".join(args)
|
||||||
|
logging.info("Received !weather command for '%s'", location)
|
||||||
|
|
||||||
try:
|
async with aiohttp.ClientSession() as session:
|
||||||
# Make API request to OpenWeatherMap
|
# 1. Try OpenWeatherMap
|
||||||
params = {
|
owm_data = await openweathermap_get(session, location)
|
||||||
'q': location,
|
if owm_data:
|
||||||
'appid': WEATHER_API_KEY,
|
if owm_data.get("cod") == 200:
|
||||||
'units': 'metric' # Use metric units (Celsius)
|
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"))
|
||||||
|
|
||||||
response = requests.get(WEATHER_API_URL, params=params, timeout=10)
|
# 2. Fallback: Open‑Meteo
|
||||||
response.raise_for_status()
|
logging.info("Falling back to Open‑Meteo")
|
||||||
|
loc_info = await meteo_geocode(session, location)
|
||||||
weather_data = response.json()
|
if not loc_info:
|
||||||
|
|
||||||
# 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"""
|
|
||||||
<strong>[{weather_emoji} Weather for {city_name}, {country}]</strong>: <strong>Condition:</strong> {description} | <strong>Temperature:</strong> {temp:.1f}°C ({temp_f:.1f}°F) | <strong>Feels like:</strong> {feels_like:.1f}°C ({feels_like_f:.1f}°F) | <strong>Humidity:</strong> {humidity}% | <strong>Wind Speed:</strong> {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(
|
await bot.api.send_text_message(
|
||||||
room.room_id,
|
room.room_id,
|
||||||
f"Location '{location}' not found. Please check the spelling and try again."
|
f"Location '{location}' not found."
|
||||||
)
|
)
|
||||||
elif e.response.status_code == 401:
|
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(
|
await bot.api.send_text_message(
|
||||||
room.room_id,
|
room.room_id,
|
||||||
"Weather API authentication failed. Please check the API key configuration."
|
"Could not fetch weather data from any provider."
|
||||||
)
|
)
|
||||||
else:
|
return
|
||||||
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:
|
msg = format_meteo(loc_info, wdata)
|
||||||
await bot.api.send_text_message(
|
await bot.api.send_markdown_message(room.room_id, msg)
|
||||||
room.room_id,
|
logging.info("Sent weather via Open‑Meteo (fallback)")
|
||||||
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):
|
# ---------------------------------------------------------------------------
|
||||||
"""
|
# Plugin setup
|
||||||
Get an emoji based on weather condition.
|
# ---------------------------------------------------------------------------
|
||||||
|
def setup(bot):
|
||||||
|
logging.info("Weather plugin loaded (OpenWeatherMap + Open‑Meteo fallback)")
|
||||||
|
|
||||||
Args:
|
|
||||||
condition (str): Weather condition from API.
|
|
||||||
|
|
||||||
Returns:
|
# ---------------------------------------------------------------------------
|
||||||
str: Weather emoji.
|
# Plugin Metadata
|
||||||
"""
|
# ---------------------------------------------------------------------------
|
||||||
weather_emojis = {
|
__version__ = "1.0.0"
|
||||||
'Clear': '☀️',
|
__author__ = "Funguy Bot"
|
||||||
'Clouds': '☁️',
|
__description__ = "Weather forecast (OWM primary, Open‑Meteo fallback)"
|
||||||
'Rain': '🌧️',
|
__help__ = """
|
||||||
'Drizzle': '🌦️',
|
<details>
|
||||||
'Thunderstorm': '⛈️',
|
<summary><strong>!weather</strong> – Current weather</summary>
|
||||||
'Snow': '❄️',
|
<p><code>!weather <location></code> – Shows temperature, conditions, humidity, wind.<br>
|
||||||
'Mist': '🌫️',
|
Uses OpenWeatherMap if a valid API key is present; falls back to free Open‑Meteo otherwise.</p>
|
||||||
'Fog': '🌫️',
|
</details>
|
||||||
'Haze': '🌫️',
|
"""
|
||||||
'Smoke': '🌫️',
|
|
||||||
'Dust': '🌫️',
|
|
||||||
'Sand': '🌫️',
|
|
||||||
'Ash': '🌫️',
|
|
||||||
'Squall': '💨',
|
|
||||||
'Tornado': '🌪️'
|
|
||||||
}
|
|
||||||
|
|
||||||
return weather_emojis.get(condition, '🌡️')
|
|
||||||
|
|||||||
@@ -168,3 +168,17 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
room.room_id, _build_welcome_message(display_name)
|
room.room_id, _build_welcome_message(display_name)
|
||||||
)
|
)
|
||||||
logging.info("Sent manual !welcome to %s", sender)
|
logging.info("Sent manual !welcome to %s", sender)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "Room welcome message"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!welcome</strong> – Receive welcome message</summary>
|
||||||
|
<p>Manually triggers the room's welcome message for yourself.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -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."
|
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)
|
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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!whois</strong> – WHOIS lookup</summary>
|
||||||
|
<p><code>!whois <domain or IP></code> – Shows registrar, creation/expiry dates, nameservers, contacts.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -280,3 +280,19 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
os.unlink(image_path)
|
os.unlink(image_path)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "Wikipedia article summary"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!wp</strong> – Wikipedia summary</summary>
|
||||||
|
<p><code>!wp <search term></code> – Returns the lead section and main image from Wikipedia.<br>
|
||||||
|
Uses MediaWiki APIs, no scraping.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -43,3 +43,18 @@ async def handle_command(room, message, bot, prefix, config):
|
|||||||
os.remove(image_path)
|
os.remove(image_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await bot.api.send_text_message(room.room_id, f"Error fetching XKCD comic: {str(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__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!xkcd</strong> – Random XKCD comic</summary>
|
||||||
|
<p>Posts a random XKCD comic image.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
@@ -71,3 +71,18 @@ async def send_collapsible_message(room, bot, content):
|
|||||||
"""
|
"""
|
||||||
message = f'<details><summary><strong>🍄Funguy ▶YouTube Search🍄<br>⤵︎Click Here To See Results⤵︎</strong></summary>{content}</details>'
|
message = f'<details><summary><strong>🍄Funguy ▶YouTube Search🍄<br>⤵︎Click Here To See Results⤵︎</strong></summary>{content}</details>'
|
||||||
await bot.api.send_markdown_message(room.room_id, message)
|
await bot.api.send_markdown_message(room.room_id, message)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Plugin Metadata
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
__version__ = "1.0.0"
|
||||||
|
__author__ = "Funguy Bot"
|
||||||
|
__description__ = "YouTube video search"
|
||||||
|
__help__ = """
|
||||||
|
<details>
|
||||||
|
<summary><strong>!yt</strong> – Search YouTube</summary>
|
||||||
|
<p><code>!yt <search terms></code> – Returns top 3 results with thumbnails and descriptions.</p>
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user