Add optional Telegram capture bot companion app#60
Merged
Conversation
A separate, port-less CapRover app that lets you save a thought into memory by sending a Telegram message. It long-polls the Telegram Bot API (no inbound webhook/port needed) and stores each message via POST /api/v1/memories, tagged agent_id=capture:telegram. Adapts the OB1 project's chat-capture integration. Security: the store is single-user and high-trust, so the bot only saves messages from an allowlist of chat IDs (TELEGRAM_ALLOWED_CHAT_IDS). With the allowlist blank it runs in discovery mode — it replies with your chat id and stores nothing — so you can authorize yourself on first run. - capture/capture.py: pure, unit-tested helpers (parse_allowed_chat_ids, extract_message, classify, process_update) + a resilient run() long-poll loop that survives transient errors and bad updates. Self-contained (httpx only). - capture/Dockerfile (python:3.12-alpine worker), requirements.txt, captain-definition. - tests/test_capture.py: 11 cases (helpers + respx-mocked getUpdates/post_memory/ sendMessage + process_update auth/discovery/help/empty/non-text paths). - docs: USER_GUIDE deploy section (BotFather + discovery flow, env table, security note, docker run) + troubleshooting rows; DEVELOPER_GUIDE layout; CLAUDE.md companion-app note. Closes #55. https://claude.ai/code/session_017835DVrvURaYnbQiPQwzue
There was a problem hiding this comment.
Pull request overview
This PR adds an optional, standalone “capture” companion app that long-polls the Telegram Bot API and forwards authorized chat messages to memserv via POST /api/v1/memories with provenance agent_id=capture:telegram, plus tests and documentation to support deployment and operation.
Changes:
- Added a new
capture/companion app (Dockerized) implementing Telegram long-polling, allowlist/discovery-mode authorization, and REST writes to/api/v1/memories. - Added a dedicated pytest suite for capture parsing/classification and network interactions (Telegram + memserv) using
respx. - Updated user/developer docs and
CLAUDE.mdto describe deploying and maintaining the new optional capture bot.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_capture.py |
Adds unit + mocked-network coverage for the capture bot helpers and update processing. |
docs/USER_GUIDE.md |
Documents CapRover + Docker deployment, env vars, security notes, and troubleshooting for the capture bot. |
docs/DEVELOPER_GUIDE.md |
Adds capture/ to the repository layout documentation. |
CLAUDE.md |
Notes capture/ as a companion app and summarizes its behavior/config. |
capture/requirements.txt |
Defines the capture app’s minimal dependency set (httpx). |
capture/Dockerfile |
Builds a long-running worker image for the capture bot. |
capture/capture.py |
Implements allowlist/discovery-mode auth, Telegram polling, message parsing/classification, and REST posting to memserv. |
capture/captain-definition |
CapRover build descriptor for the capture app. |
capture/__init__.py |
Marks capture/ as an importable package for the test suite. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… validate poll timeout
- get_updates(): Telegram reports errors as HTTP 200 with {"ok": false}; treat
non-ok / non-dict bodies as errors so the run loop logs and backs off instead
of silently idling (e.g. on an invalid token).
- process_update(): on a save failure, send the user a stable, non-revealing
message and keep the full exception in logs only (no URL/detail disclosure).
- TELEGRAM_POLL_TIMEOUT: parse via a validating _int_env() that fails fast with a
clear message on a non-integer or non-positive value, instead of a traceback.
- tests: get_updates ok:false raises, _int_env validation, and the generic
save-failure message (asserts no internal detail leaks).
https://claude.ai/code/session_017835DVrvURaYnbQiPQwzue
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an optional capture bot so you can save a thought into memory by sending a Telegram message — frictionless capture from your phone. Adapts the OB1 / Open Brain chat-capture integration (backlog issue #55) to this server.
It's a separate, port-less CapRover app (like
backup//digest/). It long-polls the Telegram Bot API — no inbound webhook, no public port — and stores each message viaPOST /api/v1/memories, taggedagent_id=capture:telegram.Security (single-user, high-trust store)
The bot only saves messages from an allowlist of Telegram chat IDs (
TELEGRAM_ALLOWED_CHAT_IDS). With the allowlist blank it runs in discovery mode — it replies with your chat id and stores nothing — so you can authorize yourself on first run without leaking write access. Anyone not on the allowlist is refused.How it works
long-poll getUpdates → for each message: classify → auth-check → POST /api/v1/memories → reply./note <text>(and/save,/remember) also work;/startand/helpshow usage.app/or the main image.run()loop is resilient: transient Telegram/API errors back off and retry, and one bad update can't kill the loop.Files
capture/capture.py— pure helpers (parse_allowed_chat_ids,extract_message,classify,process_update) +run().capture/Dockerfile(python:3.12-alpinelong-running worker),requirements.txt,captain-definition.Tests
tests/test_capture.py— 11 cases: allowlist parsing, message/edited-message extraction, command classification, respx-mockedgetUpdates/post_memory(bearer + provenance)/sendMessage, andprocess_updatefor the authorized-save, unauthorized-reject, discovery-mode, help, empty-note, and non-text-update paths.ruffclean. Verified the required-env guard exits non-zero.Docs
docker runexample; plus troubleshooting rows.Architecture notes
No changes to the main app, its invariants, or runtime deps. The bot is just another authenticated REST client in its own container. Extending to Slack slash commands / Discord bots is straightforward: parse the inbound message, then call the same endpoint.
Closes #55.
https://claude.ai/code/session_017835DVrvURaYnbQiPQwzue
Generated by Claude Code