🍄 Funguy Bot
A modular, security-focused Matrix chat bot built with simplematrixbotlib, written in Python. Features a plugin architecture, in-process cron scheduling, per-room plugin management, rate limiting, and a full suite of recon, encoding, and utility commands.
Written during recovery from cervical spinal surgery (CDA – Cervical Discectomy and Disc Arthroplasty). Expect bugs — please report them!
📋 Table of Contents
- Features
- Requirements
- Installation
- Configuration
- Running the Bot
- Core Admin Commands
- Plugin Reference
- Rate Limiting
- Security Notes
- Support
- Credits
✨ Features
- Modular plugin system – each command lives in its own
plugins/*.pyfile; add or remove features without touching core code - Runtime plugin management – load, unload, enable, or disable plugins per-room with no restart required
- In-process cron scheduler – schedule any bot command to fire automatically (APScheduler + SQLite, no system crontab needed)
- Rate limiting – non-admin users are capped at 3 commands per 5-second window; admins are always exempt
- SSRF protection – all outbound network plugins validate that targets resolve to public IPs only
- Admin/moderator access control – a dedicated
admin_useris set in config; moderation commands require power level ≥ 50 - Live configuration – settings can be changed at runtime with
!set/!saveconfwithout restarting
📦 Requirements
- Python 3.9+
- A Matrix homeserver account for the bot
fortunepackage installed on the host (sudo apt install fortune) if usingfortune.pydict/ WordNet installed on the host (sudo apt install dict dict-wn) if usingdictionary.py- Optional API keys for certain plugins (see Configuration)
🚀 Installation
1. Clone the repository
git clone https://gitlab.com/Eggzy/funguybot.git
cd funguybot
2. Create and activate a Python virtual environment
python3 -m venv venv
source venv/bin/activate
3. Install dependencies
pip3 install -r requirements.txt
4. (Optional) Install Playwright for the Goodreads quote plugin (quote.py)
pip3 install playwright beautifulsoup4 lxml
playwright install chromium
5. Configure the bot — create your .env and funguy.conf files (see Configuration)
6. Set up and start the systemd service (see Running the Bot)
⚙️ Configuration
Environment Variables (.env)
Create a .env file in the bot's root directory. Only the three Matrix variables are required; all API keys are optional.
# ── Required ──────────────────────────────────────────────────────────
MATRIX_URL="https://matrix.org" # Your homeserver URL
MATRIX_USER="@yourbot:matrix.org" # Bot's Matrix ID
MATRIX_PASS="your_password" # Bot's password
# ── Logging (optional, default: INFO) ─────────────────────────────────
LOG_LEVEL=INFO # DEBUG | INFO | WARNING | ERROR | CRITICAL
# ── Plugin API Keys (all optional) ────────────────────────────────────
OPENWEATHER_API_KEY= # weather.py
SHODAN_KEY= # shodan.py
DNSDUMPSTER_KEY= # dnsdumpster.py
LASTFM_API_KEY= # lastfm.py
YOUTUBE_API_KEY= # youtube-search.py
INFERMATIC_API= # infermatic-text.py
INFERMATIC_MODEL= # infermatic-text.py – model name string
OMDB_API_KEY= # imdb.py
GNEWS_API_KEY= # news.py
# ── SMTP (optional, for notification plugins) ─────────────────────────
SMTP_SERVER=mail.example.com
SMTP_PORT=465
SMTP_USER=bot@example.com
SMTP_PASSWORD=yourpassword
Bot Configuration (funguy.conf)
Create funguy.conf in the bot's root directory. This is a TOML file consumed by simplematrixbotlib and the config.py plugin.
[simplematrixbotlib.config]
admin_user = "@youradmin:matrix.org" # Full Matrix ID of the admin
prefix = "!" # Single-character command prefix
join_on_invite = true
encryption_enabled = false
emoji_verify = false
ignore_unverified_devices = true
store_path = "./store/"
# Pre-disable plugins per room (optional)
[plugins.disabled]
# "!yourroom:matrix.org" = ["pluginname1", "pluginname2"]
All writable options can also be changed live by the admin using !set (see config.py).
🤖 Running the Bot
Systemd Service (Recommended)
Create /etc/systemd/system/funguybot.service, replacing the $variables with your actual paths and user:
[Unit]
Description=Funguy Bot Service
After=network.target
[Service]
Type=simple
User=$user
Group=$group
WorkingDirectory=$working_directory
ExecStart=$working_directory/start-funguy.sh
Restart=on-failure
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=funguybot
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable funguybot
systemctl start funguybot
Direct Launch
source venv/bin/activate
python3 funguy.py
🔑 Core Admin Commands
These commands are handled by funguy.py itself and require admin_user privileges.
| Command | Description |
|---|---|
!reload |
Reload all plugins from disk (no restart needed) |
!load <plugin> |
Dynamically load a single plugin by filename (without .py) |
!unload <plugin> |
Dynamically unload a single plugin |
!enable <plugin> |
Re-enable a plugin in the current room |
!disable <plugin> |
Disable a plugin in the current room (persisted to funguy.conf) |
!restart |
Gracefully stop the bot process (systemd will restart it automatically) |
!rehash |
Reload funguy.conf and the disabled-plugin list without restarting |
🧩 Plugin Reference
All commands use the prefix defined in funguy.conf (default !). Plugin filenames correspond to the names used with !load / !unload / !disable / !enable.
🛡️ admin.py – Room Moderation
Full moderator toolkit with multi-word display name resolution. Requires power level ≥ 50 or admin_user. The bot automatically resolves multi-word display names and prompts with a numbered disambiguation list when names are ambiguous.
| Command | Description |
|---|---|
!kick <name|@user> [reason] |
Kick a user from the room |
!ban <name|@user> [reason] |
Ban a user |
!unban <@user:domain> |
Unban a user (full MXID required) |
!invite <name|@user> |
Invite a user to the room |
!op <name|@user> [power_level] |
Promote a user (max 50 / moderator level) |
!deop <name|@user> |
Demote a user to power level 0 |
!userinfo <name|@user> |
Show display name and power level |
!topic [new topic] |
View or set the room topic |
!roomname [new name] |
View or set the room name |
!avatar [mxc://…] |
View or set the room avatar (must be an mxc:// URL) |
!members |
List all joined members with power levels |
!bans |
List all banned users |
!modhelp |
Show moderator command reference |
!admin <action> also works as an explicit parent command for all of the above.
📄 arxiv.py – arXiv Academic Search
| Command | Description |
|---|---|
!arxiv <query> |
Search papers with abstracts |
!arxiv list <query> |
Search titles only (no abstracts) |
!arxiv category <cat> |
Browse recent papers by category |
!arxiv recent [category] |
Papers from the last 7 days |
!arxiv random |
Random paper |
!arxiv <id> |
Fetch paper by arXiv ID (e.g. 2101.00101) |
Categories: ai, ml, security, crypto, cv, nlp, math, physics, quantum, bio, software
₿ bitcoin.py – Bitcoin Price
!btc
Fetches the latest BTC/USD price from bitcointicker.co (Bitstamp feed, 60-second intervals). No API key required.
⚙️ config.py – Live Configuration (Admin Only)
Manage bot settings at runtime. Changes are in-memory until you run !saveconf.
| Command | Description |
|---|---|
!set <option> <value> |
Change a configuration option |
!get <option> |
View the current value of one option |
!show |
Display all current configuration values |
!saveconf |
Write current settings to funguy.conf |
!loadconf |
Reload settings from funguy.conf |
!reset |
Reset all options to defaults (preserves admin_user) |
!config help |
Show config command help |
Configurable options: prefix, timeout, join_on_invite, encryption_enabled, emoji_verify, ignore_unverified_devices, store_path, allowlist, blocklist
admin_useris read-only via!set— editfunguy.confdirectly to change it.
🕐 cron.py – Scheduled Commands (Admin Only)
In-process cron scheduler backed by APScheduler and SQLite (cron_jobs.db). Room context is automatically derived from where the command is issued — no room ID needed.
| Command | Description |
|---|---|
!cron add <cron_expr> <command> [tz=IANA] |
Schedule a command in the current room |
!cron remove <job_id> |
Delete a job |
!cron list |
List jobs in the current room |
!cron list * |
List all jobs across all rooms |
!cron enable <job_id> |
Re-enable a paused job |
!cron disable <job_id> |
Pause a job without deleting it |
!cron clear |
Remove all jobs from the current room |
Cron expression format: 5 fields — minute hour day-of-month month day-of-week
!cron add 0 8 * * * !weather London tz=Europe/London
Timezone defaults to UTC. Use any IANA zone name (e.g. tz=America/New_York).
📅 date.py – Date & Time
!date
Displays the current day of the week, ordinal date (e.g. "the 9th"), and 12-hour time. No API key required.
🔍 ddg.py – DuckDuckGo Search
No API key required. Uses the ddgs library.
| Command | Description |
|---|---|
!ddg <query> |
Top web result snippet (collapsible) |
!ddg search <query> |
5 web results |
!ddg image <query> |
3 image results |
!ddg news <query> |
3 news articles |
!ddg video <query> |
3 video results |
!ddg bang <!bang query> |
DuckDuckGo bang redirect |
!ddg define <word> |
Word definition |
!ddg calc <expression> |
Calculator |
!ddg weather [location] |
Weather snippet |
📖 dictionary.py – Word Definitions
!define <word>
Looks up definitions using the system dict command against WordNet (with fallback to all installed dictionaries). Requires the dict and dict-wn packages on the host (sudo apt install dict dict-wn).
🌐 dns.py – DNS Reconnaissance (SSRF-safe)
!dns <domain>
Queries A, AAAA, MX, NS, TXT, CNAME, SOA, SRV, and PTR records and outputs a clean, emoji-aligned table. Private/internal IP targets are blocked.
🗺️ dnsdumpster.py – DNSDumpster Recon
Requires DNSDUMPSTER_KEY in .env.
| Command | Description |
|---|---|
!dnsdumpster <domain> |
Full DNS mapping via DNSDumpster API |
!dnsdumpster test |
Test API connection against google.com |
🔧 encode.py – CyberChef-Style Toolkit
A fully offline, CyberChef-like data manipulation plugin with dozens of operations across encoding, cryptography, compression, data processing, forensics, and networking.
!encode <operation> [arguments] <data>
!encode help <op> # Detailed help for a specific operation
Encoding
| Operation | Example |
|---|---|
base64 encode|decode |
!encode base64 encode Hello World |
base32 encode|decode |
!encode base32 encode Hello |
hex encode|decode |
!encode hex encode Secret |
url encode|decode |
!encode url encode https://example.com/a b |
html encode|decode |
!encode html encode "<script>" |
unicode encode|decode |
!encode unicode encode café |
binary encode|decode |
!encode binary encode Hi |
rot13 |
!encode rot13 Uryyb Jbeyq |
morse encode|decode |
!encode morse encode SOS |
Cryptography
| Operation | Example |
|---|---|
xor <key_hex> |
!encode xor 41 Hello |
aes encrypt|decrypt <key_hex> <iv_hex> |
AES-CBC |
chacha20 encrypt|decrypt <key_hex> <nonce_hex> |
ChaCha20 |
rsa encrypt|decrypt <PEM_key> |
RSA-OAEP |
md5 / sha1 / sha256 / sha512 |
!encode sha256 hello |
sha3-256 / sha3-512 |
!encode sha3-256 hello |
hmac <algo> <key_hex> |
!encode hmac sha256 6b6579 message |
bcrypt hash <rounds> / bcrypt verify |
!encode bcrypt hash 12 mypassword |
argon2 hash <params> / argon2 verify |
PHC format |
pbkdf2 <salt_hex> <iters> <keylen> <algo> |
!encode pbkdf2 aabbccdd 100000 32 sha256 pass |
Compression
gzip, zlib, bzip2, lzma — each supports compress and decompress subcommands.
Data Processing
| Operation | Example |
|---|---|
json format|validate |
!encode json format '{"key":"value"}' |
xml format |
!encode xml format "<root><a>1</a></root>" |
yaml format|tojson |
!encode yaml format "key: value" |
csv |
Parse CSV (first row as header) |
asn1 |
Parse ASN.1 DER (base64 input) |
pemder topem|toder |
PEM ↔ DER conversion |
Forensics
| Operation | Description |
|---|---|
entropy |
Shannon entropy of input |
ioc |
Extract IPs, domains, URLs, emails, hashes |
strings |
Extract ASCII strings (≥4 chars) from hex data |
filemagic |
Detect file type by magic bytes |
base64blob |
Find embedded Base64 blobs in text |
xbrute |
XOR single-byte brute force |
yara |
Scan with a YARA rule (rule as base64) |
peinfo |
PE header analysis (hex input) |
Networking
| Operation | Example |
|---|---|
cidr |
!encode cidr 192.168.1.0/24 |
ipconv hex|dec|binary |
!encode ipconv hex 192.168.1.1 |
urlparse |
!encode urlparse https://user:pass@example.com:8080/path?q=1 |
dns |
!encode dns example.com |
Recipes — chain multiple operations:
!encode recipe list
!encode recipe run '{"steps":[{"op":"base64","args":["encode"]},{"op":"hex","args":["encode"]}]}' "hello world"
💣 exploitdb.py – Exploit-DB Search
!exploitdb <search term> [max_results]
Searches the Exploit-DB CSV export from GitLab. Returns up to 10 results (default 5) with title, EDB-ID, type, platform, author, and a direct link. No API key required.
🃏 fortune.py – Random Fortune
!fortune
Runs /usr/games/fortune on the host and sends the result to the room. Requires the fortune package (sudo apt install fortune).
📍 geo.py – IP / Domain Geolocation
!geo <ip_address or domain>
Geolocates an IP or domain using ip-api.com (primary) with ipapi.co as fallback. Shows country, city, region, postal code, coordinates, timezone, ISP, organization, and ASN. Private IP targets are blocked.
📰 hackernews.py – Hacker News
No API key required. Uses the official Firebase HN API and Algolia search.
| Command | Description |
|---|---|
!hn |
Top 5 stories (default) |
!hn top|new|best|ask|show|job |
Stories by type |
!hn story <id> |
Full story details |
!hn comments <id> |
Top comments for a story |
!hn search <query> |
Full-text search via Algolia |
Append a number to set the result count: !hn new 10
🔐 hashid.py – Hash Type Identifier
!hashid <hash>
Identifies 100+ hash formats including MD5, SHA family, bcrypt, Argon2, yescrypt, scrypt, PBKDF2, NTLM, NetNTLMv2, LM, LDAP, Oracle, MSSQL, MySQL, phpBB3, WordPress, Drupal, and more. Displays Hashcat mode (-m) and John the Ripper format (--format) for each match, sorted by confidence.
🔒 headers.py – HTTP Security Header Analysis
!headers <url>
Fetches a URL and analyzes its HTTP security headers. Outputs a security score (0–100) plus a structured breakdown covering HSTS, CSP, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Referrer-Policy, Permissions-Policy, SSL certificate details, and actionable recommendations. Private/internal addresses are blocked.
❓ help.py – Dynamic Help
!help
Aggregates and displays the __help__ metadata from every currently loaded plugin in a single collapsible message.
🎬 imdb.py – IMDb / OMDb Lookup
Requires OMDB_API_KEY in .env.
| Command | Description |
|---|---|
!imdb <title> |
Full details + poster image |
!imdb id <tt1234567> |
Lookup by IMDb ID |
!imdb search <query> |
Search titles |
!imdb episode <series> -s N -e N |
Episode info |
Optional flags: -y <year>, -t movie|series|episode, --short-plot
🤖 infermatic-text.py – AI Text Generation
Requires INFERMATIC_API (and optionally INFERMATIC_MODEL) in .env.
| Command | Description |
|---|---|
!text <prompt> |
Generate text with the default model |
!text --list-models |
List available models |
!text --use-model <model> <prompt> |
Use a specific model |
Optional flags: --temperature <0.0–1.0> (default 0.9), --max-tokens <N> (default 512)
🌐 isup.py – Site Availability Check
!isup <domain or IP>
Performs DNS resolution then checks HTTP/HTTPS availability, reporting the status code and response time.
😂 joke.py – Random Jokes
!joke # General joke
!joke programming # Programming joke
Fetches jokes from the Official Joke API. No API key required.
⭐ karma.py – Room Karma Tracking
Karma is tracked by display name — Matrix IDs (@user:server) are not accepted.
| Command | Description |
|---|---|
!karma <user> |
Show karma points for a user |
!karma++ <user> / !-- <user> |
Give or remove karma |
!karma top [n] / !karma bottom [n] |
Leaderboard (top or bottom N) |
!karma rank <user> |
Show a user's rank |
!karma stats |
Room-wide statistics |
!karma history <user> |
Recent votes for a user |
Shortcuts: !++ user, !-- user, or inline username++ / username-- anywhere in a message.
🎵 lastfm.py – Last.fm Music Stats
Requires LASTFM_API_KEY in .env. Run !lastfm for the full command list. Outputs neatly aligned code blocks.
📡 news.py – News Headlines
Requires GNEWS_API_KEY in .env.
| Command | Description |
|---|---|
!news |
Top headlines |
!news top|world|tech|business|science|health|sports|crypto |
Category headlines |
!news search <query> |
Search news |
Append a number to set result count: !news tech 8
🔌 plugins.py – List Loaded Plugins
!plugins
Displays the total count of loaded plugins and a collapsible list with each plugin's name and description.
🔗 proxy.py – SOCKS5 Proxy Finder
!proxy
Fetches a public proxy list, tests each candidate for connectivity, and returns a random working SOCKS5 proxy with its measured latency.
💬 quote.py – Goodreads Quotes
!quote random
!quote <author>
Scrapes Goodreads using Playwright (headless Chromium). Requires playwright, beautifulsoup4, and lxml to be installed (see Installation).
📊 roomstats.py – Per-User Room Statistics
| Command | Description |
|---|---|
!roomstats |
Aggregate room stats + top 10 users |
!rank <stat> |
Top 10 users by a specific statistic |
!stats [name] |
Show statistics for a specific user |
🔎 shodan.py – Shodan Reconnaissance
Requires SHODAN_KEY in .env.
| Command | Description |
|---|---|
!shodan ip <ip> |
IP info with open ports and services |
!shodan search <query> |
Search internet-connected devices |
!shodan host <domain> |
Host and subdomain enumeration |
!shodan count <query> |
Result count for a query |
🔐 sslscan.py – SSL/TLS Security Scanner
!sslscan <domain[:port]>
Tests SSL/TLS protocol support, cipher suites, certificate validity, and known vulnerabilities. Provides a security score (0–100) and actionable recommendations. Defaults to port 443.
🎨 stable-diffusion.py – Image Generation
Requires a locally running Stable Diffusion API (e.g. AUTOMATIC1111 or ComfyUI).
!sd [options] <prompt>
| Flag | Description |
|---|---|
--steps N |
Sampling steps (default 4) |
--cfg <scale> |
CFG scale (default 2) |
--h H --w W |
Image height / width in pixels (default 512×512) |
--neg <prompt> |
Negative prompt |
--sampler <name> |
Sampler name (default DPM++ SDE) |
--seed <N> |
Deterministic seed |
LORAs can be embedded directly in the prompt: <lora:filename:weight>
🌍 subdomains.py – Subdomain Enumeration
!subdomains <domain>
Discovers subdomains using SSL certificate transparency logs via the CertSpotter API. No API key required.
🧮 subnet.py – Subnet Calculator
| Command | Description |
|---|---|
!subnet info <CIDR> |
Detailed network info (network, broadcast, hosts, mask, etc.) |
!subnet split <CIDR> --prefix <N> |
Split into smaller subnets by new prefix length |
!subnet split <CIDR> --diff <N> |
Split by prefix delta |
!subnet adjacent <CIDR> <count> |
Show current and adjacent networks |
Supports both IPv4 and IPv6. RFC 3021 /31 and /32 networks are handled correctly (both addresses listed as usable).
💻 sysinfo.py – System Information
!sysinfo
Displays CPU usage and model, RAM, disk, network I/O, GPU info, temperature sensors, and top processes in a clean, emoji-aligned code block.
🕒 timezone.py – World Clock
!time <city> # Any city worldwide (geocoded)
!time <IANA zone> # e.g. Europe/London, Asia/Karachi
!time help # Show help
Examples: !time Lahore, !time New York, !time America/Chicago
No city names are hardcoded. IANA zones resolve completely offline; city name lookup uses free geocoding.
📚 urbandictionary.py – Urban Dictionary
!ud <term>
Fetches top definitions from Urban Dictionary. No API key required.
☁️ weather.py – Current Weather
!weather <location>
Shows temperature, feels-like, conditions, humidity, wind speed, and more in a clean aligned table. Uses OpenWeatherMap as the primary source (requires OPENWEATHER_API_KEY) with Open-Meteo as a free fallback.
👋 welcome.py – Room Welcome Message
!welcome
Manually triggers the room's configured welcome message for the requesting user.
🔍 whois.py – WHOIS Lookup
!whois <domain or IP>
Returns registrar, creation/expiry dates, nameservers, and registrant info in a clean aligned table.
📖 wikipedia.py – Wikipedia Summary
!wp <search term>
Returns the lead section and main image of a Wikipedia article using the MediaWiki API. No scraping, no API key required.
🗂️ xkcd.py – xkcd Comics
!xkcd # Random comic
!xkcd <number> # Specific comic (e.g. !xkcd 538)
Fetches comics directly from the xkcd JSON API. No API key required.
🎥 youtube-search.py – YouTube Search
Requires YOUTUBE_API_KEY in .env.
!yt <search query>
Returns the top video results from YouTube with titles, channel names, and direct links.
⚡ Rate Limiting
Non-admin users are limited to 3 commands per 5 seconds. Exceeding this limit returns:
⛔ You're sending commands too quickly. Please wait a few seconds.
The admin_user defined in funguy.conf is always exempt from rate limiting.
🔐 Security Notes
- All plugins that make outbound HTTP requests (
geo,dns,headers,sslscan, etc.) validate that the target resolves to a public IP address before connecting. Private, loopback, link-local, and reserved ranges are blocked. - The
admin_userconfig field is read-only via!set— it can only be changed by editingfunguy.confdirectly, preventing privilege escalation through chat commands. - Moderator commands (
!kick,!ban,!op, etc.) require Matrix power level ≥ 50 in the room, independent of the global admin setting. - The
encode.pyplugin operates entirely offline — no data is ever sent to external services.
💬 Support
Join the community Matrix room for help, bug reports, and discussion:
Self-hosting | Security | Sysadmin | Homelab | Programming
🧙 Credits
Creator & Developer: HB (@hashborgir:mozilla.org)
🍄 Funguy Bot — built during recovery from cervical spinal surgery. Bugs are features in disguise.