# retoor <retoor@molodetz.nl>
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.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<uid>[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"],
}
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),
}