Skip to content

feat: telegram bot command receiver (closes #135)#153

Merged
luceinaltis merged 1 commit into
mainfrom
feat/135-telegram-bot-command-receiver
Apr 9, 2026
Merged

feat: telegram bot command receiver (closes #135)#153
luceinaltis merged 1 commit into
mainfrom
feat/135-telegram-bot-command-receiver

Conversation

@luceinaltis
Copy link
Copy Markdown
Owner

Summary

Adds inbound command receiving via the Telegram getUpdates long-polling API so users can interact with qracer remotely from chat. Builds on the existing TelegramAdapter — stdlib urllib only, no new dependencies.

  • New TelegramBotPoller in qracer/notifications/telegram_poller.py:
    • Long-polls getUpdates, tracks the update offset so messages are never returned twice.
    • Filters by the authorised chat_id (other chats are silently ignored).
    • Parses incoming text into BotCommand objects.
    • send_reply() posts plain-text replies, auto-truncating to fit Telegram's per-message limit (default 4000 chars).
    • All HTTP/JSON errors are caught, logged, and converted to an empty result so the server tick keeps running.
  • New BotCommand dataclass with a parse(text) classmethod that strips the @botname suffix groups append (e.g. /analyze@qracerbot AAPL) and lower-cases the action.
  • Server gains an optional telegram_poller constructor param. _tick() polls when configured (failures are logged, never raised) and calls _handle_bot_command for each command, replying via poller.send_reply.
  • Commands implemented in Server._dispatch_bot_command:
    • /help (and /start) — usage text
    • /status — uptime, notification channels, autonomous on/off
    • /alerts — list active alerts via the existing AlertStore
    • /alert TICKER above|below PRICE — create a price alert
    • /tasks — list active scheduled tasks via the existing TaskStore
    • /schedule ACTION TICKER SCHEDULE — schedule a task (e.g. /schedule analyze AAPL every 1h)
    • /analyze and /portfolio — reply with a friendly "use the qracer CLI" message; deeper engine plumbing for these is out of scope for this issue.
    • Unknown commands fall through to a /help hint.
  • build_telegram_poller factory in qracer/notifications/factory.py mirrors build_notification_registry and returns None when credentials are missing.
  • cli serve instantiates the poller from TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID and announces "Telegram bot: receiving commands" at startup.

Closes #135.

How to test it

Automated:

uv run pytest tests/notifications tests/test_server.py
uv run ruff check qracer/ tests/
uv run ruff format --check qracer/ tests/

I added 30 unit tests for the poller (tests/notifications/test_telegram_poller.py) covering:

  • BotCommand.parse happy paths and rejection cases (empty, whitespace, non-slash, lone slash, /@bot).
  • Construction validation (missing token / chat id).
  • poll() parsing, offset tracking, chat-id filtering, non-text message skipping, non-command-text skipping.
  • Offset is sent on subsequent calls.
  • HTTP / URL / JSON / ok=false errors all return [] without raising.
  • send_reply() success, truncation, empty-text short-circuit, HTTP / URL errors.

I extended tests/test_server.py with 22 tests covering:

  • _tick polls Telegram when configured, skips when not, dispatches commands and replies.
  • Poll failures and dispatch exceptions are caught and produce an error reply (not a crash).
  • Each _cmd_* handler in isolation, plus _format_duration.

Manual smoke test (requires real bot):

export TELEGRAM_BOT_TOKEN=...   # from @BotFather
export TELEGRAM_CHAT_ID=...     # your chat id
qracer serve
# In Telegram, message your bot:
/help
/status
/alerts
/alert AAPL above 200
/tasks
/schedule analyze AAPL every 1h

You should see each command echoed back as a reply within ~1s of sending.

Add inbound command receiving via the Telegram getUpdates long-polling
API so users can interact with qracer remotely from chat. Builds on the
existing TelegramAdapter (urllib only — no new dependencies).

- TelegramBotPoller: long-polls getUpdates, tracks offset, filters by
  authorised chat_id, parses messages into BotCommand objects, and can
  send plain-text replies (auto-truncated to fit Telegram's limit).
- BotCommand.parse: parses "/action arg1 arg2" with @botName suffix
  stripping and case folding.
- Server: optional telegram_poller wired into _tick(); dispatches
  /status, /alerts, /alert, /tasks, /schedule, /help (and /start) using
  the existing alert and task stores. /analyze and /portfolio reply
  with a "use the CLI" message until deeper engine plumbing exists.
- build_telegram_poller factory in notifications.factory.
- cli serve: instantiates the poller from credentials and announces
  bot mode at startup.
@luceinaltis luceinaltis merged commit 52dde68 into main Apr 9, 2026
3 checks passed
@luceinaltis luceinaltis deleted the feat/135-telegram-bot-command-receiver branch April 9, 2026 00:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Telegram bot command receiver (two-way communication)

2 participants