Continue AI features and improve protocol support

This commit is contained in:
2026-02-15 16:57:32 +00:00
parent 2d3b8fdac6
commit 85e97e895d
62 changed files with 5472 additions and 441 deletions

View File

@@ -0,0 +1,54 @@
from dataclasses import dataclass, field
from typing import Any
@dataclass(slots=True)
class UnifiedEvent:
"""
Normalized event envelope shared across protocol adapters.
"""
service: str
event_type: str
identifier: str = ""
text: str = ""
ts: int | None = None
message_timestamps: list[int] = field(default_factory=list)
attachments: list[dict[str, Any]] = field(default_factory=list)
payload: dict[str, Any] = field(default_factory=dict)
def normalize_gateway_event(service: str, payload: dict[str, Any]) -> UnifiedEvent:
event_type = str(payload.get("type") or "").strip().lower()
message_timestamps = []
raw_timestamps = payload.get("message_timestamps") or payload.get("timestamps") or []
if isinstance(raw_timestamps, list):
for item in raw_timestamps:
try:
message_timestamps.append(int(item))
except Exception:
continue
elif raw_timestamps:
try:
message_timestamps = [int(raw_timestamps)]
except Exception:
message_timestamps = []
ts = payload.get("ts") or payload.get("timestamp")
try:
ts = int(ts) if ts is not None else None
except Exception:
ts = None
return UnifiedEvent(
service=service,
event_type=event_type,
identifier=str(
payload.get("identifier") or payload.get("source") or payload.get("from") or ""
).strip(),
text=str(payload.get("text") or ""),
ts=ts,
message_timestamps=message_timestamps,
attachments=list(payload.get("attachments") or []),
payload=dict(payload or {}),
)

View File

@@ -1,5 +1,12 @@
from asgiref.sync import sync_to_async
from core.clients import transport
from core.clients.instagram import InstagramClient
from core.clients.signal import SignalClient
from core.clients.whatsapp import WhatsAppClient
from core.clients.xmpp import XMPPClient
from core.messaging import history
from core.models import PersonIdentifier
from core.util import logs
@@ -16,11 +23,15 @@ class UnifiedRouter(object):
self.xmpp = XMPPClient(self, loop, "xmpp")
self.signal = SignalClient(self, loop, "signal")
self.whatsapp = WhatsAppClient(self, loop, "whatsapp")
self.instagram = InstagramClient(self, loop, "instagram")
def _start(self):
print("UR _start")
self.xmpp.start()
self.signal.start()
self.whatsapp.start()
self.instagram.start()
def run(self):
try:
@@ -37,14 +48,66 @@ class UnifiedRouter(object):
async def message_received(self, protocol, *args, **kwargs):
self.log.info(f"Message received ({protocol}) {args} {kwargs}")
async def _resolve_identifier_objects(self, protocol, identifier):
if isinstance(identifier, PersonIdentifier):
return [identifier]
value = str(identifier or "").strip()
if not value:
return []
return await sync_to_async(list)(
PersonIdentifier.objects.filter(
identifier=value,
service=protocol,
)
)
async def message_read(self, protocol, *args, **kwargs):
self.log.info(f"Message read ({protocol}) {args} {kwargs}")
identifier = kwargs.get("identifier")
timestamps = kwargs.get("message_timestamps") or []
read_ts = kwargs.get("read_ts")
payload = kwargs.get("payload") or {}
read_by = kwargs.get("read_by") or ""
identifiers = await self._resolve_identifier_objects(protocol, identifier)
for row in identifiers:
await history.apply_read_receipts(
user=row.user,
identifier=row,
message_timestamps=timestamps,
read_ts=read_ts,
source_service=protocol,
read_by_identifier=read_by or row.identifier,
payload=payload,
)
async def started_typing(self, protocol, *args, **kwargs):
self.log.info(f"Started typing ({protocol}) {args} {kwargs}")
identifier = kwargs.get("identifier")
identifiers = await self._resolve_identifier_objects(protocol, identifier)
for src in identifiers:
targets = await sync_to_async(list)(
PersonIdentifier.objects.filter(
user=src.user,
person=src.person,
).exclude(service=protocol)
)
for target in targets:
await transport.start_typing(target.service, target.identifier)
async def stopped_typing(self, protocol, *args, **kwargs):
self.log.info(f"Stopped typing ({protocol}) {args} {kwargs}")
identifier = kwargs.get("identifier")
identifiers = await self._resolve_identifier_objects(protocol, identifier)
for src in identifiers:
targets = await sync_to_async(list)(
PersonIdentifier.objects.filter(
user=src.user,
person=src.person,
).exclude(service=protocol)
)
for target in targets:
await transport.stop_typing(target.service, target.identifier)
async def reacted(self, protocol, *args, **kwargs):
self.log.info(f"Reacted ({protocol}) {args} {kwargs}")