feat: migrate bridge to events API (project_id, EventOut format)

This commit is contained in:
Markov 2026-03-26 13:12:07 +01:00
parent cd5be52b2b
commit 1289dbf707
2 changed files with 22 additions and 65 deletions

View File

@ -44,22 +44,29 @@ async def on_tracker_event(event: dict):
return
if event_type == "message.new":
msg = event.get("data", {})
author = msg.get("author", {})
logger.info("message.new from %s: %s", author.get("slug", "?"), (msg.get("content", ""))[:50])
data = event.get("data", {})
actor = data.get("actor") or {}
payload = data.get("payload") or {}
evt_type = data.get("type", "")
content = payload.get("content", "")
logger.info("message.new type=%s from %s: %s", evt_type, actor.get("slug", "?"), content[:50])
# Only forward chat messages (not task_comment, task_status, etc.)
if evt_type not in ("chat_message",):
return
# Skip messages from bridge itself (avoid echo)
author_slug = author.get("slug", "")
if author_slug == "bridge":
actor_slug = actor.get("slug", "")
if actor_slug == "bridge":
return
# Skip messages that bridge sent on behalf of Telegram users
# (they contain "[Username]" prefix)
text_content = msg.get("content", "")
if text_content.startswith("[") and "] " in text_content[:50]:
if content.startswith("[") and "] " in content[:50]:
return
project_id = msg.get("project_id")
project_id = data.get("project_id")
if not project_id:
return
@ -67,13 +74,11 @@ async def on_tracker_event(event: dict):
if not topic_id:
return
author_name = author.get("name", author.get("slug", "???"))
text = msg.get("content", "")
if not text:
if not content:
return
# Format: "Author: message"
tg_text = f"<b>{_escape_html(author_name)}</b>: {_escape_html(text)}"
actor_name = actor.get("name", actor.get("slug", "???"))
tg_text = f"<b>{_escape_html(actor_name)}</b>: {_escape_html(content)}"
# Silent if any human is online in web
silent = any(s for s in _online_members if s not in ("coder", "architect", "bridge"))
await _send_to_topic(topic_id, tg_text, silent=silent)

View File

@ -5,7 +5,6 @@ import json
import logging
from typing import Callable, Awaitable
import httpx
import websockets
from config import Config
@ -21,7 +20,6 @@ class TrackerClient:
self.on_message = on_message
self._ws = None
self._running = False
self._project_chat_ids: dict[str, str] = {} # project_uuid -> chat_id
self._send_queue: asyncio.Queue = asyncio.Queue()
async def connect(self):
@ -35,7 +33,6 @@ class TrackerClient:
self._ws = ws
logger.info("Connected to Tracker WS")
# Run reader, writer and heartbeat concurrently
await asyncio.gather(
self._reader(ws),
self._writer(ws),
@ -84,7 +81,6 @@ class TrackerClient:
if event_type == "auth.ok":
logger.info("Authenticated as bridge")
await self._cache_projects()
return
if event_type == "error":
@ -100,63 +96,19 @@ class TrackerClient:
if event_type in ("message.new", "task.updated", "task.created", "project.created"):
await self.on_message(event)
async def _cache_projects(self):
"""Fetch all projects and cache their chat_ids."""
try:
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{self.config.tracker_url}/api/v1/projects",
headers={"Authorization": f"Bearer {self.config.bridge_token}"},
)
if resp.status_code == 200:
for p in resp.json():
pid = p.get("id")
cid = p.get("chat_id")
if pid and cid:
self._project_chat_ids[pid] = cid
logger.info("Cached %d project chat_ids", len(self._project_chat_ids))
except Exception as e:
logger.error("Failed to cache projects: %s", e)
async def get_chat_id(self, project_uuid: str) -> str | None:
"""Get chat_id for a project, fetching if not cached."""
if project_uuid in self._project_chat_ids:
return self._project_chat_ids[project_uuid]
# Try to fetch
try:
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{self.config.tracker_url}/api/v1/projects/{project_uuid}",
headers={"Authorization": f"Bearer {self.config.bridge_token}"},
)
if resp.status_code == 200:
cid = resp.json().get("chat_id")
if cid:
self._project_chat_ids[project_uuid] = cid
return cid
except Exception as e:
logger.error("Failed to fetch project %s: %s", project_uuid[:8], e)
return None
async def send_message(self, project_uuid: str, text: str):
"""Send a chat message to Tracker."""
async def send_message(self, project_id: str, text: str):
"""Send a chat message to Tracker via WS chat.send."""
if not self._ws:
logger.warning("Not connected to Tracker")
return
chat_id = await self.get_chat_id(project_uuid)
if not chat_id:
logger.warning("No chat_id for project %s", project_uuid[:8])
return
payload = {
"type": "chat.send",
"chat_id": chat_id,
"project_id": project_id,
"content": text,
}
await self._send_queue.put(payload)
logger.info("Queued to Tracker: project=%s chat=%s", project_uuid[:8], chat_id[:8])
logger.info("Queued to Tracker: project=%s", project_id[:8])
def stop(self):
self._running = False