docs: update docs for Telegram integration

This commit is contained in:
user
2026-02-21 20:06:29 +01:00
parent 3bcba8b0a9
commit 0d92e6ed31
7 changed files with 199 additions and 4 deletions

View File

@@ -25,6 +25,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)
- Plugin system with `@command` and `@event` decorators
- Hot-reload: load, unload, reload plugins at runtime
- Admin permission system (hostmask patterns + IRCOP detection + AAD IDs)

View File

@@ -129,7 +129,7 @@
- [x] Plugin command unit tests (encode, hash, dns, cidr, defang)
- [x] CI pipeline (Gitea Actions, Python 3.11-3.13, ruff + pytest)
## v2.1.0 -- Teams Integration
## v2.1.0 -- Teams + Telegram Integration
- [x] Microsoft Teams adapter via outgoing webhooks (no SDK)
- [x] `TeamsBot` class with same plugin API as IRC `Bot`
@@ -139,6 +139,13 @@
- [x] IRC-only methods as no-ops (join, part, kick, mode, set_topic)
- [x] Incoming webhook support for `send()` (proactive messages)
- [x] Paste overflow via FlaskPaste (same as IRC)
- [ ] Adaptive Cards for richer formatting
- [ ] Graph API integration for DMs and richer channel access
- [x] Teams `send()` routed through SOCKS5 proxy (bug fix)
- [x] Telegram adapter via long-polling (`getUpdates`, no SDK)
- [x] `TelegramBot` class with same plugin API as IRC `Bot`
- [x] `TelegramMessage` dataclass duck-typed with IRC `Message`
- [x] All Telegram HTTP through SOCKS5 proxy
- [x] Message splitting at 4096-char limit
- [x] `@botusername` suffix stripping in groups
- [ ] Adaptive Cards for richer formatting (Teams)
- [ ] Graph API integration for DMs and richer channel access (Teams)
- [ ] Teams event handlers (member join/leave)

View File

@@ -1,6 +1,20 @@
# derp - Tasks
## Current Sprint -- v2.1.0 Teams Integration (2026-02-21)
## Current Sprint -- v2.1.0 Telegram Integration (2026-02-21)
| Pri | Status | Task |
|-----|--------|------|
| P0 | [x] | Fix `src/derp/teams.py` -- route `send()` through SOCKS5 proxy |
| P0 | [x] | `src/derp/telegram.py` -- TelegramBot, TelegramMessage, long-polling |
| P0 | [x] | `src/derp/config.py` -- `[telegram]` defaults |
| P0 | [x] | `src/derp/cli.py` -- conditionally start TelegramBot |
| P0 | [x] | All Telegram HTTP through SOCKS5 proxy (`derp.http.urlopen`) |
| P0 | [x] | Permission tiers via user IDs (exact match) |
| P0 | [x] | @botusername suffix stripping, message splitting (4096 chars) |
| P1 | [x] | Tests: `test_telegram.py` (75 cases) |
| P2 | [x] | Documentation update (USAGE.md, CHEATSHEET.md, API.md, README.md, ROADMAP.md) |
## Previous Sprint -- v2.1.0 Teams Integration (2026-02-21)
| Pri | Status | Task |
|-----|--------|------|

13
TODO.md
View File

@@ -88,10 +88,23 @@ is preserved in git history for reference.
- [x] TeamsBot + TeamsMessage (duck-typed with IRC Message)
- [x] HMAC-SHA256 webhook validation
- [x] Permission tiers via AAD object IDs
- [x] Route `send()` through SOCKS5 proxy (bug fix)
- [ ] Adaptive Cards for richer formatting
- [ ] Graph API integration for DMs
- [ ] Teams event handlers (member join/leave)
## Telegram
- [x] Telegram adapter via long-polling (no SDK)
- [x] TelegramBot + TelegramMessage (duck-typed with IRC Message)
- [x] All HTTP through SOCKS5 proxy
- [x] Message splitting at 4096-char limit
- [x] @botusername suffix stripping in groups
- [x] Permission tiers via user IDs
- [ ] Inline keyboard support for interactive replies
- [ ] Markdown/HTML formatting mode toggle
- [ ] Webhook mode (for setWebhook instead of getUpdates)
## Testing
- [x] Plugin command unit tests (encode, hash, dns, cidr, defang)

View File

@@ -288,6 +288,75 @@ Same stable attributes and methods as `Bot`:
---
## `derp.telegram` -- Telegram Adapter
Alternative bot adapter for Telegram via long-polling (`getUpdates`).
All HTTP routed through SOCKS5 proxy. Exposes the same plugin API as
`derp.bot.Bot` so protocol-agnostic plugins work without modification.
### `TelegramMessage` dataclass
Duck-typed compatible with IRC `Message`:
| Field | Type | Description |
|-------|------|-------------|
| `raw` | `dict` | Original Telegram Update |
| `nick` | `str \| None` | Sender first_name (or username fallback) |
| `prefix` | `str \| None` | Sender user_id as string (for ACL) |
| `text` | `str \| None` | Message body (stripped of @bot suffix) |
| `target` | `str \| None` | chat_id as string |
| `is_channel` | `bool` | `True` for groups, `False` for DMs |
| `command` | `str` | Always `"PRIVMSG"` (compat shim) |
| `params` | `list[str]` | `[target, text]` |
| `tags` | `dict[str, str]` | Empty dict (no IRCv3 tags) |
### `TelegramBot`
Same stable attributes and methods as `Bot`:
| Attribute | Type | Description |
|-----------|------|-------------|
| `name` | `str` | Always `"telegram"` |
| `config` | `dict` | Merged TOML configuration |
| `nick` | `str` | Bot display name (from `getMe`) |
| `prefix` | `str` | Command prefix (from `[telegram]` or `[bot]`) |
| `state` | `StateStore` | Persistent key-value storage |
| `registry` | `PluginRegistry` | Shared command and event registry |
**Sending messages** -- same signatures, Telegram API transport:
| Method | Behaviour |
|--------|-----------|
| `send(target, text)` | `sendMessage` API call (proxied, rate-limited) |
| `reply(msg, text)` | `send(msg.target, text)` |
| `long_reply(msg, lines, *, label="")` | Paste overflow, same logic as IRC |
| `action(target, text)` | Italic Markdown text via `sendMessage` |
| `shorten_url(url)` | Same FlaskPaste integration |
**Message splitting**: messages > 4096 chars split at line boundaries.
**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 user_id string matching:
`_get_tier(msg)`, `_is_admin(msg)`
### Helper Functions
| Function | Signature | Description |
|----------|-----------|-------------|
| `_strip_bot_suffix` | `(text: str, bot_username: str) -> str` | Strip `@username` from command text |
| `_build_telegram_message` | `(update: dict, bot_username: str) -> TelegramMessage \| None` | Parse Telegram Update into message |
| `_split_message` | `(text: str, max_len: int = 4096) -> list[str]` | Split long text at line boundaries |
---
## Handler Signatures
All command and event handlers are async functions receiving `bot` and

View File

@@ -504,6 +504,23 @@ Teams endpoint: `POST /api/messages`. HMAC-SHA256 auth via `Authorization: HMAC
Replies returned as JSON in HTTP response. IRC-only commands (kick, ban, topic) are no-ops.
~90% of plugins work without modification.
## Telegram Integration
```toml
# config/derp.toml
[telegram]
enabled = true
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. 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.
## Plugin Template
```python

View File

@@ -1400,3 +1400,77 @@ The bot returns a JSON response:
```
Multiple reply lines are joined with `\n`.
## Telegram Integration
Connect derp to Telegram via long-polling (`getUpdates`). All outbound HTTP
is routed through the SOCKS5 proxy. No public endpoint required, no Telegram
SDK dependency.
### How It Works
The bot calls `getUpdates` in a loop with a long-poll timeout (default 30s).
When a message arrives with the configured prefix, it is dispatched through
the shared plugin registry. Replies are sent immediately via `sendMessage`.
### Configuration
```toml
[telegram]
enabled = true
bot_token = "123456:ABC-DEF..." # from @BotFather
poll_timeout = 30 # long-poll timeout in seconds
admins = [123456789] # Telegram user IDs (numeric)
operators = [] # Telegram user IDs
trusted = [] # Telegram user IDs
```
### Telegram Setup
1. **Create a bot** via [@BotFather](https://t.me/BotFather):
- `/newbot` and follow the prompts
- Copy the bot token and set `bot_token` in config
2. **Add the bot** to a group or send it a DM
3. **Configure permissions** using Telegram user IDs. Use `!whoami` to
discover your numeric user ID.
### Permission Tiers
Same 4-tier model as IRC, but matches exact Telegram user IDs (numeric
strings) instead of fnmatch hostmask patterns:
```toml
[telegram]
admins = [123456789]
operators = [987654321]
trusted = [111222333]
```
### Plugin Compatibility
Same compatibility as Teams -- ~90% of plugins work without modification.
| Feature | IRC | Telegram |
|---------|-----|----------|
| `bot.reply()` | Sends PRIVMSG | `sendMessage` API call |
| `bot.send()` | Sends PRIVMSG | `sendMessage` API call |
| `bot.action()` | CTCP ACTION | Italic Markdown 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 user IDs |
| Message limit | 512 bytes (IRC) | 4096 chars (Telegram) |
### Group Commands
In groups, Telegram appends `@botusername` to commands. The bot strips
this automatically: `!help@mybot` becomes `!help`.
### Transport
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.