rmesh-reticulum/app/rchats_bridge.py

80 lines
2.5 KiB
Python

"""
rChats bridge — relays LXMF and MeshCore messages to/from rChats.
Messages arriving over mesh are forwarded to rChats internal API.
Messages from rChats can be sent out over mesh.
"""
import json
import logging
import threading
import time
import httpx
from .config import RCHATS_BRIDGE_ENABLED, RCHATS_INTERNAL_URL
logger = logging.getLogger("rmesh.rchats_bridge")
def init():
"""Initialize the rChats bridge."""
if not RCHATS_BRIDGE_ENABLED:
logger.info("rChats bridge disabled")
return
logger.info("rChats bridge enabled — forwarding to %s", RCHATS_INTERNAL_URL)
def on_lxmf_event(event: dict):
"""Handle LXMF events and forward relevant ones to rChats."""
if not RCHATS_BRIDGE_ENABLED:
return
event_type = event.get("type", "")
if event_type == "lxmf.delivery":
msg = event.get("message", {})
_forward_to_rchats(msg, source="reticulum")
elif event_type == "meshcore.message":
msg = event.get("message", {})
_forward_to_rchats(msg, source="meshcore")
def _forward_to_rchats(msg: dict, source: str):
"""Forward a mesh message to rChats internal API."""
space_slug = msg.get("space_slug")
if not space_slug:
return # Only forward space-scoped messages
payload = {
"source": source,
"sender": msg.get("sender_hash", "") or msg.get("sender", ""),
"content": msg.get("content", ""),
"title": msg.get("title", ""),
"has_image": "image" in msg.get("fields", {}),
"has_audio": "audio" in msg.get("fields", {}),
"has_files": "file_attachments" in msg.get("fields", {}),
"space": space_slug,
"timestamp": msg.get("timestamp", time.time()),
}
threading.Thread(
target=_post_to_rchats, args=(space_slug, payload), daemon=True
).start()
def _post_to_rchats(space_slug: str, payload: dict):
"""POST message to rChats internal mesh-relay endpoint."""
try:
url = f"{RCHATS_INTERNAL_URL}/api/internal/mesh-relay"
with httpx.Client(timeout=10) as client:
response = client.post(url, json=payload)
if response.status_code < 400:
logger.info("Forwarded mesh message to rChats space '%s'", space_slug)
else:
logger.warning("rChats rejected message: %d %s", response.status_code, response.text[:100])
except httpx.ConnectError:
logger.debug("rChats not reachable at %s", RCHATS_INTERNAL_URL)
except Exception as e:
logger.warning("Failed to forward to rChats: %s", e)