Volume changes now ramp linearly per-sample via _scale_pcm_ramp instead of jumping abruptly. Each frame steps _cur_vol toward target by at most 0.1, giving ~200ms for a full 0-to-1 sweep. Fast path unchanged when volume is stable.
18 KiB
Cheatsheet
Dev Commands
make install # Setup venv + install
make test # Run tests
make lint # Lint with ruff
make run # Start bot (bare metal)
make link # Symlink to ~/.local/bin
derp -c config.toml # Run with custom config
derp -v # Verbose/debug mode
derp --cprofile # Profile to derp.prof
SASL Authentication
# In config/derp.toml
[server]
sasl_user = "account"
sasl_pass = "password"
Rate Limiting
# In config/derp.toml (defaults shown)
[bot]
rate_limit = 2.0 # Messages per second
rate_burst = 5 # Burst capacity
Per-Channel Plugin Control
# Only allow specific plugins in a channel
[channels."#public"]
plugins = ["core", "dns", "cidr", "encode"]
# Omit section entirely to allow all plugins
core always active. PMs unrestricted. Denied commands silently ignored.
Structured Logging
[logging]
format = "json" # JSONL output (default: "text")
Container
make build # Build image (only for dep changes)
make up # Start (podman-compose)
make down # Stop
make logs # Follow logs
Code, plugins, config, and data are bind-mounted. No rebuild needed for
code changes -- restart the container or use !reload for plugins.
Bot Commands
!ping # Pong
!help # List commands
!help <cmd> # Command help
!help <plugin> # Plugin description + commands
!version # Bot version
!uptime # Bot uptime
!echo <text> # Echo text back
!h # Shorthand (any unambiguous prefix works)
Permission Tiers
user < trusted < oper < admin
# config/derp.toml
[bot]
admins = ["*!~root@*.ops.net"] # admin tier
operators = ["*!~staff@trusted.host"] # oper tier
trusted = ["*!~user@known.host"] # trusted tier
!whoami # Show your hostmask + permission tier
!admins # Show configured tiers + detected opers (admin)
IRC operators are auto-detected via WHO (admin tier). Hostmask patterns
use fnmatch. admin=True on commands still works (maps to tier="admin").
Channel Management (admin)
!kick nick reason # Kick user from channel
!ban *!*@bad.host # Ban hostmask
!unban *!*@bad.host # Remove ban
!topic New topic text # Set channel topic
!topic # Query current topic
!mode +m # Set channel mode
!mode +o nick # Give ops
Auto-joins channels when invited by an admin/ircop. Persists across restarts. Removed from auto-rejoin list if bot is kicked.
State Store (admin)
!state list myplugin # List keys
!state get myplugin key # Get value
!state del myplugin key # Delete key
!state clear myplugin # Clear all keys
IRCv3 Capabilities
# config/derp.toml
[server]
ircv3_caps = ["multi-prefix", "away-notify", "server-time"]
SASL auto-added when sasl_user/sasl_pass configured.
Plugin Management (admin)
!plugins # List loaded plugins
!load <plugin> # Hot-load a plugin (admin)
!reload <plugin> # Reload a changed plugin (admin)
!unload <plugin> # Remove a plugin (admin)
Archive
!archive https://example.com/page # Save to Wayback Machine
URL must have http:// or https:// scheme. 30s timeout. SOCKS5-proxied.
Bulk DNS
!resolve example.com github.com # A records (concurrent)
!resolve example.com AAAA # Specific type
!resolve 1.2.3.4 8.8.8.8 # Auto PTR for IPs
Max 10 hosts. Types: A, AAAA, MX, NS, TXT, CNAME, PTR, SOA. TCP DNS via SOCKS5, server 1.1.1.1.
Recon
!dork list # List dork categories
!dork admin example.com # Admin/login panel dorks
!dork files example.com # Exposed document dorks
!wayback example.com # Wayback Machine snapshot
!wayback example.com 20240101 # Snapshot near date
Categories: admin, backup, cloud, config, creds, dirs, errors, exposed, files, login.
OSINT
!username list # List services by category
!username john # Full scan (~25 services)
!username john github # Check single service
!dns example.com # A record lookup (UDP, local resolver)
!dns 1.2.3.4 # Reverse PTR lookup
!dns example.com MX # Specific type (A/AAAA/MX/NS/TXT/CNAME/PTR/SOA)
!tdns example.com # A record lookup (TCP via SOCKS5 proxy)
!tdns example.com MX @8.8.8.8 # Explicit type + custom server
!cert example.com # CT log lookup (max 5 domains)
!whois example.com # WHOIS domain lookup
!whois 8.8.8.8 # WHOIS IP lookup
!subdomain example.com # CT log subdomain enum
!subdomain example.com brute # + DNS wordlist brute
!headers example.com # HTTP fingerprint (tech + security)
Ops
!opslog add Compromised target # Add timestamped entry
!opslog list # Show last 5 entries
!opslog list 10 # Show last 10
!opslog search pivot # Search entries
!opslog del 3 # Delete entry by ID
!opslog clear # Clear channel log (admin)
!note set target 10.0.0.1 # Store a note
!note get target # Retrieve a note
!note del target # Delete a note
!note list # List all keys
!note clear # Clear all notes (admin)
Exploit-DB
!exploitdb search apache # Search by keyword
!exploitdb 12345 # Lookup by EDB ID
!exploitdb cve CVE-2024-1234 # Search by CVE
!exploitdb update # Download latest CSV
!exploitdb stats # Show index size
Payloads
!payload list # List categories
!payload sqli # Show SQLi payloads
!payload xss 3 # Show XSS payload #3
!payload ssti jinja # Search SSTI for 'jinja'
!payload lfi all # Show all LFI payloads
Categories: sqli, xss, ssti, lfi, cmdi, xxe
Red Team
!revshell bash 10.0.0.1 4444 # Reverse shell one-liner
!revshell list # List types (bash/sh/nc/nce/python/perl/php/ruby/socat/lua/ps)
!encode b64 hello # Base64 encode
!decode hex 68656c6c6f # Hex decode
!encode rot13 hello # ROT13
!hash hello # MD5 + SHA1 + SHA256
!hash sha512 hello # Specific algorithm
!hashid <hash> # Identify hash type
OPSEC
!defang https://evil.com # Defang IOC
!refang hxxps[://]evil[.]com # Refang IOC
Canary Tokens
!canary gen db-cred # 40-char hex token (default)
!canary gen aws staging-key # AWS AKIA keypair
!canary gen basic svc-login # user:pass pair
!canary list # List channel canaries
!canary info db-cred # Show full token
!canary del db-cred # Delete canary (admin)
Types: token (hex), aws (AKIA+secret), basic (user:pass).
Max 50/channel. gen/del admin only. Persists across restarts.
Network
!tcping example.com # TCP latency (port 443, 3 probes)
!tcping example.com 22 # Custom port
!tcping example.com 80 5 # Custom port + count (max 10)
!cidr 10.0.0.0/24 # Subnet info
!cidr contains 10.0.0.0/8 10.1.2.3 # Membership check
!portcheck 10.0.0.1 # Scan common ports
!portcheck 10.0.0.1 22,80,443 # Scan specific ports
!httpcheck https://example.com # HTTP status + timing
!tlscheck example.com # TLS/cert inspection
!tlscheck 10.0.0.1 8443 # Custom port
!blacklist 1.2.3.4 # DNSBL reputation check
Intelligence (local databases + APIs)
!internetdb 8.8.8.8 # Shodan InternetDB: ports, CVEs, CPEs, tags
!geoip 8.8.8.8 # GeoIP: city, country, coords, tz
!asn 8.8.8.8 # ASN: number + organization
!tor 1.2.3.4 # Check Tor exit node
!tor update # Download exit list
!iprep 1.2.3.4 # Firehol/ET blocklist check
!iprep update # Download blocklist feeds
!cve CVE-2024-1234 # Lookup specific CVE
!cve search apache rce # Search CVE descriptions
!cve update # Download NVD feed (slow)
!cve stats # Show index size
!mac AA:BB:CC:DD:EE:FF # MAC OUI vendor lookup
!mac random # Generate random MAC
!mac update # Download IEEE OUI database
Security Intelligence (API)
!abuse 8.8.8.8 # AbuseIPDB reputation check
!abuse 8.8.8.8 1.1.1.1 # Batch check (max 5)
!abuse 8.8.8.8 report 14 Spam # Report IP (admin)
!vt <hash> # VirusTotal file hash lookup
!vt 8.8.8.8 # VirusTotal IP lookup
!vt example.com # VirusTotal domain lookup
!vt https://evil.com # VirusTotal URL lookup
!jwt eyJhbG... # Decode JWT token
!emailcheck user@example.com # SMTP verification (admin)
API keys: set ABUSEIPDB_API_KEY / VIRUSTOTAL_API_KEY env vars or
configure in config/derp.toml under [abuseipdb] / [virustotal].
VT rate limit: 4 req/min. Email check: max 5, admin only.
FlaskPaste
!shorten https://long-url.com/x # Shorten URL
!paste some text to paste # Create paste
Auto-shortens URLs in !alert announcements and history when plugin is loaded.
Paste helper used internally for multi-line bot output.
Config: [flaskpaste] in config/derp.toml or FLASKPASTE_URL env var.
Data Setup
./scripts/update-data.sh # Update tor + iprep + oui
MAXMIND_LICENSE_KEY=xxx ./scripts/update-data.sh # + GeoLite2
Random
!rand password # 16-char random password
!rand password 32 all # 32-char, full charset
!rand hex 64 # Random hex string
!rand uuid # UUID4
!rand bytes 32 # Random bytes (hex)
!rand int 100 # Random 0..99
!rand coin # Heads or tails
!rand dice 2d20 # Roll 2x d20
Timer
!timer 5m # 5-minute countdown
!timer 1h30m deploy # Named timer
!timer 90 # 90 seconds
!timer list # Show active timers
!timer cancel deploy # Cancel a timer
Remind
!remind 5m check oven # One-shot (in-memory)
!remind every 1h hydrate # Repeating (in-memory)
!remind at 2027-06-15 deploy # Calendar one-shot (persisted)
!remind at 2027-06-15 14:30 go # With explicit time
!remind yearly 02-14 valentines # Yearly recurring (persisted)
!remind yearly 12-25 09:00 xmas # Yearly with time
!remind list # Show active reminders
!remind cancel abc123 # Cancel by ID
Default time: 12:00. Timezone: bot.timezone config (default UTC).
RSS
!rss add <url> [name] # Subscribe feed (admin)
!rss del <name> # Unsubscribe feed (admin)
!rss list # List channel feeds
!rss check <name> # Force-poll now
Names: lowercase alphanumeric + hyphens, 1-20 chars. Max 20 feeds/channel. Polls every 10min. Announces max 5 new items per cycle. Persists across restarts.
YouTube
!yt follow <url> [name] # Follow YouTube channel (admin)
!yt unfollow <name> # Unfollow channel (admin)
!yt list # List followed channels
!yt check <name> # Force-poll now
Accepts any YouTube URL: video, channel, handle, shorts, embed. Names: lowercase alphanumeric + hyphens, 1-20 chars. Max 20 channels/channel. Polls every 10min. Announces max 5 new videos per cycle. Persists across restarts.
Twitch
!twitch follow <user> [name] # Follow streamer (admin)
!twitch unfollow <name> # Unfollow streamer (admin)
!twitch list # List followed streamers
!twitch check <name> # Force-poll now
Names: lowercase alphanumeric + hyphens, 1-20 chars. Max 20 streamers/channel. Polls every 2min. Announces offline->live transitions. Persists across restarts. No API credentials needed (uses public GQL endpoint).
Alert
!alert add <name> <keyword...> # Add keyword alert (admin)
!alert del <name> # Remove alert (admin)
!alert list # List alerts
!alert check <name> # Force-poll now
!alert info <id> # Show full result details
!alert history <name> [n] # Show recent results (default 5)
Searches keywords across 27 backends: YouTube (yt), Twitch (tw), SearXNG (sx),
Reddit (rd), Mastodon (ft), DuckDuckGo (dg), Google News (gn), Kick (kk),
Dailymotion (dm), PeerTube (pt), Bluesky (bs), Lemmy (ly), Odysee (od),
Archive.org (ia), Hacker News (hn), GitHub (gh), Wikipedia (wp),
Stack Exchange (se), GitLab (gl), npm (nm), PyPI (pp), Docker Hub (dh),
arXiv (ax), Lobsters (lb), DEV.to (dv), Medium (md), Hugging Face (hf).
Names: lowercase alphanumeric + hyphens, 1-20 chars. Keywords: 1-100 chars.
Max 20 alerts/channel. Polls every 5min. Output: ACTION with [name/tag/id] date - URL,
then PRIVMSG with full title. add replies instantly (seeds in background).
Per-backend error tracking (5+ errors backs off that backend only).
Multi-instance backends (pt, ft, ly, sx) fetch concurrently.
Use !alert info <id> for details. Persists across restarts.
History in data/alert_history.db.
SearX
!searx <query> # Search SearXNG
Shows top 3 results as Title -- URL. Channel only. Max query length: 200 chars.
Cron (admin)
!cron add 1h #ops !rss check news # Schedule command every hour
!cron add 2d #alerts !tor update # Every 2 days
!cron del abc123 # Remove job by ID
!cron list # List jobs in channel
Intervals: 5m, 1h30m, 2d, 90s, or raw seconds. Min 1m, max 7d.
Max 20 jobs/channel. Persists across restarts. Channel only.
Webhook (admin)
# config/derp.toml
[webhook]
enabled = true
host = "127.0.0.1"
port = 8080
secret = "your-shared-secret"
# Send message to IRC channel via webhook
SECRET="your-shared-secret"
BODY='{"channel":"#ops","text":"Deploy done"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
curl -X POST http://127.0.0.1:8080/ \
-H "X-Signature: sha256=$SIG" -d "$BODY"
!webhook # Show listener status (admin)
POST JSON: {"channel":"#chan","text":"msg"}. Optional "action":true.
Auth: HMAC-SHA256 via X-Signature header. Starts on IRC connect.
Teams Integration
# config/derp.toml
[teams]
enabled = true
proxy = true # SOCKS5 proxy for outbound HTTP
bot_name = "derp"
bind = "127.0.0.1"
port = 8081
webhook_secret = "base64-secret-from-teams"
incoming_webhook_url = "" # optional, for proactive msgs
admins = ["aad-object-id-uuid"] # AAD object IDs
operators = []
trusted = []
Expose via Cloudflare Tunnel: cloudflared tunnel --url http://127.0.0.1:8081
Teams endpoint: POST /api/messages. HMAC-SHA256 auth via Authorization: HMAC <sig>.
Replies returned as JSON in HTTP response. IRC-only commands (kick, ban, topic) are no-ops.
~90% of plugins work without modification.
Telegram Integration
# config/derp.toml
[telegram]
enabled = true
proxy = true # SOCKS5 proxy for HTTP
bot_token = "123456:ABC-DEF..." # from @BotFather
poll_timeout = 30 # long-poll seconds
admins = [123456789] # Telegram user IDs
operators = []
trusted = []
Long-polling via getUpdates -- no public endpoint needed. HTTP through
SOCKS5 proxy by default (proxy = true). Strips @botusername suffix in groups. Messages
split at 4096 chars. IRC-only commands are no-ops. ~90% of plugins work.
Mumble Integration
# config/derp.toml
[mumble]
enabled = true
proxy = false # pymumble connects directly
host = "mumble.example.com"
port = 64738
username = "derp"
password = ""
admins = ["admin_user"] # Mumble usernames
operators = []
trusted = []
Uses pymumble for protocol handling (connection, voice, Opus encoding). HTML stripped on receive, escaped on send. IRC-only commands are no-ops. ~90% of plugins work.
Music (Mumble only)
!play <url|playlist> # Play audio (YouTube, SoundCloud, etc.)
!play <playlist-url> # Playlist tracks expanded into queue
!stop # Stop playback, clear queue
!skip # Skip current track
!queue # Show queue
!queue <url> # Add to queue (alias for !play)
!np # Now playing
!volume # Show current volume
!volume 75 # Set volume (0-100, default 50)
Requires: yt-dlp, ffmpeg, libopus on the host.
Max 50 tracks in queue. Playlists auto-expand; excess truncated at limit.
Volume ramps smoothly over ~200ms (no abrupt jumps mid-playback).
Mumble-only: !play replies with error on other adapters, others silently no-op.
Plugin Template
from derp.plugin import command, event
@command("name", help="Description")
async def cmd_name(bot, message):
text = message.text.split(None, 1)
await bot.reply(message, "response")
@event("JOIN")
async def on_join(bot, message):
await bot.send(message.target, f"Hi {message.nick}")
Message Object
msg.nick # Sender nick
msg.target # Channel or nick
msg.text # Message body
msg.is_channel # True if channel
msg.prefix # nick!user@host
msg.command # PRIVMSG, JOIN, etc.
msg.params # All params list
msg.tags # IRCv3 tags dict
Multi-Server
# config/derp.toml
[bot]
prefix = "!" # Shared defaults
plugins_dir = "plugins"
[servers.libera]
host = "irc.libera.chat"
port = 6697
nick = "derp"
channels = ["#test"]
[servers.oftc]
host = "irc.oftc.net"
port = 6697
nick = "derpbot"
channels = ["#derp"]
admins = ["*!~admin@oftc.host"] # Per-server override
Per-server blocks accept both server keys (host, port, nick, tls, ...)
and bot overrides (prefix, channels, admins, ...). Unset keys inherit
from [bot]/[server] defaults. Legacy [server] config still works.
State isolated per server: data/state-libera.db, data/state-oftc.db.
Plugins loaded once, shared across all servers.
Config Locations
1. --config PATH # CLI flag
2. ./config/derp.toml # Project dir
3. ~/.config/derp/derp.toml # User config
4. Built-in defaults # Fallback