feat: add IRCv3 cap negotiation, channel management, state persistence
Implement CAP LS 302 flow with configurable ircv3_caps list, replacing the minimal SASL-only registration. Parse IRCv3 message tags (@key=value) with proper value unescaping. Add channel management plugin (kick, ban, unban, topic, mode) and bot API methods. Add SQLite key-value StateStore for plugin state persistence with !state inspection command. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,37 @@ admins = ["*!~user@trusted.host", "ops!*@*.ops.net"]
|
||||
|
||||
IRC operators are auto-detected via WHO. Hostmask patterns use fnmatch.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
```toml
|
||||
# config/derp.toml
|
||||
[server]
|
||||
ircv3_caps = ["multi-prefix", "away-notify", "server-time"]
|
||||
```
|
||||
|
||||
SASL auto-added when sasl_user/sasl_pass configured.
|
||||
|
||||
## Plugin Management (admin)
|
||||
|
||||
```
|
||||
@@ -232,6 +263,7 @@ 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
|
||||
```
|
||||
|
||||
## Config Locations
|
||||
|
||||
@@ -35,6 +35,13 @@ 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
|
||||
@@ -66,6 +73,12 @@ level = "info" # Logging level: debug, info, warning, error
|
||||
| `!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) |
|
||||
| `!encode <fmt> <text>` | Encode text (b64, hex, url, rot13) |
|
||||
| `!decode <fmt> <text>` | Decode text (b64, hex, url, rot13) |
|
||||
@@ -156,7 +169,8 @@ are configured.
|
||||
| `!whoami` | Show your hostmask and admin status |
|
||||
| `!admins` | Show configured patterns and detected opers (admin) |
|
||||
|
||||
Admin-restricted commands: `!load`, `!reload`, `!unload`, `!admins`.
|
||||
Admin-restricted commands: `!load`, `!reload`, `!unload`, `!admins`, `!state`,
|
||||
`!kick`, `!ban`, `!unban`, `!topic`, `!mode`.
|
||||
|
||||
### Writing Admin Commands
|
||||
|
||||
@@ -166,6 +180,76 @@ 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.
|
||||
|
||||
## 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
|
||||
@@ -254,6 +338,10 @@ The `bot` object provides:
|
||||
| `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:
|
||||
|
||||
@@ -266,3 +354,4 @@ The `message` object provides:
|
||||
| `message.text` | Trailing text content |
|
||||
| `message.is_channel` | Whether target is a channel |
|
||||
| `message.params` | All message parameters |
|
||||
| `message.tags` | IRCv3 message tags (dict) |
|
||||
|
||||
Reference in New Issue
Block a user