diff --git a/README.md b/README.md index d2cdb73..022ddea 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ make down # Stop - Async IRC over plain TCP or TLS (SASL PLAIN auth, IRCv3 CAP negotiation) - Microsoft Teams support via outgoing webhooks (no SDK dependency) - Telegram support via long-polling (no SDK dependency, SOCKS5 proxied) +- Mumble support via TCP/TLS protobuf control channel (text only, SOCKS5 proxied) - Plugin system with `@command` and `@event` decorators - Hot-reload: load, unload, reload plugins at runtime - Admin permission system (hostmask patterns + IRCOP detection + AAD IDs) diff --git a/ROADMAP.md b/ROADMAP.md index dac1f81..205f57f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -149,3 +149,13 @@ - [ ] Adaptive Cards for richer formatting (Teams) - [ ] Graph API integration for DMs and richer channel access (Teams) - [ ] Teams event handlers (member join/leave) + +## v2.2.0 -- Protocol Expansion + +- [x] Mumble adapter via TCP/TLS protobuf control channel (text chat only) +- [ ] Discord adapter via WebSocket gateway + REST API +- [ ] Matrix adapter via long-poll `/sync` endpoint +- [ ] XMPP adapter via persistent TCP + XML stanzas (MUC support) +- [ ] Slack adapter via Socket Mode WebSocket +- [ ] Mattermost adapter via WebSocket API +- [ ] Bluesky adapter via AT Protocol firehose + REST API diff --git a/TASKS.md b/TASKS.md index bc3fe3d..314d62e 100644 --- a/TASKS.md +++ b/TASKS.md @@ -1,6 +1,20 @@ # derp - Tasks -## Current Sprint -- v2.1.0 Telegram Integration (2026-02-21) +## Current Sprint -- v2.2.0 Mumble Adapter (2026-02-21) + +| Pri | Status | Task | +|-----|--------|------| +| P0 | [x] | `src/derp/mumble.py` -- MumbleBot, MumbleMessage, protobuf codec | +| P0 | [x] | TCP/TLS connection through SOCKS5 proxy | +| P0 | [x] | Minimal protobuf encoder/decoder (no external protobuf dep) | +| P0 | [x] | Mumble protocol: Version, Authenticate, Ping, TextMessage | +| P0 | [x] | Channel/user state tracking from ChannelState/UserState messages | +| P0 | [x] | `src/derp/config.py` -- `[mumble]` defaults | +| P0 | [x] | `src/derp/cli.py` -- conditionally start MumbleBot | +| P1 | [x] | Tests: `test_mumble.py` (93 cases, 1470 total) | +| P2 | [x] | Documentation update (USAGE.md, CHEATSHEET.md, API.md, README.md, ROADMAP.md) | + +## Previous Sprint -- v2.1.0 Telegram Integration (2026-02-21) | Pri | Status | Task | |-----|--------|------| diff --git a/TODO.md b/TODO.md index f8ac86c..cdd5dd9 100644 --- a/TODO.md +++ b/TODO.md @@ -105,6 +105,61 @@ is preserved in git history for reference. - [ ] Markdown/HTML formatting mode toggle - [ ] Webhook mode (for setWebhook instead of getUpdates) +## Discord + +- [ ] Discord adapter via WebSocket gateway + REST API (no SDK) +- [ ] DiscordBot + DiscordMessage (duck-typed with IRC Message) +- [ ] Gateway intents for message content +- [ ] Message splitting at 2000-char limit +- [ ] Permission tiers via user/role IDs +- [ ] Slash command registration (optional) + +## Matrix + +- [ ] Matrix adapter via long-poll `/sync` endpoint (no SDK) +- [ ] MatrixBot + MatrixMessage (duck-typed with IRC Message) +- [ ] Room-based messaging (rooms map to channels) +- [ ] Power levels map to permission tiers +- [ ] E2EE support (optional, requires libolm) + +## XMPP + +- [ ] XMPP adapter via persistent TCP + XML stanzas (no SDK) +- [ ] XMPPBot + XMPPMessage (duck-typed with IRC Message) +- [ ] MUC (Multi-User Chat) support -- rooms map to channels +- [ ] SASL authentication +- [ ] TLS/STARTTLS connection + +## Mumble + +- [x] Mumble adapter via TCP/TLS + protobuf control channel (no SDK) +- [x] MumbleBot + MumbleMessage (duck-typed with IRC Message) +- [x] Text chat only (no voice) +- [x] Channel-based messaging +- [x] Minimal protobuf encoder/decoder (no protobuf dep) + +## Slack + +- [ ] Slack adapter via Socket Mode WebSocket (no SDK) +- [ ] SlackBot + SlackMessage (duck-typed with IRC Message) +- [ ] OAuth token + WebSocket for events +- [ ] Channel/DM messaging +- [ ] Permission tiers via user IDs + +## Mattermost + +- [ ] Mattermost adapter via WebSocket API (no SDK) +- [ ] MattermostBot + MattermostMessage (duck-typed with IRC Message) +- [ ] Self-hosted Slack alternative +- [ ] Channel/DM messaging + +## Bluesky + +- [ ] Bluesky adapter via AT Protocol firehose + REST API (no SDK) +- [ ] BlueskyBot + BlueskyMessage (duck-typed with IRC Message) +- [ ] Mention-based command dispatch +- [ ] Post/reply via `com.atproto.repo.createRecord` + ## Testing - [x] Plugin command unit tests (encode, hash, dns, cidr, defang) diff --git a/docs/API.md b/docs/API.md index 99fd567..473dde4 100644 --- a/docs/API.md +++ b/docs/API.md @@ -357,6 +357,87 @@ Same stable attributes and methods as `Bot`: --- +## `derp.mumble` -- Mumble Adapter + +Alternative bot adapter for Mumble via TCP/TLS protobuf control channel +(text chat only). All TCP routed through SOCKS5 proxy. Uses a minimal +built-in protobuf encoder/decoder (no external dependency). Exposes the +same plugin API as `derp.bot.Bot`. + +### `MumbleMessage` dataclass + +Duck-typed compatible with IRC `Message`: + +| Field | Type | Description | +|-------|------|-------------| +| `raw` | `dict` | Decoded protobuf fields | +| `nick` | `str \| None` | Sender username (from session lookup) | +| `prefix` | `str \| None` | Sender username (for ACL) | +| `text` | `str \| None` | Message body (HTML stripped) | +| `target` | `str \| None` | channel_id as string (or `"dm"`) | +| `is_channel` | `bool` | `True` for channel msgs, `False` for DMs | +| `command` | `str` | Always `"PRIVMSG"` (compat shim) | +| `params` | `list[str]` | `[target, text]` | +| `tags` | `dict[str, str]` | Empty dict (no IRCv3 tags) | + +### `MumbleBot` + +Same stable attributes and methods as `Bot`: + +| Attribute | Type | Description | +|-----------|------|-------------| +| `name` | `str` | Always `"mumble"` | +| `config` | `dict` | Merged TOML configuration | +| `nick` | `str` | Bot username (from config) | +| `prefix` | `str` | Command prefix (from `[mumble]` or `[bot]`) | +| `state` | `StateStore` | Persistent key-value storage | +| `registry` | `PluginRegistry` | Shared command and event registry | + +**Sending messages** -- same signatures, Mumble protobuf transport: + +| Method | Behaviour | +|--------|-----------| +| `send(target, text)` | TextMessage to channel (HTML-escaped) | +| `reply(msg, text)` | `send(msg.target, text)` | +| `long_reply(msg, lines, *, label="")` | Paste overflow, same logic as IRC | +| `action(target, text)` | Italic HTML text (`...`) | +| `shorten_url(url)` | Same FlaskPaste integration | + +**IRC no-ops** (debug log, no error): + +`join`, `part`, `kick`, `mode`, `set_topic` + +**Plugin management** -- delegates to shared registry: + +`load_plugins`, `load_plugin`, `reload_plugin`, `unload_plugin` + +**Permission tiers** -- same model, exact username string matching: + +`_get_tier(msg)`, `_is_admin(msg)` + +### Protobuf Codec (internal) + +Minimal protobuf wire format helpers -- not part of the stable API: + +| Function | Description | +|----------|-------------| +| `_encode_varint(value)` | Encode unsigned int as protobuf varint | +| `_decode_varint(data, offset)` | Decode varint, returns `(value, offset)` | +| `_encode_field(num, wire_type, value)` | Encode a single protobuf field | +| `_decode_fields(data)` | Decode payload into `{field_num: [values]}` | +| `_pack_msg(msg_type, payload)` | Wrap payload in 6-byte Mumble header | +| `_unpack_header(data)` | Unpack header into `(msg_type, length)` | + +### Helper Functions + +| Function | Signature | Description | +|----------|-----------|-------------| +| `_strip_html` | `(text: str) -> str` | Strip HTML tags and unescape entities | +| `_escape_html` | `(text: str) -> str` | Escape text for Mumble HTML messages | +| `_build_mumble_message` | `(fields, users, our_session) -> MumbleMessage \| None` | Build message from decoded TextMessage fields | + +--- + ## Handler Signatures All command and event handlers are async functions receiving `bot` and diff --git a/docs/CHEATSHEET.md b/docs/CHEATSHEET.md index c0ef64e..bff2d64 100644 --- a/docs/CHEATSHEET.md +++ b/docs/CHEATSHEET.md @@ -521,6 +521,26 @@ Long-polling via `getUpdates` -- no public endpoint needed. All HTTP through SOCKS5 proxy. Strips `@botusername` suffix in groups. Messages split at 4096 chars. IRC-only commands are no-ops. ~90% of plugins work. +## Mumble Integration + +```toml +# config/derp.toml +[mumble] +enabled = true +host = "mumble.example.com" +port = 64738 +username = "derp" +password = "" +tls_verify = false # self-signed certs common +admins = ["admin_user"] # Mumble usernames +operators = [] +trusted = [] +``` + +TCP/TLS via SOCKS5 proxy. Text chat only (no voice). Minimal protobuf +codec (no external dep). HTML stripped on receive, escaped on send. +IRC-only commands are no-ops. ~90% of plugins work. + ## Plugin Template ```python diff --git a/docs/USAGE.md b/docs/USAGE.md index 0cb215f..0831326 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -1474,3 +1474,83 @@ this automatically: `!help@mybot` becomes `!help`. All HTTP traffic (API calls, long-polling) routes through the SOCKS5 proxy at `127.0.0.1:1080` via `derp.http.urlopen`. No direct outbound connections are made. + +## Mumble Integration + +Connect derp to a Mumble server via TCP/TLS protobuf control channel. +Text chat only (no voice). All TCP is routed through the SOCKS5 proxy. +No protobuf library dependency -- uses a minimal built-in varint/field +encoder/decoder for the ~7 message types needed. + +### How It Works + +The bot connects to the Mumble server over TLS (via SOCKS5), sends +Version and Authenticate messages, then enters a read loop. It tracks +channels (ChannelState), users (UserState), and dispatches commands +from TextMessage messages through the shared plugin registry. + +### Configuration + +```toml +[mumble] +enabled = true +host = "mumble.example.com" # Mumble server hostname +port = 64738 # Default Mumble port +username = "derp" # Bot username +password = "" # Server password (optional) +tls_verify = false # Mumble commonly uses self-signed certs +admins = ["admin_user"] # Mumble usernames +operators = [] # Mumble usernames +trusted = [] # Mumble usernames +``` + +### Mumble Setup + +1. **Ensure a Mumble server** (Murmur/Mumble-server) is running and + accessible through the SOCKS5 proxy + +2. **Configure the bot** with the server hostname, port, and credentials + +3. **Configure permissions** using Mumble registered usernames. Use + `!whoami` to discover your username as the bot sees it. + +### Permission Tiers + +Same 4-tier model as IRC, but matches exact Mumble usernames instead of +fnmatch hostmask patterns: + +```toml +[mumble] +admins = ["admin_user"] +operators = ["oper_user"] +trusted = ["trusted_user"] +``` + +### Plugin Compatibility + +Same compatibility as Teams/Telegram -- ~90% of plugins work without +modification. + +| Feature | IRC | Mumble | +|---------|-----|--------| +| `bot.reply()` | Sends PRIVMSG | TextMessage to channel | +| `bot.send()` | Sends PRIVMSG | TextMessage to channel | +| `bot.action()` | CTCP ACTION | Italic HTML text (`...`) | +| `bot.long_reply()` | Paste overflow | Paste overflow (same logic) | +| `bot.state` | Per-server SQLite | Per-server SQLite | +| `bot.join/part/kick/mode` | IRC commands | No-op (logged at debug) | +| Event handlers (JOIN, etc.) | Fired on IRC events | Not triggered | +| Hostmask ACL | fnmatch patterns | Exact usernames | + +### Text Encoding + +Mumble uses HTML for text messages. On receive, the bot strips tags and +unescapes entities. On send, text is HTML-escaped. Action messages use +`` tags for italic formatting. + +### Transport + +All TCP connections route through the SOCKS5 proxy at `127.0.0.1:1080` +via `derp.http.create_connection`. TLS is applied on top of the proxied +socket. Mumble commonly uses self-signed certificates, so `tls_verify` +defaults to `false`.