# SPDX-License-Identifier: LGPL-3.0-or-later from dataclasses import dataclass, field from datetime import datetime from typing import TYPE_CHECKING, Any, Dict, Optional from .utils import AutoStrEnum, auto if TYPE_CHECKING: from .models.items import Account, Member ORDER: Dict[str, int] = { "online": 0, "unavailable": 1, "offline": 2, } @dataclass class Presence: """Represents a single matrix user's presence fields. These objects are stored in `Backend.presences`, indexed by user ID. It must only be instanced when receiving a `PresenceEvent` or registering an `Account` model item. When receiving a `PresenceEvent`, we get or create a `Presence` object in `Backend.presences` for the targeted user. If the user is registered in any room, add its `Member` model item to `members`. Finally, update every `Member` presence fields inside `members`. When a room member is registered, we try to find a `Presence` in `Backend.presences` for that user ID. If found, the `Member` item is added to `members`. When an Account model is registered, we create a `Presence` in `Backend.presences` for the accountu's user ID whether the server supports presence or not (we cannot know yet at this point), and assign that `Account` to the `Presence.account` field. Special attributes: members: A `{room_id: Member}` dict for storing room members related to this `Presence`. As each room has its own `Member`s objects, we have to keep track of their presence fields. `Member`s are indexed by room ID. account: `Account` related to this `Presence`, if any. Should be assigned when client starts (`MatrixClient._start()`) and cleared when client stops (`MatrixClient._start()`). """ class State(AutoStrEnum): offline = auto() # can mean offline, invisible or unknwon unavailable = auto() online = auto() invisible = auto() echo_unavailable = auto() echo_online = auto() echo_invisible = auto() def __lt__(self, other: "Presence.State") -> bool: return ORDER[self.value] < ORDER[other.value] presence: State = State.offline currently_active: bool = False last_active_at: datetime = datetime.fromtimestamp(0) status_msg: str = "" members: Dict[str, "Member"] = field(default_factory=dict) account: Optional["Account"] = None def update_members(self) -> None: """Update presence fields of every `MĚ€ember` in `members`. Currently it is only called when receiving a `PresenceEvent` and when registering room members. """ for member in self.members.values(): member.set_fields( presence = self.presence, status_msg = self.status_msg, last_active_at = self.last_active_at, currently_active = self.currently_active, ) def update_account(self) -> None: """Update presence fields of `Account` related to this `Presence`.""" # Do not update if account is changing to invisible. # When setting presence to invisible, the server will give us a # presence event telling us we are offline, but we do not want to set # account presence to offline. if ( not self.account or self.presence == self.State.offline and self.account.presence != self.State.echo_invisible ): return fields: Dict[str, Any] = {} if self.account.presence == self.State.echo_invisible: fields["presence"] = self.State.invisible else: fields["presence"] = self.presence fields["status_msg"] = self.status_msg fields["last_active_at"] = self.last_active_at fields["currently_active"] = self.currently_active self.account.set_fields(**fields)