feat: queue telegram requests with rate limits (#54)
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# Telegram Transport
|
||||
|
||||
## Overview
|
||||
|
||||
`TelegramClient` is the single transport for Telegram writes. It owns a
|
||||
`TelegramOutbox` that serializes send/edit/delete operations, applies
|
||||
coalescing, and enforces rate limits + retry-after backoff.
|
||||
|
||||
This document captures current behavior so transport changes stay intentional.
|
||||
|
||||
## Flow
|
||||
|
||||
1. CLI emits JSON events.
|
||||
2. We render progress on every step and diff against the last output.
|
||||
3. Only deltas enqueue a Telegram edit.
|
||||
4. High-value messages enqueue a send.
|
||||
5. All writes go through the outbox.
|
||||
|
||||
## Outbox model
|
||||
|
||||
- Single worker processes one op at a time.
|
||||
- Each op is keyed; only one pending op per key.
|
||||
- New ops with the same key overwrite the payload but **do not** reset
|
||||
`queued_at` (fairness).
|
||||
|
||||
Keys (include `chat_id` to avoid cross-chat collisions):
|
||||
|
||||
- `("edit", chat_id, message_id)` for edits (coalesced).
|
||||
- `("delete", chat_id, message_id)` for deletes.
|
||||
- `("send", chat_id, replace_message_id)` when replacing a progress message.
|
||||
- Unique key for normal sends.
|
||||
|
||||
Scheduling:
|
||||
|
||||
- Ordered by `(priority, queued_at)`.
|
||||
- Priorities: send=0, delete=1, edit=2.
|
||||
- Within a priority tier, the oldest pending op runs first.
|
||||
- `updated_at` is kept for debugging only.
|
||||
|
||||
## Rate limiting + backoff
|
||||
|
||||
- Per-chat pacing is computed from `private_chat_rps` and `group_chat_rps`.
|
||||
Defaults: 1.0 msg/s for private, 20/60 msg/s for groups (≈1 message every 3s).
|
||||
- Pacing is currently enforced via a single global `next_at`; per-chat
|
||||
`next_at` is a future consideration if we ever run multiple chats in parallel.
|
||||
- The worker waits until `max(next_at, retry_at)` before executing the next op.
|
||||
- On 429, `RetryAfter` is raised using `parameters.retry_after` when present;
|
||||
if missing, we fall back to a 5s delay. The outbox sets `retry_at` and
|
||||
requeues the op if no newer op for the same key has arrived.
|
||||
|
||||
## Error handling
|
||||
|
||||
- Non-429 errors are logged and dropped (no retry).
|
||||
- On `RetryAfter`, the op is retried unless a newer op superseded the same key.
|
||||
|
||||
## Replace progress messages
|
||||
|
||||
`send_message(replace_message_id=...)`:
|
||||
|
||||
- Drops any pending edit for that progress message.
|
||||
- Enqueues the send at highest priority.
|
||||
- If the send succeeds, enqueues a delete for the old progress message.
|
||||
|
||||
This keeps the final message first and avoids deleting progress if the send
|
||||
fails.
|
||||
|
||||
## getUpdates
|
||||
|
||||
`get_updates` bypasses the outbox and retries on `RetryAfter` by sleeping
|
||||
for the provided delay.
|
||||
|
||||
## Close semantics
|
||||
|
||||
`TelegramClient.close()` shuts down the outbox and closes the HTTP client.
|
||||
Pending ops are failed with `None` (best-effort).
|
||||
Reference in New Issue
Block a user