feat: migrate bridge to events API (project_id, EventOut format)
This commit is contained in:
parent
cd5be52b2b
commit
1289dbf707
31
bridge.py
31
bridge.py
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user