mirror of
https://github.com/element-hq/synapse
synced 2024-06-28 05:43:30 +00:00
Added presence update on change of profile information and config flags for selective presence tracking
Signed-off-by: Michael Hollister <michael@futo.org>
This commit is contained in:
parent
37558d5e4c
commit
16a21470dd
1
changelog.d/16992.feature
Normal file
1
changelog.d/16992.feature
Normal file
|
@ -0,0 +1 @@
|
|||
Added presence update on change of profile information and config flags for selective presence tracking. Contributed by @Michael-Hollister.
|
|
@ -83,6 +83,8 @@ class UserPresenceState:
|
|||
last_user_sync_ts: int
|
||||
status_msg: Optional[str]
|
||||
currently_active: bool
|
||||
displayname: Optional[str]
|
||||
avatar_url: Optional[str]
|
||||
|
||||
def as_dict(self) -> JsonDict:
|
||||
return attr.asdict(self)
|
||||
|
@ -101,4 +103,6 @@ class UserPresenceState:
|
|||
last_user_sync_ts=0,
|
||||
status_msg=None,
|
||||
currently_active=False,
|
||||
displayname=None,
|
||||
avatar_url=None,
|
||||
)
|
||||
|
|
|
@ -384,6 +384,16 @@ class ServerConfig(Config):
|
|||
# Whether to internally track presence, requires that presence is enabled,
|
||||
self.track_presence = self.presence_enabled and presence_enabled != "untracked"
|
||||
|
||||
# Disabling server-side presence tracking
|
||||
self.sync_presence_tracking = presence_config.get(
|
||||
"sync_presence_tracking", True
|
||||
)
|
||||
|
||||
# Disabling federation presence tracking
|
||||
self.federation_presence_tracking = presence_config.get(
|
||||
"federation_presence_tracking", True
|
||||
)
|
||||
|
||||
# Custom presence router module
|
||||
# This is the legacy way of configuring it (the config should now be put in the modules section)
|
||||
self.presence_router_module_class = None
|
||||
|
|
|
@ -1428,6 +1428,17 @@ class FederationHandlerRegistry:
|
|||
if not self.config.server.track_presence and edu_type == EduTypes.PRESENCE:
|
||||
return
|
||||
|
||||
if (
|
||||
not self.config.server.federation_presence_tracking
|
||||
and edu_type == EduTypes.PRESENCE
|
||||
):
|
||||
filtered_edus = []
|
||||
for e in content["push"]:
|
||||
# Process only profile presence updates to reduce resource impact
|
||||
if "status_msg" in e or "displayname" in e or "avatar_url" in e:
|
||||
filtered_edus.append(e)
|
||||
content["push"] = filtered_edus
|
||||
|
||||
# Check if we have a handler on this instance
|
||||
handler = self.edu_handlers.get(edu_type)
|
||||
if handler:
|
||||
|
|
|
@ -201,6 +201,7 @@ class BasePresenceHandler(abc.ABC):
|
|||
|
||||
self._presence_enabled = hs.config.server.presence_enabled
|
||||
self._track_presence = hs.config.server.track_presence
|
||||
self._sync_presence_tracking = hs.config.server.sync_presence_tracking
|
||||
|
||||
self._federation = None
|
||||
if hs.should_send_federation():
|
||||
|
@ -451,6 +452,8 @@ class BasePresenceHandler(abc.ABC):
|
|||
state = {
|
||||
"presence": current_presence_state.state,
|
||||
"status_message": current_presence_state.status_msg,
|
||||
"displayname": current_presence_state.displayname,
|
||||
"avatar_url": current_presence_state.avatar_url,
|
||||
}
|
||||
|
||||
# Copy the presence state to the tip of the presence stream.
|
||||
|
@ -579,7 +582,11 @@ class WorkerPresenceHandler(BasePresenceHandler):
|
|||
Called by the sync and events servlets to record that a user has connected to
|
||||
this worker and is waiting for some events.
|
||||
"""
|
||||
if not affect_presence or not self._track_presence:
|
||||
if (
|
||||
not affect_presence
|
||||
or not self._track_presence
|
||||
or not self._sync_presence_tracking
|
||||
):
|
||||
return _NullContextManager()
|
||||
|
||||
# Note that this causes last_active_ts to be incremented which is not
|
||||
|
@ -648,6 +655,8 @@ class WorkerPresenceHandler(BasePresenceHandler):
|
|||
row.last_user_sync_ts,
|
||||
row.status_msg,
|
||||
row.currently_active,
|
||||
row.displayname,
|
||||
row.avatar_url,
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
@ -1140,7 +1149,11 @@ class PresenceHandler(BasePresenceHandler):
|
|||
client that is being used by a user.
|
||||
presence_state: The presence state indicated in the sync request
|
||||
"""
|
||||
if not affect_presence or not self._track_presence:
|
||||
if (
|
||||
not affect_presence
|
||||
or not self._track_presence
|
||||
or not self._sync_presence_tracking
|
||||
):
|
||||
return _NullContextManager()
|
||||
|
||||
curr_sync = self._user_device_to_num_current_syncs.get((user_id, device_id), 0)
|
||||
|
@ -1340,6 +1353,8 @@ class PresenceHandler(BasePresenceHandler):
|
|||
|
||||
new_fields["status_msg"] = push.get("status_msg", None)
|
||||
new_fields["currently_active"] = push.get("currently_active", False)
|
||||
new_fields["displayname"] = push.get("displayname", None)
|
||||
new_fields["avatar_url"] = push.get("avatar_url", None)
|
||||
|
||||
prev_state = await self.current_state_for_user(user_id)
|
||||
updates.append(prev_state.copy_and_replace(**new_fields))
|
||||
|
@ -1369,6 +1384,8 @@ class PresenceHandler(BasePresenceHandler):
|
|||
the `state` dict.
|
||||
"""
|
||||
status_msg = state.get("status_msg", None)
|
||||
displayname = state.get("displayname", None)
|
||||
avatar_url = state.get("avatar_url", None)
|
||||
presence = state["presence"]
|
||||
|
||||
if presence not in self.VALID_PRESENCE:
|
||||
|
@ -1414,6 +1431,8 @@ class PresenceHandler(BasePresenceHandler):
|
|||
else:
|
||||
# Syncs do not override the status message.
|
||||
new_fields["status_msg"] = status_msg
|
||||
new_fields["displayname"] = displayname
|
||||
new_fields["avatar_url"] = avatar_url
|
||||
|
||||
await self._update_states(
|
||||
[prev_state.copy_and_replace(**new_fields)], force_notify=force_notify
|
||||
|
@ -1634,6 +1653,8 @@ class PresenceHandler(BasePresenceHandler):
|
|||
if state.state != PresenceState.OFFLINE
|
||||
or now - state.last_active_ts < 7 * 24 * 60 * 60 * 1000
|
||||
or state.status_msg is not None
|
||||
or state.displayname is not None
|
||||
or state.avatar_url is not None
|
||||
]
|
||||
|
||||
await self._federation_queue.send_presence_to_destinations(
|
||||
|
@ -1668,6 +1689,14 @@ def should_notify(
|
|||
notify_reason_counter.labels(user_location, "status_msg_change").inc()
|
||||
return True
|
||||
|
||||
if old_state.displayname != new_state.displayname:
|
||||
notify_reason_counter.labels(user_location, "displayname_change").inc()
|
||||
return True
|
||||
|
||||
if old_state.avatar_url != new_state.avatar_url:
|
||||
notify_reason_counter.labels(user_location, "avatar_url_change").inc()
|
||||
return True
|
||||
|
||||
if old_state.state != new_state.state:
|
||||
notify_reason_counter.labels(user_location, "state_change").inc()
|
||||
state_transition_counter.labels(
|
||||
|
@ -1725,6 +1754,8 @@ def format_user_presence_state(
|
|||
* status_msg: Optional. Included if `status_msg` is set on `state`. The user's
|
||||
status.
|
||||
* currently_active: Optional. Included only if `state.state` is "online".
|
||||
* displayname: Optional. The current display name for this user, if any.
|
||||
* avatar_url: Optional. The current avatar URL for this user, if any.
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -1733,7 +1764,9 @@ def format_user_presence_state(
|
|||
"user_id": "@alice:example.com",
|
||||
"last_active_ago": 16783813918,
|
||||
"status_msg": "Hello world!",
|
||||
"currently_active": True
|
||||
"currently_active": True,
|
||||
"displayname": "Alice",
|
||||
"avatar_url": "mxc://localhost/wefuiwegh8742w"
|
||||
}
|
||||
"""
|
||||
content: JsonDict = {"presence": state.state}
|
||||
|
@ -1745,6 +1778,10 @@ def format_user_presence_state(
|
|||
content["status_msg"] = state.status_msg
|
||||
if state.state == PresenceState.ONLINE:
|
||||
content["currently_active"] = state.currently_active
|
||||
if state.displayname:
|
||||
content["displayname"] = state.displayname
|
||||
if state.avatar_url:
|
||||
content["avatar_url"] = state.avatar_url
|
||||
|
||||
return content
|
||||
|
||||
|
|
|
@ -200,6 +200,19 @@ class ProfileHandler:
|
|||
if propagate:
|
||||
await self._update_join_states(requester, target_user)
|
||||
|
||||
if self.hs.config.server.track_presence:
|
||||
presence_handler = self.hs.get_presence_handler()
|
||||
current_presence_state = await presence_handler.get_state(target_user)
|
||||
|
||||
state = {
|
||||
"presence": current_presence_state.state,
|
||||
"status_message": current_presence_state.status_msg,
|
||||
"displayname": new_displayname,
|
||||
"avatar_url": current_presence_state.avatar_url,
|
||||
}
|
||||
|
||||
await presence_handler.set_state(target_user, requester.device_id, state)
|
||||
|
||||
async def get_avatar_url(self, target_user: UserID) -> Optional[str]:
|
||||
if self.hs.is_mine(target_user):
|
||||
try:
|
||||
|
@ -293,6 +306,19 @@ class ProfileHandler:
|
|||
if propagate:
|
||||
await self._update_join_states(requester, target_user)
|
||||
|
||||
if self.hs.config.server.track_presence:
|
||||
presence_handler = self.hs.get_presence_handler()
|
||||
current_presence_state = await presence_handler.get_state(target_user)
|
||||
|
||||
state = {
|
||||
"presence": current_presence_state.state,
|
||||
"status_message": current_presence_state.status_msg,
|
||||
"displayname": current_presence_state.displayname,
|
||||
"avatar_url": new_avatar_url,
|
||||
}
|
||||
|
||||
await presence_handler.set_state(target_user, requester.device_id, state)
|
||||
|
||||
@cached()
|
||||
async def check_avatar_size_and_mime_type(self, mxc: str) -> bool:
|
||||
"""Check that the size and content type of the avatar at the given MXC URI are
|
||||
|
|
|
@ -330,6 +330,8 @@ class PresenceStream(_StreamFromIdGen):
|
|||
last_user_sync_ts: int
|
||||
status_msg: str
|
||||
currently_active: bool
|
||||
displayname: str
|
||||
avatar_url: str
|
||||
|
||||
NAME = "presence"
|
||||
ROW_TYPE = PresenceStreamRow
|
||||
|
|
|
@ -181,6 +181,8 @@ class PresenceStore(PresenceBackgroundUpdateStore, CacheInvalidationWorkerStore)
|
|||
"last_user_sync_ts",
|
||||
"status_msg",
|
||||
"currently_active",
|
||||
"displayname",
|
||||
"avatar_url",
|
||||
"instance_name",
|
||||
),
|
||||
values=[
|
||||
|
@ -193,6 +195,8 @@ class PresenceStore(PresenceBackgroundUpdateStore, CacheInvalidationWorkerStore)
|
|||
state.last_user_sync_ts,
|
||||
state.status_msg,
|
||||
state.currently_active,
|
||||
state.displayname,
|
||||
state.avatar_url,
|
||||
self._instance_name,
|
||||
)
|
||||
for stream_id, state in zip(stream_orderings, presence_states)
|
||||
|
@ -232,7 +236,8 @@ class PresenceStore(PresenceBackgroundUpdateStore, CacheInvalidationWorkerStore)
|
|||
sql = """
|
||||
SELECT stream_id, user_id, state, last_active_ts,
|
||||
last_federation_update_ts, last_user_sync_ts,
|
||||
status_msg, currently_active
|
||||
status_msg, currently_active, displayname,
|
||||
avatar_url
|
||||
FROM presence_stream
|
||||
WHERE ? < stream_id AND stream_id <= ?
|
||||
ORDER BY stream_id ASC
|
||||
|
@ -285,6 +290,8 @@ class PresenceStore(PresenceBackgroundUpdateStore, CacheInvalidationWorkerStore)
|
|||
"last_user_sync_ts",
|
||||
"status_msg",
|
||||
"currently_active",
|
||||
"displayname",
|
||||
"avatar_url",
|
||||
),
|
||||
desc="get_presence_for_users",
|
||||
),
|
||||
|
@ -299,8 +306,10 @@ class PresenceStore(PresenceBackgroundUpdateStore, CacheInvalidationWorkerStore)
|
|||
last_user_sync_ts=last_user_sync_ts,
|
||||
status_msg=status_msg,
|
||||
currently_active=bool(currently_active),
|
||||
displayname=displayname,
|
||||
avatar_url=avatar_url,
|
||||
)
|
||||
for user_id, state, last_active_ts, last_federation_update_ts, last_user_sync_ts, status_msg, currently_active in rows
|
||||
for user_id, state, last_active_ts, last_federation_update_ts, last_user_sync_ts, status_msg, currently_active, displayname, avatar_url, in rows
|
||||
}
|
||||
|
||||
async def should_user_receive_full_presence_with_token(
|
||||
|
@ -427,6 +436,8 @@ class PresenceStore(PresenceBackgroundUpdateStore, CacheInvalidationWorkerStore)
|
|||
"last_user_sync_ts",
|
||||
"status_msg",
|
||||
"currently_active",
|
||||
"displayname",
|
||||
"avatar_url",
|
||||
),
|
||||
order_direction="ASC",
|
||||
),
|
||||
|
@ -440,6 +451,8 @@ class PresenceStore(PresenceBackgroundUpdateStore, CacheInvalidationWorkerStore)
|
|||
last_user_sync_ts,
|
||||
status_msg,
|
||||
currently_active,
|
||||
displayname,
|
||||
avatar_url,
|
||||
) in rows:
|
||||
users_to_state[user_id] = UserPresenceState(
|
||||
user_id=user_id,
|
||||
|
@ -449,6 +462,8 @@ class PresenceStore(PresenceBackgroundUpdateStore, CacheInvalidationWorkerStore)
|
|||
last_user_sync_ts=last_user_sync_ts,
|
||||
status_msg=status_msg,
|
||||
currently_active=bool(currently_active),
|
||||
displayname=displayname,
|
||||
avatar_url=avatar_url,
|
||||
)
|
||||
|
||||
# We've run out of updates to query
|
||||
|
@ -471,7 +486,8 @@ class PresenceStore(PresenceBackgroundUpdateStore, CacheInvalidationWorkerStore)
|
|||
# query.
|
||||
sql = (
|
||||
"SELECT user_id, state, last_active_ts, last_federation_update_ts,"
|
||||
" last_user_sync_ts, status_msg, currently_active FROM presence_stream"
|
||||
" last_user_sync_ts, status_msg, currently_active, displayname, avatar_url "
|
||||
" FROM presence_stream"
|
||||
" WHERE state != ?"
|
||||
)
|
||||
|
||||
|
@ -489,8 +505,10 @@ class PresenceStore(PresenceBackgroundUpdateStore, CacheInvalidationWorkerStore)
|
|||
last_user_sync_ts=last_user_sync_ts,
|
||||
status_msg=status_msg,
|
||||
currently_active=bool(currently_active),
|
||||
displayname=displayname,
|
||||
avatar_url=avatar_url,
|
||||
)
|
||||
for user_id, state, last_active_ts, last_federation_update_ts, last_user_sync_ts, status_msg, currently_active in rows
|
||||
for user_id, state, last_active_ts, last_federation_update_ts, last_user_sync_ts, status_msg, currently_active, displayname, avatar_url, in rows
|
||||
]
|
||||
|
||||
def take_presence_startup_info(self) -> List[UserPresenceState]:
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
#
|
||||
|
||||
SCHEMA_VERSION = 85 # remember to update the list below when updating
|
||||
SCHEMA_VERSION = 86 # remember to update the list below when updating
|
||||
"""Represents the expectations made by the codebase about the database schema
|
||||
|
||||
This should be incremented whenever the codebase changes its requirements on the
|
||||
|
@ -139,12 +139,15 @@ Changes in SCHEMA_VERSION = 84
|
|||
|
||||
Changes in SCHEMA_VERSION = 85
|
||||
- Add a column `suspended` to the `users` table
|
||||
|
||||
Changes in SCHEMA_VERSION = 86
|
||||
- Added displayname and avatar_url columns to presence_stream
|
||||
"""
|
||||
|
||||
|
||||
SCHEMA_COMPAT_VERSION = (
|
||||
# Transitive links are no longer written to `event_auth_chain_links`
|
||||
84
|
||||
# Added displayname and avatar_url columns to presence_stream
|
||||
86
|
||||
)
|
||||
"""Limit on how far the synapse codebase can be rolled back without breaking db compat
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
--
|
||||
-- This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||
--
|
||||
-- Copyright (C) 2023 New Vector, Ltd
|
||||
--
|
||||
--
|
||||
-- This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||
--
|
||||
-- Copyright (C) 2024 New Vector, Ltd
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU Affero General Public License as
|
||||
-- published by the Free Software Foundation, either version 3 of the
|
||||
-- License, or (at your option) any later version.
|
||||
--
|
||||
-- See the GNU Affero General Public License for more details:
|
||||
-- <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||
|
||||
ALTER TABLE presence_stream ADD COLUMN displayname TEXT;
|
||||
ALTER TABLE presence_stream ADD COLUMN avatar_url TEXT;
|
|
@ -450,6 +450,8 @@ class FilteringTestCase(unittest.HomeserverTestCase):
|
|||
last_user_sync_ts=0,
|
||||
status_msg=None,
|
||||
currently_active=False,
|
||||
displayname=None,
|
||||
avatar_url=None,
|
||||
),
|
||||
]
|
||||
|
||||
|
@ -478,6 +480,8 @@ class FilteringTestCase(unittest.HomeserverTestCase):
|
|||
last_user_sync_ts=0,
|
||||
status_msg=None,
|
||||
currently_active=False,
|
||||
displayname=None,
|
||||
avatar_url=None,
|
||||
),
|
||||
]
|
||||
|
||||
|
|
|
@ -366,6 +366,8 @@ class PresenceUpdateTestCase(unittest.HomeserverTestCase):
|
|||
last_user_sync_ts=1,
|
||||
status_msg="I'm online!",
|
||||
currently_active=True,
|
||||
displayname=None,
|
||||
avatar_url=None,
|
||||
)
|
||||
presence_states.append(presence_state)
|
||||
|
||||
|
@ -718,6 +720,8 @@ class PresenceHandlerInitTestCase(unittest.HomeserverTestCase):
|
|||
last_user_sync_ts=now,
|
||||
status_msg=None,
|
||||
currently_active=True,
|
||||
displayname=None,
|
||||
avatar_url=None,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue