# retoor from __future__ import annotations import re from typing import Optional from devplacepy.config import PRESENCE_ONLINE_LIMIT from devplacepy.database import db, get_users_by_uids from devplacepy.database.awards import award_is_prominent from devplacepy.services import presence from devplacepy.services.base import BaseService from devplacepy.services.pubsub import publish as pubsub_publish from devplacepy.services.pubsub.hub import pubsub ROSTER_TOPIC = "public.presence.roster" TOPIC_PATTERN = re.compile(r"^public\.presence\.(?P[A-Za-z0-9_-]{1,128})$") class PresenceRelayService(BaseService): title = "Presence relay" description = ( "Single source of truth for LIVE online status. Each tick it recomputes one online " "set with hysteresis - a user is online at the timeout window but only drops after an " "extra grace margin - and drives BOTH the per-user avatar dots (public.presence.{uid}) " "and the shared feed roster (public.presence.roster) from that one set, so they never " "disagree. It publishes ONLY on change (a real online<->offline transition or a first-seen " "subscriber), reads due users in one batched query per tick, and does nothing when idle. " "Runs on the service lock owner where every subscriber converges." ) default_enabled = True def __init__(self): super().__init__(name="presence_relay", interval_seconds=2) self._online: set[str] = set() self._published: dict[str, bool] = {} self._roster_uids: Optional[frozenset] = None async def run_once(self) -> None: if "users" not in db.tables: return dot_pairs: list[tuple[str, str]] = [] roster_subscribed = False for entry in pubsub.topics(): topic = entry["topic"] if not entry["subscribers"] or "*" in topic: continue if topic == ROSTER_TOPIC: roster_subscribed = True continue match = TOPIC_PATTERN.match(topic) if match is not None: dot_pairs.append((topic, match.group("uid"))) roster_rows = presence.online_candidates() if roster_subscribed else [] rows: dict[str, dict] = {} dot_uids = {uid for _, uid in dot_pairs} if dot_uids: rows.update(get_users_by_uids(list(dot_uids))) for row in roster_rows: rows[row["uid"]] = row prev = self._online online: set[str] = set() for uid, row in rows.items(): elapsed = presence.seconds_since(row.get("last_seen")) if presence.stays_online(elapsed, uid in prev): online.add(uid) self._online = online await self._publish_dots(dot_pairs, online, rows) await self._publish_roster(roster_subscribed, roster_rows, online) async def _publish_dots(self, dot_pairs, online, rows) -> None: active = {topic for topic, _ in dot_pairs} self._published = {t: v for t, v in self._published.items() if t in active} published = 0 for topic, uid in dot_pairs: is_on = uid in online if self._published.get(topic) == is_on: continue row = rows.get(uid) or {} published += await pubsub_publish( topic, {"online": is_on, "last_seen": row.get("last_seen")} ) self._published[topic] = is_on if published: self.log(f"pushed {published} presence change(s)") async def _publish_roster(self, subscribed, roster_rows, online) -> None: if not subscribed: self._roster_uids = None return users = [row for row in roster_rows if row["uid"] in online][:PRESENCE_ONLINE_LIMIT] uids = frozenset(row["uid"] for row in users) if uids == self._roster_uids: return self._roster_uids = uids await pubsub_publish( ROSTER_TOPIC, { "count": len(users), "users": [ { "uid": row["uid"], "username": row["username"], "avatar_seed": row.get("avatar_seed") or row["username"], "last_award_slug": row.get("last_award_slug") or "", "award_prominent": award_is_prominent(row), } for row in users ], }, ) self.log(f"roster changed: {len(users)} online") def collect_metrics(self) -> dict: return { "online": len(self._online), "tracked_dots": len(self._published), }