feat: telegram-lurker plugin for chat archival to git #208
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Competitor
Kind/Documentation
Kind/Enhancement
Kind/Epic
Kind/Feature
Kind/Security
Kind/Story
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Scope/Core
Scope/Cross-Plugin
Scope/Plugin-System
Scope/Single-Plugin
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
4 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ultanio/cobot!208
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feature/telegram-lurker"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Telegram Lurker Plugin
Silent chat archival plugin that logs Telegram conversations to a git repo as JSONL.
Features
/logon [channel|all]//logoff [channel|all]commandschannels/{id}/{date}.jsonlConfiguration
Dependencies
cobot-telegramwithregister_command()API (separate PR needed for cobot-telegram)Tests
17 tests passing — covers plugin meta, config, state persistence, JSONL writing, message filtering, logon/logoff commands.
Generated via BMad Method quick-spec → quick-dev workflow
8e9bc3bab8b00eab43b3Review: telegram-lurker plugin
Nice clean plugin — well-structured, good test coverage (17 tests), and the JSONL-per-channel-per-day approach is solid for archival. Some observations:
👍 What works well
telegram.on_messageviaregister_handler+register_commandis exactly how the plugin architecture should be used. No monkey-patching.active_channelssurvives restarts via JSON state file inside the repo. Simple and correct.tempfilefor isolation.⚠️ Issues to address
1.
_on_messageis sync but called from async contextThe handler is registered as a plain sync function. This works for now because
_write_jsonldoes synchronous file I/O, but it blocks the event loop. For a lurker writing to disk on every message in busy channels, this could become a bottleneck. Consider making itasyncand usingaiofiles, or offloading writes to a thread viaasyncio.to_thread.2.
_groupsaccess pattern assumes list, code has dictIn
_handle_logonwithallarg, you iterateself._telegram_plugin._groupsand access.idon each item — treating it as a list of objects. But looking atTelegramPlugin,_groupsis adict[int, GroupConfig]. This will crash:Should be
self._telegram_plugin._groups.values()or iterate keys directly.3. No error handling in
_write_jsonlIf the disk is full or permissions are wrong, the
open()call will throw and the message is silently lost. At minimum, catchIOErrorand log it — for an archival plugin, silent data loss is the worst failure mode.4. Missing
.gitignorefor state file.lurker-state.jsoncontains runtime state (active channel IDs) and gets committed alongside the JSONL archive. This is probably intentional (state survives repo clone), but worth a comment — or exclude it if you don't want operational state in the archive history.5. No media capture
The telegram plugin fires
telegram.on_mediawith local paths, but the lurker only capturestext. For a chat archive, at least logging the media metadata (type, file_id, filename) in the JSONL record would make the archive more complete, even without downloading files.📝 Minor / style
_telegram_pluginaccess viaself._registry.get("telegram")— there's no null check if the registry itself is None at that point instart(). The outerif self._registrycheck covers it, but if telegram is truly a hard dependency, consider failing loudly instead of silently skipping.Verdict
The plugin code itself is clean and does what it says. Fix the
_groupsdict iteration bug (#2) — that's a runtime crash. The rest are improvements that can land in follow-ups. The bundled PRD/validation docs should ideally be a separate PR.b00eab43b35030fcd2d6Review: REQUEST CHANGES
🔴 Critical
print()instead ofself.log_*()— violates plugin design guide. Useself.log_info(),self.log_warn(),self.log_error()throughout.asyncio.create_subprocess_execwithout timeout can hang forever if git locks up. Addasyncio.wait_for()wrapper.json.dump()to file. Use tempfile+rename pattern (see filedrop plugin for example).🟡 Important
filters.ALL & ~filters.COMMAND, so command messages (including /logon /logoff) are excluded fromtelegram.on_message. The lurker won't log its own activation commands. Consider whether this is intentional._bmad-output/with PRD validation report (512 lines) and full PRD (770 lines) probably belong in a wiki or separate branch, not in the cobot source tree.🟢 Good
register_command()API addition to telegram pluginMissing tests
Overall the architecture is sound — just needs hardening before merge.
5030fcd2d621c89e185521c89e185504fae7cd20Bug Report from Zeus — Live Testing Results
Zeus tested this PR live on olymp with a dedicated Lurker bot. Two issues found:
Bug 1: /logon command does not work (HIGH)
register_command()registersCommandHandlers on the TelegramApplicationobject, but cobot's telegram plugin polls viagetUpdatesdirectly and never callsApplication.process_update()orrun_polling(). So CommandHandlers are registered but never dispatched.Fix options:
poll_updatesflow by checking for/logonand/logoffprefixes in message textBug 2: Hot-reload triggers on every message (LOW)
The hotreload plugin detects changes and restarts the bot after each received message — likely because the state file or JSONL log write triggers the filesystem watcher.
Fix options:
lurker-archive/(or configuredrepo_path) to the hotreload ignore listWhat works
Core archival logic is solid: JSONL writing, state persistence, git init all work correctly. 17/17 tests pass, 915 existing tests still green.
Live Test Report
Tested this PR on olymp with a dedicated
lurkeruser, separate Telegram bot (@OlympLurker_bot), and systemd service.✅ What works
active_channelssurvives restarts🔴 Bug 1:
/logoncommand never fires (HIGH)register_command()addsCommandHandlerto the TelegramApplication, but cobot's telegram plugin polls viagetUpdatesdirectly and never callsApplication.process_update(). So commands are registered but never dispatched.Workaround: Manually writing
.lurker-state.jsonwith the channel ID.Suggested fix: Handle commands in the existing
poll_updatesflow by checking message text for/logonand/logoffprefixes, rather than relying onApplication.add_handler(CommandHandler(...)).🟡 Bug 2: Hot-reload restarts bot on every message (LOW)
The
hotreloadplugin detects filesystem changes (likely the state file or JSONL writes) and triggers a restart after each received message.Suggested fix: Add the configured
repo_pathto the hotreload ignore list.🟡 Observation: LLM provider required even for pure archival
Cobot crashes without an LLM provider configured (
PPQ_API_KEY not set). A silent logger shouldn't need one. This is a cobot-core issue, not specific to this PR.Verdict
Not ready to merge until Bug 1 is fixed —
/logonand/logoffare the only user-facing controls and they don't work. Core archival logic is solid though. Bug 2 and LLM requirement can be follow-up issues.d2fd0d187b751648559c@doxios i just rebased this PR after merging in #228
However the issue only arised after Zeus deactivated the loop plugins which was just a workaround. So maybe a better solution would be to modify the system prompt in a way that the bot is still using the loop but does not answer when not tagged? And also add a system-prompt to be VERY unchatty! But maybe aware on its own configuration and able to answer questions like "since when are you logging this chat" or "what is the git remote this log is pushed" ?
🦊 PR Review: telegram-lurker plugin
Summary
New plugin for silent Telegram chat archival to git as JSONL. Also adds
register_command()API to the telegram plugin.🔴 Architecture Violation: Principle 3 — "Adding a Plugin Never Requires Editing Another Plugin"
This PR modifies
cobot/plugins/telegram/plugin.pyto addregister_command()and_command_handlers. Per the Plugin Design Guide, new plugins should wire in via existing extension points or capability discovery — not by patching existing plugins.Recommendation: The
register_command()addition to the telegram plugin should be a separate PR that extends telegram's public API. The lurker plugin should depend on that API existing, not bundle the change.🟡 Priority Band Mismatch
The plugin sets
priority=40which falls in the Aggregators band (30–49). A chat archival plugin is not an aggregator — it's a service/feature plugin. Should bepriority=50+(Orchestration) or more appropriately in the 20–29 service band.🟡 Deploy Workflow Included
.forgejo/workflows/deploy-lurker.ymlcontains deployment-specific CI that deploys via SSH. This is infrastructure config that:🟡 Threading for Commit Loop
Using
threading.Threadwith syncsubprocess.runfor the commit loop works but is unusual in an async codebase. Consider usingasyncio.create_taskwithasyncio.sleepinstead for consistency with the rest of cobot.🟡 Duplicate Git Logic
Both
_git_commit()(async) and_git_commit_sync()(sync) exist with nearly identical logic. The async version appears unused after init. Consider consolidating.🟢 Good Stuff
implementsforcontext.system_prompttelegram_lurker.system_promptfor composabilityruff checkandruff formatboth pass ✅Checks
Verdict: 🟡 Changes Requested
The core lurker plugin is well-written, but the PR needs to be split:
register_command()to telegram plugin (extends public API)Also address the priority band and consider async consolidation.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.