81 lines
2.5 KiB
Python
81 lines
2.5 KiB
Python
"""WebSocket client for Tracker."""
|
|
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
from typing import Callable, Awaitable
|
|
|
|
import websockets
|
|
from websockets.asyncio.client import connect
|
|
|
|
from config import Config
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TrackerClient:
|
|
"""Connects to Tracker WS as a bridge member."""
|
|
|
|
def __init__(self, config: Config, on_message: Callable[[dict], Awaitable[None]]):
|
|
self.config = config
|
|
self.on_message = on_message
|
|
self._ws = None
|
|
self._topic_map: dict[str, int] = {} # project_uuid -> topic_id
|
|
self._running = False
|
|
|
|
async def connect(self):
|
|
"""Connect and authenticate."""
|
|
url = f"{self.config.tracker_ws_url}?token={self.config.bridge_token}"
|
|
self._running = True
|
|
|
|
while self._running:
|
|
try:
|
|
async with connect(url) as ws:
|
|
self._ws = ws
|
|
logger.info("Connected to Tracker WS")
|
|
|
|
async for raw in ws:
|
|
try:
|
|
event = json.loads(raw)
|
|
await self._handle_event(event)
|
|
except json.JSONDecodeError:
|
|
logger.warning("Invalid JSON from Tracker: %s", raw[:200])
|
|
except websockets.ConnectionClosed:
|
|
logger.warning("Tracker WS closed, reconnecting in 5s...")
|
|
await asyncio.sleep(5)
|
|
except Exception as e:
|
|
logger.error("Tracker WS error: %s, reconnecting in 5s...", e)
|
|
await asyncio.sleep(5)
|
|
|
|
async def _handle_event(self, event: dict):
|
|
"""Route Tracker events."""
|
|
event_type = event.get("type", "")
|
|
|
|
if event_type == "auth.ok":
|
|
logger.info("Authenticated as bridge")
|
|
return
|
|
|
|
if event_type == "error":
|
|
logger.error("Tracker error: %s", event.get("message"))
|
|
return
|
|
|
|
# Forward message events to Telegram
|
|
if event_type in ("message.new", "task.updated", "task.created"):
|
|
await self.on_message(event)
|
|
|
|
async def send_message(self, project_uuid: str, text: str, author_name: str = None):
|
|
"""Send a chat message to Tracker."""
|
|
if not self._ws:
|
|
logger.warning("Not connected to Tracker")
|
|
return
|
|
|
|
payload = {
|
|
"type": "chat.send",
|
|
"project_id": project_uuid,
|
|
"text": text,
|
|
}
|
|
await self._ws.send(json.dumps(payload))
|
|
|
|
def stop(self):
|
|
self._running = False
|