|
@@ -240,6 +240,7 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
self._resume_cursor: Optional[str] = None
|
|
self._resume_cursor: Optional[str] = None
|
|
|
self._watch_room_ids: List[str] = []
|
|
self._watch_room_ids: List[str] = []
|
|
|
self._ws_task: Optional[asyncio.Task] = None
|
|
self._ws_task: Optional[asyncio.Task] = None
|
|
|
|
|
+ self._presence_task: Optional[asyncio.Task] = None
|
|
|
self._ws_ready: Optional[asyncio.Event] = None
|
|
self._ws_ready: Optional[asyncio.Event] = None
|
|
|
self._ws_active = False
|
|
self._ws_active = False
|
|
|
self._ws_ref = None # reference to open websocket for dynamic resubscribe
|
|
self._ws_ref = None # reference to open websocket for dynamic resubscribe
|
|
@@ -375,11 +376,10 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
self._chatto_client = None
|
|
self._chatto_client = None
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
- # Broadcast online presence so the bot appears online in the member list
|
|
|
|
|
- try:
|
|
|
|
|
- await client.update_presence(status=PresenceStatus.ONLINE)
|
|
|
|
|
- except Exception:
|
|
|
|
|
- logger.debug("Chatto: update_presence(online) failed on connect", exc_info=True)
|
|
|
|
|
|
|
+ # Announce online presence so the bot appears online in the member list.
|
|
|
|
|
+ # The server treats this as a TTL, so _presence_refresh_loop below has to
|
|
|
|
|
+ # keep re-announcing it — a single call here lapses back to offline.
|
|
|
|
|
+ await self._announce_online()
|
|
|
|
|
|
|
|
self._closing = False
|
|
self._closing = False
|
|
|
# Start background realtime WS event stream loop.
|
|
# Start background realtime WS event stream loop.
|
|
@@ -387,6 +387,9 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
self._ws_task = asyncio.create_task(
|
|
self._ws_task = asyncio.create_task(
|
|
|
self._chattolib_event_loop(), name="chatto-event-stream",
|
|
self._chattolib_event_loop(), name="chatto-event-stream",
|
|
|
)
|
|
)
|
|
|
|
|
+ self._presence_task = asyncio.create_task(
|
|
|
|
|
+ self._presence_refresh_loop(), name="chatto-presence-refresh",
|
|
|
|
|
+ )
|
|
|
self._mark_connected()
|
|
self._mark_connected()
|
|
|
|
|
|
|
|
self._list_functions()
|
|
self._list_functions()
|
|
@@ -399,19 +402,40 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
|
|
|
|
|
return True
|
|
return True
|
|
|
|
|
|
|
|
- async def disconnect(self) -> None:
|
|
|
|
|
- """Stop WebSocket, liveness probe, typing tasks, and clear state.
|
|
|
|
|
|
|
+ async def _announce_online(self) -> bool:
|
|
|
|
|
+ """Tell the server we are online. Returns whether the call got through.
|
|
|
|
|
|
|
|
- BasePlatformAdapter override
|
|
|
|
|
|
|
+ Logged at warning level on failure: a silently dropped presence call is
|
|
|
|
|
+ indistinguishable from a bot that is simply not running.
|
|
|
"""
|
|
"""
|
|
|
- # Broadcast offline presence before tearing down
|
|
|
|
|
try:
|
|
try:
|
|
|
client = await self._require_client()
|
|
client = await self._require_client()
|
|
|
- await client.update_presence(status=PresenceStatus.OFFLINE)
|
|
|
|
|
- except Exception:
|
|
|
|
|
- logger.debug("Chatto: update_presence(PresenceStatus.OFFLINE) failed on disconnect", exc_info=True)
|
|
|
|
|
|
|
+ await client.update_presence(status=PresenceStatus.ONLINE)
|
|
|
|
|
+ return True
|
|
|
|
|
+ except Exception as exc:
|
|
|
|
|
+ logger.warning("Chatto: presence refresh failed, bot may appear offline: %s", exc)
|
|
|
|
|
+ return False
|
|
|
|
|
|
|
|
|
|
+ async def _presence_refresh_loop(self) -> None:
|
|
|
|
|
+ """Re-announce ONLINE until disconnect, since presence expires server-side.
|
|
|
|
|
|
|
|
|
|
+ Failures are not fatal — the next tick tries again, so a blip in the
|
|
|
|
|
+ presence endpoint costs at most one interval of visible offline time.
|
|
|
|
|
+ """
|
|
|
|
|
+ while not self._closing:
|
|
|
|
|
+ await self._sleep_interruptible(ChattoConstants.PRESENCE_REFRESH_INTERVAL)
|
|
|
|
|
+ if self._closing:
|
|
|
|
|
+ return
|
|
|
|
|
+ await self._announce_online()
|
|
|
|
|
+
|
|
|
|
|
+ async def disconnect(self) -> None:
|
|
|
|
|
+ """Stop WebSocket, presence refresh, typing tasks, and clear state.
|
|
|
|
|
+
|
|
|
|
|
+ BasePlatformAdapter override
|
|
|
|
|
+ """
|
|
|
|
|
+ # No explicit offline broadcast: chattolib rejects OFFLINE outright
|
|
|
|
|
+ # ("stop refreshing to go offline"), so cancelling the refresh loop
|
|
|
|
|
+ # below is what actually takes the bot offline.
|
|
|
self._ws_active = False
|
|
self._ws_active = False
|
|
|
self._closing = True
|
|
self._closing = True
|
|
|
|
|
|
|
@@ -427,6 +451,13 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
pass
|
|
pass
|
|
|
self._ws_task = None
|
|
self._ws_task = None
|
|
|
|
|
|
|
|
|
|
+ if self._presence_task and not self._presence_task.done():
|
|
|
|
|
+ self._presence_task.cancel()
|
|
|
|
|
+ try:
|
|
|
|
|
+ await self._presence_task
|
|
|
|
|
+ except (asyncio.CancelledError, Exception):
|
|
|
|
|
+ pass
|
|
|
|
|
+ self._presence_task = None
|
|
|
|
|
|
|
|
if self._chatto_client:
|
|
if self._chatto_client:
|
|
|
try:
|
|
try:
|