- Plugin registry: add unload_plugin(), reload_plugin(), path tracking - Bot: add load_plugin(), reload_plugin(), unload_plugin() public API - Core plugin: add !load, !reload, !unload, !plugins commands - Command dispatch: support unambiguous prefix matching (!h -> !help) - Help: support !help <plugin> to show plugin description and commands - Tests: 17 new tests covering hot-reload, prefix matching Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
154 lines
4.3 KiB
Markdown
154 lines
4.3 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 |
|
|
| `-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)
|
|
|
|
[bot]
|
|
prefix = "!" # Command prefix character
|
|
channels = ["#test"] # Channels to join on connect
|
|
plugins_dir = "plugins" # Plugin directory path
|
|
|
|
[logging]
|
|
level = "info" # Logging level: debug, info, warning, error
|
|
```
|
|
|
|
## 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 |
|
|
| `!echo <text>` | Echo back text (example plugin) |
|
|
| `!cert <domain> [...]` | Lookup CT logs for up to 5 domains |
|
|
| `!load <plugin>` | Hot-load a plugin from the plugins directory |
|
|
| `!reload <plugin>` | Reload a plugin, picking up file changes |
|
|
| `!unload <plugin>` | Unload a plugin, removing its handlers |
|
|
| `!plugins` | List loaded plugins with handler counts |
|
|
|
|
### 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
|
|
|
|
## 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 |
|
|
|
|
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 |
|