Lightweight containerized prosody tooling + moved auth scripts + xmpp reconnect/auth stabilization
This commit is contained in:
@@ -642,6 +642,12 @@ class HandleMessage(Command):
|
||||
actor=(
|
||||
effective_source_uuid or effective_source_number or ""
|
||||
),
|
||||
target_author=str(
|
||||
(reaction_payload.get("raw") or {}).get("targetAuthorUuid")
|
||||
or (reaction_payload.get("raw") or {}).get("targetAuthor")
|
||||
or (reaction_payload.get("raw") or {}).get("targetAuthorNumber")
|
||||
or ""
|
||||
),
|
||||
remove=bool(reaction_payload.get("remove")),
|
||||
payload=reaction_payload.get("raw") or {},
|
||||
)
|
||||
@@ -1308,6 +1314,12 @@ class SignalClient(ClientBase):
|
||||
emoji=str(reaction_payload.get("emoji") or ""),
|
||||
source_service="signal",
|
||||
actor=(source_uuid or source_number or ""),
|
||||
target_author=str(
|
||||
(reaction_payload.get("raw") or {}).get("targetAuthorUuid")
|
||||
or (reaction_payload.get("raw") or {}).get("targetAuthor")
|
||||
or (reaction_payload.get("raw") or {}).get("targetAuthorNumber")
|
||||
or ""
|
||||
),
|
||||
remove=bool(reaction_payload.get("remove")),
|
||||
payload=reaction_payload.get("raw") or {},
|
||||
)
|
||||
@@ -1453,6 +1465,12 @@ class SignalClient(ClientBase):
|
||||
emoji=str(reaction_payload.get("emoji") or ""),
|
||||
source_service="signal",
|
||||
actor=(source_uuid or source_number or ""),
|
||||
target_author=str(
|
||||
(reaction_payload.get("raw") or {}).get("targetAuthorUuid")
|
||||
or (reaction_payload.get("raw") or {}).get("targetAuthor")
|
||||
or (reaction_payload.get("raw") or {}).get("targetAuthorNumber")
|
||||
or ""
|
||||
),
|
||||
remove=bool(reaction_payload.get("remove")),
|
||||
payload=reaction_payload.get("raw") or {},
|
||||
)
|
||||
|
||||
@@ -240,13 +240,17 @@ async def send_reaction(
|
||||
):
|
||||
base = getattr(settings, "SIGNAL_HTTP_URL", "http://signal:8080").rstrip("/")
|
||||
sender_number = settings.SIGNAL_NUMBER
|
||||
if not recipient_uuid or not target_timestamp:
|
||||
normalized_recipient = normalize_signal_recipient(recipient_uuid)
|
||||
normalized_target_author = normalize_signal_recipient(
|
||||
str(target_author or normalized_recipient)
|
||||
)
|
||||
if not normalized_recipient or not target_timestamp:
|
||||
return False
|
||||
|
||||
payload = {
|
||||
"recipient": recipient_uuid,
|
||||
"recipient": normalized_recipient,
|
||||
"reaction": str(emoji or ""),
|
||||
"target_author": str(target_author or recipient_uuid),
|
||||
"target_author": normalized_target_author,
|
||||
"timestamp": int(target_timestamp),
|
||||
"remove": bool(remove),
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ from django.core.cache import cache
|
||||
|
||||
from core.clients import signalapi
|
||||
from core.messaging import media_bridge
|
||||
from core.transports.capabilities import supports, unsupported_reason
|
||||
from core.util import logs
|
||||
|
||||
log = logs.get_logger("transport")
|
||||
@@ -32,6 +33,10 @@ def _service_key(service: str) -> str:
|
||||
return str(service or "").strip().lower()
|
||||
|
||||
|
||||
def _capability_checks_enabled() -> bool:
|
||||
return bool(getattr(settings, "CAPABILITY_ENFORCEMENT_ENABLED", True))
|
||||
|
||||
|
||||
def _runtime_key(service: str) -> str:
|
||||
return f"gia:service:runtime:{_service_key(service)}"
|
||||
|
||||
@@ -898,6 +903,10 @@ async def send_reaction(
|
||||
remove: bool = False,
|
||||
):
|
||||
service_key = _service_key(service)
|
||||
if _capability_checks_enabled() and not supports(service_key, "reactions"):
|
||||
reason = unsupported_reason(service_key, "reactions")
|
||||
log.warning("capability-check failed service=%s feature=reactions: %s", service_key, reason)
|
||||
return False
|
||||
if not str(emoji or "").strip() and not remove:
|
||||
return False
|
||||
|
||||
@@ -968,6 +977,13 @@ async def send_reaction(
|
||||
|
||||
async def start_typing(service: str, recipient: str):
|
||||
service_key = _service_key(service)
|
||||
if _capability_checks_enabled() and not supports(service_key, "typing"):
|
||||
log.warning(
|
||||
"capability-check failed service=%s feature=typing: %s",
|
||||
service_key,
|
||||
unsupported_reason(service_key, "typing"),
|
||||
)
|
||||
return False
|
||||
if service_key == "signal":
|
||||
await signalapi.start_typing(recipient)
|
||||
return True
|
||||
@@ -998,6 +1014,13 @@ async def start_typing(service: str, recipient: str):
|
||||
|
||||
async def stop_typing(service: str, recipient: str):
|
||||
service_key = _service_key(service)
|
||||
if _capability_checks_enabled() and not supports(service_key, "typing"):
|
||||
log.warning(
|
||||
"capability-check failed service=%s feature=typing: %s",
|
||||
service_key,
|
||||
unsupported_reason(service_key, "typing"),
|
||||
)
|
||||
return False
|
||||
if service_key == "signal":
|
||||
await signalapi.stop_typing(recipient)
|
||||
return True
|
||||
|
||||
@@ -135,6 +135,9 @@ class XMPPComponent(ComponentXMPP):
|
||||
def __init__(self, ur, jid, secret, server, port):
|
||||
self.ur = ur
|
||||
self._upload_config_warned = False
|
||||
self._reconnect_task = None
|
||||
self._reconnect_delay_seconds = 1.0
|
||||
self._reconnect_delay_max_seconds = 30.0
|
||||
|
||||
self.log = logs.get_logger("XMPP")
|
||||
|
||||
@@ -821,14 +824,49 @@ class XMPPComponent(ComponentXMPP):
|
||||
|
||||
async def session_start(self, *args):
|
||||
self.log.info("XMPP session started")
|
||||
self._reconnect_delay_seconds = 1.0
|
||||
if self._reconnect_task and not self._reconnect_task.done():
|
||||
self._reconnect_task.cancel()
|
||||
self._reconnect_task = None
|
||||
await self.enable_carbons()
|
||||
|
||||
async def _reconnect_loop(self):
|
||||
try:
|
||||
while True:
|
||||
delay = float(self._reconnect_delay_seconds)
|
||||
await asyncio.sleep(delay)
|
||||
try:
|
||||
self.log.info("XMPP reconnect attempt delay_s=%.1f", delay)
|
||||
connected = self.connect()
|
||||
if connected is False:
|
||||
raise RuntimeError("connect returned false")
|
||||
self.process(forever=False)
|
||||
return
|
||||
except Exception as exc:
|
||||
self.log.warning("XMPP reconnect attempt failed: %s", exc)
|
||||
self._reconnect_delay_seconds = min(
|
||||
self._reconnect_delay_max_seconds,
|
||||
max(1.0, float(self._reconnect_delay_seconds) * 2.0),
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
finally:
|
||||
self._reconnect_task = None
|
||||
|
||||
def _schedule_reconnect(self):
|
||||
if self._reconnect_task and not self._reconnect_task.done():
|
||||
return
|
||||
self._reconnect_task = self.loop.create_task(self._reconnect_loop())
|
||||
|
||||
def on_disconnected(self, *args):
|
||||
"""
|
||||
Handles XMPP disconnection and triggers a reconnect loop.
|
||||
"""
|
||||
self.log.warning("XMPP disconnected, attempting to reconnect...")
|
||||
self.connect()
|
||||
self.log.warning(
|
||||
"XMPP disconnected, scheduling reconnect attempt in %.1fs",
|
||||
float(self._reconnect_delay_seconds),
|
||||
)
|
||||
self._schedule_reconnect()
|
||||
|
||||
async def request_upload_slot(self, recipient, filename, content_type, size):
|
||||
"""
|
||||
@@ -1716,7 +1754,7 @@ class XMPPClient(ClientBase):
|
||||
self.client.loop = self.loop
|
||||
|
||||
self.client.connect()
|
||||
# self.client.process()
|
||||
self.client.process(forever=False)
|
||||
|
||||
async def start_typing_for_person(self, user, person_identifier):
|
||||
await self.client.send_typing_for_person(user, person_identifier, True)
|
||||
|
||||
Reference in New Issue
Block a user