canary: generate realistic fake credentials (token/aws/basic) for planting as canary tripwires. Per-channel state persistence. tcping: TCP connect latency probe through SOCKS5 proxy with min/avg/max reporting. Proxy-compatible alternative to traceroute. archive: save URLs to Wayback Machine via Save Page Now API, routed through SOCKS5 proxy. resolve: bulk DNS resolution (up to 10 hosts) via TCP DNS through SOCKS5 proxy with concurrent asyncio.gather. 83 new tests (1010 total), docs updated.
1114 lines
39 KiB
Markdown
1114 lines
39 KiB
Markdown
# Usage Guide
|
|
|
|
## Running
|
|
|
|
```bash
|
|
# From project directory
|
|
derp
|
|
|
|
# With options
|
|
derp --config /path/to/derp.toml --verbose
|
|
```
|
|
|
|
## CLI Flags
|
|
|
|
| Flag | Description |
|
|
|------|-------------|
|
|
| `-c, --config PATH` | Config file path |
|
|
| `-v, --verbose` | Debug logging |
|
|
| `--cprofile [PATH]` | Enable cProfile, dump to PATH [derp.prof] |
|
|
| `--tracemalloc [N]` | Enable tracemalloc, capture N frames deep [10] |
|
|
| `-V, --version` | Print version |
|
|
| `-h, --help` | Show help |
|
|
|
|
## Configuration
|
|
|
|
All settings in `config/derp.toml`:
|
|
|
|
```toml
|
|
[server]
|
|
host = "irc.libera.chat" # IRC server hostname
|
|
port = 6697 # Port (6697 = TLS, 6667 = plain)
|
|
tls = true # Enable TLS encryption
|
|
nick = "derp" # Bot nickname
|
|
user = "derp" # Username (ident)
|
|
realname = "derp IRC bot" # Real name field
|
|
password = "" # Server password (optional)
|
|
sasl_user = "" # SASL PLAIN username (optional)
|
|
sasl_pass = "" # SASL PLAIN password (optional)
|
|
ircv3_caps = [ # IRCv3 capabilities to request
|
|
"multi-prefix",
|
|
"away-notify",
|
|
"server-time",
|
|
"cap-notify",
|
|
"account-notify",
|
|
]
|
|
|
|
[bot]
|
|
prefix = "!" # Command prefix character
|
|
channels = ["#test"] # Channels to join on connect
|
|
plugins_dir = "plugins" # Plugin directory path
|
|
rate_limit = 2.0 # Max messages per second (default: 2.0)
|
|
rate_burst = 5 # Burst capacity (default: 5)
|
|
paste_threshold = 4 # Max lines before overflow to FlaskPaste (default: 4)
|
|
admins = [] # Hostmask patterns (fnmatch), IRCOPs auto-detected
|
|
timezone = "UTC" # Timezone for calendar reminders (IANA tz name)
|
|
|
|
[logging]
|
|
level = "info" # Logging level: debug, info, warning, error
|
|
format = "text" # Log format: "text" (default) or "json"
|
|
```
|
|
|
|
## Built-in Commands
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| `!ping` | Bot responds with "pong" |
|
|
| `!help` | List all available commands |
|
|
| `!help <cmd>` | Show help for a specific command |
|
|
| `!help <plugin>` | Show plugin description and its commands |
|
|
| `!version` | Show bot version |
|
|
| `!uptime` | Show how long the bot has been running |
|
|
| `!echo <text>` | Echo back text (example plugin) |
|
|
| `!cert <domain> [...]` | Lookup CT logs for up to 5 domains |
|
|
| `!whoami` | Show your hostmask and admin status |
|
|
| `!load <plugin>` | Hot-load a plugin (admin) |
|
|
| `!reload <plugin>` | Reload a plugin (admin) |
|
|
| `!unload <plugin>` | Unload a plugin (admin) |
|
|
| `!admins` | Show admin patterns and detected opers (admin) |
|
|
| `!plugins` | List loaded plugins with handler counts |
|
|
| `!state <action> <plugin> [key]` | Inspect plugin state store (admin) |
|
|
| `!kick <nick> [reason]` | Kick user from channel (admin) |
|
|
| `!ban <mask>` | Ban a hostmask in channel (admin) |
|
|
| `!unban <mask>` | Remove a ban from channel (admin) |
|
|
| `!topic [text]` | Set or query channel topic (admin) |
|
|
| `!mode <mode> [args]` | Set channel mode (admin) |
|
|
| `!dns <target> [type]` | DNS lookup (A, AAAA, MX, NS, TXT, CNAME, PTR, SOA) |
|
|
| `!tdns <target> [type] [@server]` | TCP DNS lookup via SOCKS5 proxy |
|
|
| `!encode <fmt> <text>` | Encode text (b64, hex, url, rot13) |
|
|
| `!decode <fmt> <text>` | Decode text (b64, hex, url, rot13) |
|
|
| `!hash [algo] <text>` | Generate hash digests (md5, sha1, sha256, sha512) |
|
|
| `!hashid <hash>` | Identify hash type by format |
|
|
| `!defang <ioc>` | Defang URLs/IPs/domains for safe sharing |
|
|
| `!refang <text>` | Restore defanged IOCs |
|
|
| `!revshell <type> <ip> <port>` | Generate reverse shell one-liner |
|
|
| `!cidr <network>` | Subnet info (range, hosts, mask) |
|
|
| `!cidr contains <net> <ip>` | Check if IP belongs to network |
|
|
| `!whois <domain\|ip>` | WHOIS lookup via raw TCP (port 43) |
|
|
| `!portcheck <host> [ports]` | Async TCP port scan (max 20 ports) |
|
|
| `!httpcheck <url>` | HTTP status, redirects, response time |
|
|
| `!tlscheck <host> [port]` | TLS version, cipher, cert details |
|
|
| `!blacklist <ip>` | Check IP against 10 DNSBLs |
|
|
| `!rand <mode> [args]` | Random: password, hex, uuid, bytes, int, coin, dice |
|
|
| `!timer <duration> [label]` | Set countdown timer with notification |
|
|
| `!timer list` | Show active timers |
|
|
| `!timer cancel <label>` | Cancel a running timer |
|
|
| `!remind <duration> <text>` | One-shot reminder after duration |
|
|
| `!remind every <duration> <text>` | Repeating reminder at interval |
|
|
| `!remind at <YYYY-MM-DD> [HH:MM] <text>` | Calendar reminder at specific date/time |
|
|
| `!remind yearly <MM-DD> [HH:MM] <text>` | Yearly recurring reminder |
|
|
| `!remind list` | Show all active reminders |
|
|
| `!remind cancel <id>` | Cancel a reminder by ID |
|
|
| `!geoip <ip>` | GeoIP lookup (city, country, coords, timezone) |
|
|
| `!asn <ip>` | ASN lookup (AS number, organization) |
|
|
| `!tor <ip\|update>` | Check IP against Tor exit nodes |
|
|
| `!iprep <ip\|update>` | Check IP against Firehol/ET blocklists |
|
|
| `!cve <id\|search>` | CVE lookup from local NVD mirror |
|
|
| `!opslog <add\|list\|search\|del\|clear>` | Timestamped operational log |
|
|
| `!note <set\|get\|del\|list\|clear>` | Per-channel key-value notes |
|
|
| `!subdomain <domain> [brute]` | Subdomain enumeration (crt.sh + DNS) |
|
|
| `!headers <url>` | HTTP header fingerprinting |
|
|
| `!exploitdb <search\|id\|cve\|update>` | Search local Exploit-DB mirror |
|
|
| `!payload <type> [variant]` | Web vuln payload templates |
|
|
| `!dork <category\|list> [target]` | Google dork query builder |
|
|
| `!wayback <url> [YYYYMMDD]` | Wayback Machine snapshot lookup |
|
|
| `!username <user>` | Check username across ~25 services |
|
|
| `!username <user> <service>` | Check single service |
|
|
| `!username list` | Show available services by category |
|
|
| `!alert <add\|del\|list\|check\|info\|history>` | Keyword alert subscriptions across platforms |
|
|
| `!searx <query>` | Search SearXNG and show top results |
|
|
| `!jwt <token>` | Decode JWT header, claims, and flag issues |
|
|
| `!mac <address\|random\|update>` | MAC OUI vendor lookup / random MAC |
|
|
| `!abuse <ip> [ip2 ...]` | AbuseIPDB reputation check |
|
|
| `!abuse <ip> report <cats> <comment>` | Report IP to AbuseIPDB (admin) |
|
|
| `!vt <hash\|ip\|domain\|url>` | VirusTotal lookup |
|
|
| `!emailcheck <email> [email2 ...]` | SMTP email verification (admin) |
|
|
| `!internetdb <ip>` | Shodan InternetDB host recon (ports, CVEs, CPEs) |
|
|
| `!canary <gen\|list\|info\|del>` | Canary token generator/tracker |
|
|
| `!tcping <host> [port] [count]` | TCP connect latency probe via SOCKS5 |
|
|
| `!archive <url>` | Save URL to Wayback Machine |
|
|
| `!resolve <host> [host2 ...] [type]` | Bulk DNS resolution via TCP/SOCKS5 |
|
|
| `!shorten <url>` | Shorten a URL via FlaskPaste |
|
|
| `!pastemoni <add\|del\|list\|check>` | Paste site keyword monitoring |
|
|
|
|
### Command Shorthand
|
|
|
|
Commands can be abbreviated to any unambiguous prefix:
|
|
|
|
```
|
|
!h -> !help (unique match)
|
|
!pi -> !ping (unique match)
|
|
!p -> error: ambiguous (ping, plugins)
|
|
```
|
|
|
|
Exact matches always take priority over prefix matches.
|
|
|
|
### `!cert` -- Certificate Transparency Lookup
|
|
|
|
Query [crt.sh](https://crt.sh) CT logs to enumerate SSL certificates for
|
|
domains. Reports totals (expired/valid) and flags domains still serving
|
|
expired certs.
|
|
|
|
```
|
|
!cert example.com
|
|
!cert example.com badsite.com another.org
|
|
```
|
|
|
|
Output format:
|
|
|
|
```
|
|
example.com -- 127 certs (23 expired, 104 valid)
|
|
badsite.com -- 45 certs (8 expired, 37 valid) | live cert EXPIRED
|
|
broken.test -- error: timeout
|
|
```
|
|
|
|
- Max 5 domains per invocation
|
|
- crt.sh can be slow; the bot confirms receipt before querying
|
|
- Live cert check runs only when expired CT entries exist
|
|
|
|
## Per-Channel Plugin Control
|
|
|
|
Restrict which plugins are active in specific channels. Channels without
|
|
a `[channels."<name>"]` section run all plugins. Channels with a `plugins`
|
|
list only run those plugins. The `core` plugin is always active (exempt
|
|
from filtering). Private messages are always unrestricted.
|
|
|
|
```toml
|
|
[channels."#public"]
|
|
plugins = ["core", "dns", "cidr", "encode"]
|
|
|
|
[channels."#ops"]
|
|
plugins = ["core", "revshell", "payload", "exploitdb", "opslog"]
|
|
|
|
# #unrestricted -- no section, runs everything
|
|
```
|
|
|
|
When a command is denied by channel config, it is silently ignored (no
|
|
error message). Event handlers from denied plugins are also skipped.
|
|
|
|
## Structured Logging (JSON)
|
|
|
|
Set `format = "json"` in `[logging]` to emit one JSON object per log line
|
|
(JSONL), suitable for log aggregation tools.
|
|
|
|
```toml
|
|
[logging]
|
|
level = "info"
|
|
format = "json"
|
|
```
|
|
|
|
Each line contains:
|
|
|
|
| Field | Description |
|
|
|-------|-------------|
|
|
| `ts` | Timestamp (`YYYY-MM-DDTHH:MM:SS`) |
|
|
| `level` | Log level (`debug`, `info`, `warning`, `error`) |
|
|
| `logger` | Logger name (`derp.bot`, `derp.plugin`, etc.) |
|
|
| `msg` | Log message text |
|
|
| `exc` | Exception traceback (only present on errors) |
|
|
|
|
Default format is `"text"` (human-readable, same as before).
|
|
|
|
## Admin System
|
|
|
|
Commands marked as `admin` require elevated permissions. Admin access is
|
|
granted via:
|
|
|
|
1. **IRC operator status** -- detected automatically via `WHO`
|
|
2. **Hostmask patterns** -- configured in `[bot] admins`, fnmatch-style
|
|
|
|
```toml
|
|
[bot]
|
|
admins = [
|
|
"*!~user@trusted.host",
|
|
"ops!*@*.ops.net",
|
|
]
|
|
```
|
|
|
|
Empty by default -- only IRC operators get admin access unless patterns
|
|
are configured.
|
|
|
|
### Oper Detection
|
|
|
|
IRC operators are detected via the `*` flag in `WHO` replies. Detection
|
|
happens at two points:
|
|
|
|
- **On connect** -- `WHO #channel` for each configured channel
|
|
- **On user JOIN** -- debounced `WHO #channel` (2-second window)
|
|
|
|
The debounce prevents flooding the server during netsplit recovery: many
|
|
rapid JOINs produce a single `WHO` per channel. Note that the first
|
|
command after joining may not yet have oper status -- the debounced WHO
|
|
fires after a 2-second delay. Users who `QUIT` are removed from the oper
|
|
set automatically.
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| `!whoami` | Show your hostmask and admin status |
|
|
| `!admins` | Show configured patterns and detected opers (admin) |
|
|
|
|
Admin-restricted commands: `!load`, `!reload`, `!unload`, `!admins`, `!state`,
|
|
`!kick`, `!ban`, `!unban`, `!topic`, `!mode`.
|
|
|
|
### Writing Admin Commands
|
|
|
|
```python
|
|
@command("dangerous", help="Admin-only action", admin=True)
|
|
async def cmd_dangerous(bot, message):
|
|
...
|
|
```
|
|
|
|
## IRCv3 Capability Negotiation
|
|
|
|
The bot negotiates IRCv3 capabilities using `CAP LS 302` during registration.
|
|
This enables richer features on servers that support them.
|
|
|
|
### Default Capabilities
|
|
|
|
```toml
|
|
[server]
|
|
ircv3_caps = ["multi-prefix", "away-notify", "server-time",
|
|
"cap-notify", "account-notify"]
|
|
```
|
|
|
|
| Capability | Purpose |
|
|
|------------|---------|
|
|
| `multi-prefix` | Better IRCOP/voice detection |
|
|
| `away-notify` | Receive AWAY status changes |
|
|
| `server-time` | Accurate message timestamps |
|
|
| `cap-notify` | Dynamic capability updates |
|
|
| `account-notify` | Account login/logout notices |
|
|
| `sasl` | Auto-added when SASL credentials configured |
|
|
|
|
The bot only requests caps the server advertises. SASL is automatically
|
|
included when `sasl_user` and `sasl_pass` are configured.
|
|
|
|
### Message Tags
|
|
|
|
IRCv3 message tags (`@key=value;...`) are parsed automatically and available
|
|
on the message object as `message.tags` (a `dict[str, str]`). Values are
|
|
unescaped per the IRCv3 spec.
|
|
|
|
## Channel Management
|
|
|
|
Channel management commands require admin privileges and must be used in a
|
|
channel (not DM).
|
|
|
|
```
|
|
!kick <nick> [reason] Kick a user from the channel
|
|
!ban <mask> Set +b on a hostmask (e.g. *!*@bad.host)
|
|
!unban <mask> Remove +b from a hostmask
|
|
!topic [text] Set topic (empty = query current topic)
|
|
!mode <mode> [args] Set raw MODE on the channel
|
|
```
|
|
|
|
The bot must have channel operator status for these commands to take effect.
|
|
|
|
### Invite Auto-Join
|
|
|
|
When an admin or IRC operator sends an `INVITE`, the bot automatically joins
|
|
the target channel and persists it for auto-rejoin on reconnect. Non-admin
|
|
invites are silently ignored. If the bot is kicked from a persisted channel,
|
|
it is removed from the auto-rejoin list. Channels already in the config
|
|
`[bot] channels` are skipped (they rejoin via the normal path).
|
|
|
|
## Plugin State Persistence
|
|
|
|
Plugins can persist key-value data across restarts via `bot.state`:
|
|
|
|
```python
|
|
bot.state.set("myplugin", "last_run", "2026-02-15")
|
|
value = bot.state.get("myplugin", "last_run")
|
|
bot.state.delete("myplugin", "last_run")
|
|
keys = bot.state.keys("myplugin")
|
|
bot.state.clear("myplugin")
|
|
```
|
|
|
|
Data is stored in `data/state.db` (SQLite). Each plugin gets its own
|
|
namespace so keys never collide.
|
|
|
|
### Inspection Commands (admin)
|
|
|
|
```
|
|
!state list <plugin> List keys for a plugin
|
|
!state get <plugin> <key> Get a value
|
|
!state del <plugin> <key> Delete a key
|
|
!state clear <plugin> Clear all state for a plugin
|
|
```
|
|
|
|
## Local Databases (Wave 3)
|
|
|
|
Several plugins rely on local data files in the `data/` directory. Use the
|
|
update script or in-bot commands to populate them.
|
|
|
|
### Data Update Script
|
|
|
|
```bash
|
|
./scripts/update-data.sh # Update all feeds
|
|
MAXMIND_LICENSE_KEY=xxx ./scripts/update-data.sh # Include GeoLite2
|
|
```
|
|
|
|
The script is cron-friendly (exit 0/1, quiet unless `NO_COLOR` is unset).
|
|
|
|
### In-Bot Updates
|
|
|
|
```
|
|
!tor update # Download Tor exit node list
|
|
!iprep update # Download Firehol/ET blocklist feeds
|
|
!cve update # Download NVD CVE feed (slow, paginated)
|
|
```
|
|
|
|
### Data Directory Layout
|
|
|
|
```
|
|
data/
|
|
GeoLite2-City.mmdb # MaxMind GeoIP (requires license key)
|
|
GeoLite2-ASN.mmdb # MaxMind ASN (requires license key)
|
|
tor-exit-nodes.txt # Tor exit node IPs
|
|
iprep/ # Firehol/ET blocklist feeds
|
|
firehol_level1.netset
|
|
firehol_level2.netset
|
|
et_compromised.ipset
|
|
...
|
|
nvd/ # NVD CVE JSON files
|
|
nvd_0000.json
|
|
...
|
|
```
|
|
|
|
GeoLite2 databases require a free MaxMind license key. Set
|
|
`MAXMIND_LICENSE_KEY` when running the update script.
|
|
|
|
## Plugin Management
|
|
|
|
Plugins can be loaded, unloaded, and reloaded at runtime without
|
|
restarting the bot.
|
|
|
|
```
|
|
!load crtsh # Hot-load a new plugin from plugins/
|
|
!reload crtsh # Reload a changed plugin
|
|
!unload crtsh # Remove a plugin and all its handlers
|
|
!plugins # List loaded plugins with handler counts
|
|
```
|
|
|
|
The `core` plugin cannot be unloaded (prevents losing `!load`/`!reload`),
|
|
but it can be reloaded.
|
|
|
|
## Writing Plugins
|
|
|
|
Create a `.py` file in the `plugins/` directory:
|
|
|
|
```python
|
|
from derp.plugin import command, event
|
|
|
|
@command("hello", help="Greet the user")
|
|
async def cmd_hello(bot, message):
|
|
"""Handler receives bot instance and parsed Message."""
|
|
await bot.reply(message, f"Hello, {message.nick}!")
|
|
|
|
@event("JOIN")
|
|
async def on_join(bot, message):
|
|
"""Event handlers fire on IRC events (JOIN, PART, QUIT, etc.)."""
|
|
if message.nick != bot.nick:
|
|
await bot.send(message.target, f"Welcome, {message.nick}")
|
|
```
|
|
|
|
### Plugin API
|
|
|
|
The `bot` object provides:
|
|
|
|
| Method | Description |
|
|
|--------|-------------|
|
|
| `bot.send(target, text)` | Send message to channel or nick |
|
|
| `bot.reply(msg, text)` | Reply to source (channel or PM) |
|
|
| `bot.action(target, text)` | Send `/me` action |
|
|
| `bot.join(channel)` | Join a channel |
|
|
| `bot.part(channel [, reason])` | Leave a channel |
|
|
| `bot.quit([reason])` | Disconnect from server |
|
|
| `bot.kick(channel, nick [, reason])` | Kick a user |
|
|
| `bot.mode(target, mode_str, *args)` | Set a mode |
|
|
| `bot.set_topic(channel, topic)` | Set channel topic |
|
|
| `bot.state` | Plugin state store (get/set/delete/keys/clear) |
|
|
|
|
The `message` object provides:
|
|
|
|
| Attribute | Description |
|
|
|-----------|-------------|
|
|
| `message.nick` | Sender's nickname |
|
|
| `message.prefix` | Full `nick!user@host` prefix |
|
|
| `message.command` | IRC command (PRIVMSG, JOIN, etc.) |
|
|
| `message.target` | First param (channel or nick) |
|
|
| `message.text` | Trailing text content |
|
|
| `message.is_channel` | Whether target is a channel |
|
|
| `message.params` | All message parameters |
|
|
| `message.tags` | IRCv3 message tags (dict) |
|
|
|
|
## Message Truncation
|
|
|
|
Messages are automatically split at UTF-8 safe boundaries to comply with
|
|
the IRC 512-byte line limit (RFC 2812). The overhead of `PRIVMSG <target> :`
|
|
and `\r\n` is accounted for, so plugins can send arbitrarily long text
|
|
without worrying about protocol limits.
|
|
|
|
### `!remind` -- Reminders
|
|
|
|
Set one-shot, repeating, or calendar-based reminders. Duration-based reminders
|
|
are in-memory and lost on restart. Calendar reminders (`at`, `yearly`) are
|
|
persisted via `bot.state` and restored on reconnect.
|
|
|
|
```
|
|
!remind 5m check the oven One-shot after 5 minutes
|
|
!remind 2d12h renew cert One-shot after 2 days 12 hours
|
|
!remind every 1h drink water Repeat every hour
|
|
!remind at 2027-06-15 deploy release Fire at specific date (noon default)
|
|
!remind at 2027-06-15 14:30 deploy Fire at specific date + time
|
|
!remind yearly 02-14 valentines Every year on Feb 14 at noon
|
|
!remind yearly 12-25 09:00 merry xmas Every year on Dec 25 at 09:00
|
|
!remind list Show all active reminders
|
|
!remind cancel abc123 Cancel by ID
|
|
```
|
|
|
|
- Default time when omitted: **12:00** (noon) in configured timezone
|
|
- Timezone: `bot.timezone` in config, default `UTC`
|
|
- Leap day (02-29): fires on Feb 28 in non-leap years
|
|
- Past dates for `at` are rejected
|
|
- Calendar reminders survive restarts; duration-based do not
|
|
|
|
## Reconnect Backoff
|
|
|
|
On connection loss, the bot reconnects with exponential backoff and jitter:
|
|
|
|
- Initial delay: 5 seconds
|
|
- Growth: doubles each attempt (5s, 10s, 20s, 40s, ...)
|
|
- Cap: 300 seconds (5 minutes)
|
|
- Jitter: +/- 25% to avoid thundering herd
|
|
- Resets to 5s after a successful connection
|
|
|
|
### `!dork` -- Google Dork Query Builder
|
|
|
|
Generate Google dork queries for a target domain. Template-based, no HTTP
|
|
requests -- just outputs the query string for manual use.
|
|
|
|
```
|
|
!dork list List all dork categories
|
|
!dork admin example.com Admin/login panel dorks
|
|
!dork files example.com Exposed document dorks
|
|
```
|
|
|
|
Categories: admin, backup, cloud, config, creds, dirs, errors, exposed,
|
|
files, login.
|
|
|
|
### `!wayback` -- Wayback Machine Lookup
|
|
|
|
Check the Wayback Machine for archived snapshots of a URL.
|
|
|
|
```
|
|
!wayback example.com Check latest snapshot
|
|
!wayback example.com/page 20240101 Check snapshot near a date
|
|
```
|
|
|
|
Auto-prepends `https://` if no scheme is provided. Uses the Wayback Machine
|
|
availability API.
|
|
|
|
### `!username` -- Username Enumeration
|
|
|
|
Check username availability across ~25 services using HTTP probes and
|
|
public JSON APIs. Supports GitHub, GitLab, Reddit, Docker Hub, Keybase,
|
|
Dev.to, Twitch, Steam, and more.
|
|
|
|
```
|
|
!username list List services by category
|
|
!username john Full scan (~25 services)
|
|
!username john github Check single service
|
|
```
|
|
|
|
Output format:
|
|
|
|
```
|
|
Full scan:
|
|
Checking "john" across 25 services...
|
|
john -- 8 found, 14 not found, 3 errors
|
|
Found: GitHub, GitLab, Reddit, Twitch, Steam, PyPI, Docker Hub, Medium
|
|
|
|
Single service:
|
|
GitHub: john -> found | https://github.com/john
|
|
GitHub: john -> not found
|
|
|
|
List:
|
|
Dev: GitHub, GitLab, Codeberg, ...
|
|
Social: Reddit, Twitter/X, ...
|
|
Media: Twitch, Spotify, ...
|
|
Other: Steam, Pastebin, ...
|
|
```
|
|
|
|
- Username must match `[a-zA-Z0-9._-]{1,39}`
|
|
- Full scan sends acknowledgment before probing
|
|
- 8 parallel workers, 20s overall timeout
|
|
- Three check methods: HTTP status, JSON API, body search
|
|
|
|
### `!rss` -- RSS/Atom Feed Subscriptions
|
|
|
|
Subscribe RSS or Atom feeds to IRC channels with automatic polling and
|
|
new-item announcements.
|
|
|
|
```
|
|
!rss add <url> [name] Subscribe a feed to this channel (admin)
|
|
!rss del <name> Unsubscribe a feed (admin)
|
|
!rss list List feeds in this channel
|
|
!rss check <name> Force-poll a feed now
|
|
```
|
|
|
|
- `add` and `del` require admin privileges
|
|
- All subcommands must be used in a channel (not PM)
|
|
- If `name` is omitted on `add`, it is derived from the URL hostname
|
|
- Names must be lowercase alphanumeric + hyphens, 1-20 characters
|
|
- Maximum 20 feeds per channel
|
|
|
|
Polling and announcements:
|
|
|
|
- Feeds are polled every 10 minutes by default
|
|
- On `add`, existing items are recorded without announcing (prevents flood)
|
|
- New items are announced as `[name] title | YYYY-MM-DD -- link`
|
|
- Published date is included when available (RSS `pubDate`, Atom `published`)
|
|
- Maximum 5 items announced per poll; excess shown as `... and N more`
|
|
- Titles are truncated to 80 characters
|
|
- Supports HTTP conditional requests (`ETag`, `If-Modified-Since`)
|
|
- 5 consecutive errors doubles the poll interval (max 1 hour)
|
|
- Feed subscriptions persist across bot restarts via `bot.state`
|
|
|
|
### `!yt` -- YouTube Channel Subscriptions
|
|
|
|
Follow YouTube channels and announce new videos in IRC channels. Accepts any
|
|
YouTube URL (video, channel, handle, shorts, embed) and resolves it to the
|
|
channel's Atom feed.
|
|
|
|
```
|
|
!yt follow <url> [name] Follow a YouTube channel (admin)
|
|
!yt unfollow <name> Unfollow a channel (admin)
|
|
!yt list List followed channels
|
|
!yt check <name> Force-poll a channel now
|
|
```
|
|
|
|
- `follow` and `unfollow` require admin privileges
|
|
- All subcommands must be used in a channel (not PM)
|
|
- If `name` is omitted on `follow`, it is derived from the channel title
|
|
- Names must be lowercase alphanumeric + hyphens, 1-20 characters
|
|
- Maximum 20 channels per IRC channel
|
|
|
|
URL resolution:
|
|
|
|
- `/channel/UCXXX` URLs extract the channel ID directly
|
|
- All other YouTube URLs (handles, videos, shorts) fetch the page to resolve
|
|
- Constructs the YouTube Atom feed URL from the resolved channel ID
|
|
|
|
Polling and announcements:
|
|
|
|
- Channels are polled every 10 minutes by default
|
|
- On `follow`, existing videos are recorded without announcing (prevents flood)
|
|
- New videos are announced as `[name] Video Title | 18:25 1.5Mv 45klk 2026-01-15 -- URL`
|
|
- Metadata suffix includes duration, views, likes, and published date when available
|
|
- Duration fetched via InnerTube API per new video (only at announcement time)
|
|
- Maximum 5 videos announced per poll; excess shown as `... and N more`
|
|
- Titles are truncated to 80 characters
|
|
- Supports HTTP conditional requests (`ETag`, `If-Modified-Since`)
|
|
- 5 consecutive errors doubles the poll interval (max 1 hour)
|
|
- Subscriptions persist across bot restarts via `bot.state`
|
|
|
|
### `!twitch` -- Twitch Livestream Notifications
|
|
|
|
Follow Twitch streamers and get notified when they go live. Uses Twitch's
|
|
public GQL endpoint (no API credentials required).
|
|
|
|
```
|
|
!twitch follow <username> [name] Follow a streamer (admin)
|
|
!twitch unfollow <name> Unfollow a streamer (admin)
|
|
!twitch list List followed streamers
|
|
!twitch check <name> Check status now
|
|
```
|
|
|
|
- `follow` and `unfollow` require admin privileges
|
|
- All subcommands must be used in a channel (not PM)
|
|
- If `name` is omitted on `follow`, it defaults to the Twitch login (lowercase)
|
|
- Names must be lowercase alphanumeric + hyphens, 1-20 characters
|
|
- Twitch usernames must match `[a-zA-Z0-9_]{1,25}`
|
|
- Maximum 20 streamers per IRC channel
|
|
|
|
Polling and announcements:
|
|
|
|
- Streamers are polled every 2 minutes by default
|
|
- On `follow`, the current stream state is recorded without announcing
|
|
- Announcements fire on state transitions: offline to live, or new stream ID
|
|
- Format: `[name] is live: Stream Title (Game) | 50k viewers -- https://twitch.tv/login`
|
|
- Game is omitted if not set; viewer count shown when available
|
|
- Titles are truncated to 80 characters
|
|
- 5 consecutive errors doubles the poll interval (max 1 hour)
|
|
- Subscriptions persist across bot restarts via `bot.state`
|
|
- `list` shows live/error status with viewer count: `name (live, 50k)`
|
|
- `check` forces an immediate poll and reports current status
|
|
|
|
### `!searx` -- SearXNG Web Search
|
|
|
|
Search the local SearXNG instance and display top results.
|
|
|
|
```
|
|
!searx <query...> Search SearXNG and show top results
|
|
```
|
|
|
|
- Open to all users, channel only (no PM)
|
|
- Query is everything after `!searx`
|
|
- Shows top 3 results as `Title -- URL`
|
|
- Titles truncated to 80 characters
|
|
- Query limited to 200 characters
|
|
|
|
Output format:
|
|
|
|
```
|
|
Title One -- https://example.com/page1
|
|
Title Two -- https://example.com/page2
|
|
Title Three -- https://example.com/page3
|
|
```
|
|
|
|
### `!alert` -- Keyword Alert Subscriptions
|
|
|
|
Search keywords across 27 platforms and announce new results. Unlike
|
|
`!rss`/`!yt`/`!twitch` which follow specific channels/feeds, `!alert` searches
|
|
keywords across all supported platforms simultaneously.
|
|
|
|
```
|
|
!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 details for a result
|
|
!alert history <name> [n] Show recent results (default 5, max 20)
|
|
```
|
|
|
|
- `add` and `del` require admin privileges
|
|
- All subcommands must be used in a channel (not PM)
|
|
- Name is required as the first argument after `add`; everything after is the keyword
|
|
- Names must be lowercase alphanumeric + hyphens, 1-20 characters
|
|
- Keywords: 1-100 characters, free-form text
|
|
- Maximum 20 alerts per IRC channel
|
|
|
|
Platforms searched:
|
|
|
|
- **YouTube** (`yt`) -- InnerTube search API (no auth required)
|
|
- **Twitch** (`tw`) -- Public GQL endpoint: live streams and VODs (no auth required)
|
|
- **SearXNG** (`sx`) -- Local SearXNG instance, searches general/news/videos/social media categories filtered to last 24h (no auth required)
|
|
- **Reddit** (`rd`) -- JSON search API, sorted by new, past week (no auth required)
|
|
- **Mastodon** (`ft`) -- Public hashtag timeline across 4 instances (no auth required)
|
|
- **DuckDuckGo** (`dg`) -- HTML lite search endpoint via SOCKS5 proxy (no auth required)
|
|
- **Google News** (`gn`) -- Public RSS feed via SOCKS5 proxy (no auth required)
|
|
- **Kick** (`kk`) -- Public search API: channels and livestreams (no auth required)
|
|
- **Dailymotion** (`dm`) -- Public video API, sorted by recent (no auth required)
|
|
- **PeerTube** (`pt`) -- Federated video search across 4 instances (no auth required)
|
|
- **Bluesky** (`bs`) -- Public post search API via SOCKS5 proxy (no auth required)
|
|
- **Lemmy** (`ly`) -- Federated post search across 4 instances (no auth required)
|
|
- **Odysee** (`od`) -- LBRY JSON-RPC claim search: video, audio, documents (no auth required)
|
|
- **Archive.org** (`ia`) -- Internet Archive advanced search, sorted by date (no auth required)
|
|
- **Hacker News** (`hn`) -- Algolia search API, sorted by date (no auth required)
|
|
- **GitHub** (`gh`) -- Repository search API, sorted by recently updated (no auth required)
|
|
- **Wikipedia** (`wp`) -- MediaWiki search API, English Wikipedia (no auth required)
|
|
- **Stack Exchange** (`se`) -- Stack Overflow search API, sorted by activity (no auth required)
|
|
- **GitLab** (`gl`) -- Public project search API, sorted by last activity (no auth required)
|
|
- **npm** (`nm`) -- npm registry search API (no auth required)
|
|
- **PyPI** (`pp`) -- Recent package updates RSS feed, keyword-filtered (no auth required)
|
|
- **Docker Hub** (`dh`) -- Public repository search API (no auth required)
|
|
- **arXiv** (`ax`) -- Atom search API for academic papers (no auth required)
|
|
- **Lobsters** (`lb`) -- Community link aggregator search (no auth required)
|
|
- **DEV.to** (`dv`) -- Forem articles API, tag-based search (no auth required)
|
|
- **Medium** (`md`) -- Tag-based RSS feed (no auth required)
|
|
- **Hugging Face** (`hf`) -- Model search API, sorted by downloads (no auth required)
|
|
|
|
Backend metadata (shown as `| extra` suffix on titles):
|
|
|
|
| Tag | Metrics | Example |
|
|
|-----|---------|---------|
|
|
| `tw` | viewers / views | `500 viewers`, `1k views` |
|
|
| `rd` | score, comments | `+127 42c` |
|
|
| `ft` | reblogs, favourites | `3rb 12fav` |
|
|
| `bs` | likes, reposts | `5lk 2rp` |
|
|
| `ly` | score, comments | `+15 3c` |
|
|
| `kk` | viewers | `500 viewers` |
|
|
| `dm` | views | `1.2k views` |
|
|
| `pt` | views, likes | `120v 5lk` |
|
|
| `hn` | points, comments | `127pt 42c` |
|
|
| `gh` | stars, forks | `42* 5fk` |
|
|
| `gl` | stars, forks | `42* 5fk` |
|
|
| `se` | score, answers, views | `+5 3a 1.2kv` |
|
|
| `dh` | stars, pulls | `42* 1.2M pulls` |
|
|
| `hf` | downloads, likes | `500dl 12lk` |
|
|
| `dv` | reactions, comments | `+15 3c` |
|
|
|
|
Polling and announcements:
|
|
|
|
- Alerts are polled every 5 minutes by default
|
|
- On `add`, the bot replies immediately; existing results are seeded in the
|
|
background to avoid flooding
|
|
- New results announced as two lines:
|
|
- ACTION: `* derp [name/<tag>/<id>] date - URL`
|
|
- PRIVMSG: `title | extra` (title with compact engagement metrics when available)
|
|
- Tags: `yt`, `tw`, `sx`, `rd`, `ft`, `dg`, `gn`, `kk`, `dm`, `pt`, `bs`, `ly`,
|
|
`od`, `ia`, `hn`, `gh`, `wp`, `se`, `gl`, `nm`, `pp`, `dh`, `ax`, `lb`, `dv`,
|
|
`md`, `hf` -- `<id>` is a short deterministic ID for use with `!alert info`
|
|
- Each platform maintains its own seen list (capped at 200 per platform)
|
|
- Per-backend error tracking with exponential backoff (5+ errors skips
|
|
that backend with increasing cooldown; other backends unaffected)
|
|
- Multi-instance backends (PeerTube, Mastodon, Lemmy, SearXNG) fetch
|
|
concurrently for faster polling
|
|
- Subscriptions persist across bot restarts via `bot.state`
|
|
- Matched results are stored in `data/alert_history.db` (SQLite)
|
|
- `list` shows per-backend error counts next to each alert
|
|
- `check` forces an immediate poll across all platforms
|
|
- `history` queries stored results (titles truncated), most recent first
|
|
|
|
### `!jwt` -- JWT Decoder
|
|
|
|
Decode JSON Web Token header and payload, flag common issues.
|
|
|
|
```
|
|
!jwt eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMTIzIn0.sig
|
|
```
|
|
|
|
Output format:
|
|
|
|
```
|
|
Header: alg=RS256 typ=JWT | sig=43 bytes
|
|
sub=user123
|
|
WARN: expired (2026-03-01 12:00 UTC)
|
|
```
|
|
|
|
Issues detected:
|
|
- `alg=none` (unsigned token)
|
|
- Expired tokens (`exp` in the past)
|
|
- Not-yet-valid tokens (`nbf` in the future)
|
|
|
|
No external dependencies -- pure base64/JSON decoding.
|
|
|
|
### `!mac` -- MAC Address Lookup
|
|
|
|
OUI vendor lookup from IEEE database, random MAC generation.
|
|
|
|
```
|
|
!mac AA:BB:CC:DD:EE:FF Vendor lookup
|
|
!mac AABB.CCDD.EEFF Cisco-style format also accepted
|
|
!mac random Generate random locally-administered MAC
|
|
!mac update Download IEEE OUI database
|
|
```
|
|
|
|
Output format:
|
|
|
|
```
|
|
AA:BB:CC:DD:EE:FF -- Cisco Systems, Inc (OUI: AA:BB:CC)
|
|
Random MAC: 02:4A:F7:3C:91:E2 (locally administered)
|
|
```
|
|
|
|
- Accepts any common MAC format (colon, dash, dot, no separator)
|
|
- Random MACs have the locally-administered bit set and multicast bit cleared
|
|
- OUI database stored at `data/oui.txt`, also downloadable via `scripts/update-data.sh`
|
|
|
|
### `!abuse` -- AbuseIPDB
|
|
|
|
Check IP reputation or report abuse via the AbuseIPDB API.
|
|
|
|
```
|
|
!abuse 8.8.8.8 Check single IP
|
|
!abuse 8.8.8.8 1.1.1.1 Check multiple (max 5)
|
|
!abuse 8.8.8.8 report 14,22 Brute force Report IP (admin)
|
|
```
|
|
|
|
Output format:
|
|
|
|
```
|
|
8.8.8.8 -- Abuse: 0% (0 reports) | ISP: Google LLC | Usage: Data Center | Country: US
|
|
```
|
|
|
|
- API key: set `ABUSEIPDB_API_KEY` env var or `api_key` under `[abuseipdb]` in config
|
|
- Private/loopback IPs are rejected
|
|
- Reporting requires admin privileges
|
|
- Categories are comma-separated numbers per AbuseIPDB docs
|
|
|
|
### `!vt` -- VirusTotal
|
|
|
|
Query VirusTotal API v3 for file hashes, IPs, domains, or URLs.
|
|
|
|
```
|
|
!vt 44d88612fea8a8f36de82e12... File hash (MD5/SHA1/SHA256)
|
|
!vt 8.8.8.8 IP address
|
|
!vt example.com Domain
|
|
!vt https://example.com/page URL
|
|
```
|
|
|
|
Output format:
|
|
|
|
```
|
|
44d88612fea8a8... -- 62/72 detected | trojan, malware | first seen: 2024-01-15
|
|
8.8.8.8 -- 0/94 | AS15169 GOOGLE | Country: US | Reputation: 0
|
|
example.com -- 0/94 | Registrar: Example Inc | Reputation: 0
|
|
```
|
|
|
|
- API key: set `VIRUSTOTAL_API_KEY` env var or `api_key` under `[virustotal]` in config
|
|
- Auto-detects input type from format (hash length, URL scheme, IP, domain)
|
|
- Rate limited to 4 requests per minute (VT free tier)
|
|
- URL IDs are base64url-encoded per VT API spec
|
|
|
|
### `!emailcheck` -- SMTP Email Verification (admin)
|
|
|
|
Verify email deliverability via MX resolution and raw SMTP RCPT TO conversation
|
|
through the SOCKS5 proxy.
|
|
|
|
```
|
|
!emailcheck user@example.com Single check
|
|
!emailcheck user@example.com user2@test.org Batch (max 5)
|
|
```
|
|
|
|
Output format:
|
|
|
|
```
|
|
user@example.com -- SMTP 250 OK (mx: mail.example.com)
|
|
bad@example.com -- SMTP 550 User unknown (mx: mail.example.com)
|
|
```
|
|
|
|
- Admin only (prevents enumeration abuse)
|
|
- Resolves MX records via Tor DNS, falls back to A record
|
|
- Raw SMTP via SOCKS5 proxy: EHLO, MAIL FROM:<>, RCPT TO, QUIT
|
|
- 15-second timeout per connection
|
|
- Max 5 emails per invocation
|
|
|
|
### `!shorten` -- Shorten URL
|
|
|
|
Shorten a URL via FlaskPaste's URL shortener.
|
|
|
|
```
|
|
!shorten https://very-long-url.example.com/path/to/resource
|
|
```
|
|
|
|
Output format:
|
|
|
|
```
|
|
https://paste.mymx.me/s/AbCdEfGh
|
|
```
|
|
|
|
- URL must start with `http://` or `https://`
|
|
- mTLS client cert skips PoW; falls back to PoW challenge if no cert
|
|
- Also used internally by `!alert` to shorten announcement URLs
|
|
|
|
### `!pastemoni` -- Paste Site Keyword Monitor
|
|
|
|
Monitor public paste sites for keywords (data leaks, credential dumps, brand
|
|
mentions). Polls Pastebin's archive and GitHub's public Gists API on a
|
|
schedule, checks new pastes for keyword matches, and announces hits to the
|
|
subscribed IRC channel.
|
|
|
|
```
|
|
!pastemoni add <name> <keyword> Add monitor (admin)
|
|
!pastemoni del <name> Remove monitor (admin)
|
|
!pastemoni list List monitors
|
|
!pastemoni check <name> Force-poll now
|
|
```
|
|
|
|
- `add` and `del` require admin privileges
|
|
- All subcommands must be used in a channel (not PM)
|
|
- Names must be lowercase alphanumeric + hyphens, 1-20 characters
|
|
- Maximum 20 monitors per channel
|
|
|
|
Backends:
|
|
|
|
- **Pastebin** (`pb`) -- Scrapes `pastebin.com/archive` for recent pastes,
|
|
fetches raw content, case-insensitive keyword match against title + content
|
|
- **GitHub Gists** (`gh`) -- Queries `api.github.com/gists/public`, matches
|
|
keyword against description and filenames
|
|
|
|
Polling and announcements:
|
|
|
|
- Monitors are polled every 5 minutes by default
|
|
- On `add`, existing items are seeded in the background (no flood)
|
|
- New matches announced as `[tag] Title -- snippet -- URL`
|
|
- Maximum 5 items announced per backend per poll; excess shown as `... and N more`
|
|
- Titles truncated to 60 characters, snippets to 80 characters
|
|
- 5 consecutive all-backend failures doubles the poll interval (max 1 hour)
|
|
- Subscriptions persist across bot restarts via `bot.state`
|
|
- `list` shows keyword and per-backend error counts
|
|
- `check` forces an immediate poll across all backends
|
|
|
|
### `!internetdb` -- Shodan InternetDB
|
|
|
|
Look up host information from Shodan's free InternetDB API. Returns open ports,
|
|
reverse hostnames, CPE software fingerprints, tags, and known CVEs. No API key
|
|
required.
|
|
|
|
```
|
|
!internetdb 8.8.8.8
|
|
```
|
|
|
|
Output format:
|
|
|
|
```
|
|
8.8.8.8 -- dns.google | Ports: 53, 443 | CPEs: cpe:/a:isc:bind | Tags: cloud
|
|
```
|
|
|
|
- Single IP per query (IPv4 or IPv6)
|
|
- Private/loopback addresses are rejected
|
|
- Hostnames truncated to first 5; CVEs truncated to first 10 (with `+N more`)
|
|
- CPEs truncated to first 8
|
|
- All requests routed through SOCKS5 proxy
|
|
- Returns "no data available" for IPs not in the InternetDB index
|
|
|
|
### `!canary` -- Canary Token Generator
|
|
|
|
Generate realistic-looking credentials for planting as canary tokens (tripwires
|
|
for detecting unauthorized access). Tokens are persisted per-channel.
|
|
|
|
```
|
|
!canary gen db-cred Generate default token (40-char hex)
|
|
!canary gen aws staging-key AWS-style keypair
|
|
!canary gen basic svc-login Username:password pair
|
|
!canary list List canaries in channel
|
|
!canary info db-cred Show full token details
|
|
!canary del db-cred Delete a canary (admin)
|
|
```
|
|
|
|
Token types:
|
|
|
|
| Type | Format | Example |
|
|
|------|--------|---------|
|
|
| `token` | 40-char hex (API key / SHA1) | `a3f8b2c1d4e5...` |
|
|
| `aws` | AKIA access key + base64 secret | `AKIA7X9M2PVL5N...` |
|
|
| `basic` | user:pass pair | `svcadmin:xK9mP2vL5nR8wQ3z` |
|
|
|
|
- `gen` and `del` require admin privileges
|
|
- All subcommands must be used in a channel (not PM)
|
|
- Labels: 1-32 chars, alphanumeric + hyphens + underscores
|
|
- Maximum 50 canaries per channel
|
|
- Persisted via `bot.state` (survives restarts)
|
|
|
|
### `!tcping` -- TCP Connect Latency Probe
|
|
|
|
Measure TCP connect latency to a host:port through the SOCKS5 proxy. Sequential
|
|
probes with min/avg/max summary.
|
|
|
|
```
|
|
!tcping example.com Port 443, 3 probes
|
|
!tcping example.com 22 Port 22, 3 probes
|
|
!tcping example.com 80 5 Port 80, 5 probes
|
|
```
|
|
|
|
Output format:
|
|
|
|
```
|
|
tcping example.com:443 -- 3 probes 1: 45ms 2: 43ms 3: 47ms min/avg/max: 43/45/47 ms
|
|
```
|
|
|
|
- Default port: 443, default count: 3
|
|
- Max count: 10, timeout: 10s per probe
|
|
- Private/reserved addresses rejected
|
|
- Routed through SOCKS5 proxy
|
|
|
|
### `!archive` -- Wayback Machine Save
|
|
|
|
Save a URL to the Wayback Machine via the Save Page Now API.
|
|
|
|
```
|
|
!archive https://example.com/page
|
|
```
|
|
|
|
Output format:
|
|
|
|
```
|
|
Archiving https://example.com/page...
|
|
Archived: https://web.archive.org/web/20260220.../https://example.com/page
|
|
```
|
|
|
|
- URL must start with `http://` or `https://`
|
|
- Timeout: 30s (archiving can be slow)
|
|
- Handles 429 rate limit, 523 origin unreachable
|
|
- Sends acknowledgment before archiving
|
|
- Routed through SOCKS5 proxy
|
|
|
|
### `!resolve` -- Bulk DNS Resolution
|
|
|
|
Resolve multiple hosts via TCP DNS through the SOCKS5 proxy. Concurrent
|
|
resolution with compact output.
|
|
|
|
```
|
|
!resolve example.com github.com A records (default)
|
|
!resolve example.com AAAA Specific record type
|
|
!resolve 1.2.3.4 8.8.8.8 Auto PTR for IPs
|
|
```
|
|
|
|
Output format:
|
|
|
|
```
|
|
example.com -> 93.184.216.34
|
|
github.com -> 140.82.121.3
|
|
badhost.invalid -> NXDOMAIN
|
|
```
|
|
|
|
- Max 10 hosts per invocation
|
|
- Default type: A (auto-detect IP -> PTR)
|
|
- DNS server: 1.1.1.1 (Cloudflare)
|
|
- Concurrent via `asyncio.gather()`
|
|
- Valid types: A, NS, CNAME, SOA, PTR, MX, TXT, AAAA
|
|
|
|
### FlaskPaste Configuration
|
|
|
|
```toml
|
|
[flaskpaste]
|
|
url = "https://paste.mymx.me" # or set FLASKPASTE_URL env var
|
|
```
|
|
|
|
Auth: place client cert/key at `secrets/flaskpaste/derp.crt` and `derp.key`
|
|
for mTLS (bypasses PoW). Without them, PoW challenges are solved per request.
|
|
|
|
### URL Title Preview (urltitle)
|
|
|
|
Automatic URL title preview for channel messages. When a user posts a URL,
|
|
the bot fetches the page title and description and displays a one-line
|
|
preview. No commands -- event-driven only.
|
|
|
|
```
|
|
<alice> check out https://example.com/article
|
|
<derp> ↳ Article Title -- Description of the article...
|
|
```
|
|
|
|
Behavior:
|
|
|
|
- Automatically previews HTTP(S) URLs posted in channel messages
|
|
- Skips private messages, bot's own messages, and command messages (`!prefix`)
|
|
- URLs prefixed with `!` are suppressed: `!https://example.com` produces no preview
|
|
- HEAD-then-GET fetch strategy (checks Content-Type before downloading body)
|
|
- Skips non-HTML content types (images, PDFs, JSON, etc.)
|
|
- Skips binary file extensions (`.png`, `.jpg`, `.pdf`, `.zip`, etc.)
|
|
- Skips FlaskPaste URLs and configured ignore hosts
|
|
- Dedup: same URL only previewed once per cooldown window (5 min default)
|
|
- Max 3 URLs previewed per message (configurable)
|
|
- Title from `og:title` takes priority over `<title>` tag
|
|
- Description from `og:description` takes priority over `<meta name="description">`
|
|
- Title truncated at 200 chars, description at 150 chars
|
|
|
|
Output format:
|
|
|
|
```
|
|
↳ Page Title -- Description truncated to 150 chars...
|
|
↳ Page Title
|
|
```
|
|
|
|
Configuration (optional):
|
|
|
|
```toml
|
|
[urltitle]
|
|
cooldown = 300 # seconds before same URL previewed again
|
|
timeout = 10 # HTTP fetch timeout
|
|
max_urls = 3 # max URLs to preview per message
|
|
ignore_hosts = [] # additional hostnames to skip
|
|
```
|