Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Include room avatars in email notifications #10658

Merged
merged 13 commits into from
Sep 1, 2021
1 change: 1 addition & 0 deletions changelog.d/10658.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix a long-standing bug where room avatars were not included in email notifications.
24 changes: 23 additions & 1 deletion synapse/push/mailer.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ async def _fetch_room_state(room_id: str) -> None:
# actually sort our so-called rooms_in_order list, most recent room first
rooms_in_order.sort(key=lambda r: -(notifs_by_room[r][-1]["received_ts"] or 0))

rooms = []
rooms: List[Dict[str, Any]] = []

for r in rooms_in_order:
roomvars = await self._get_room_vars(
Expand Down Expand Up @@ -362,6 +362,7 @@ async def _get_room_vars(
"notifs": [],
"invite": is_invite,
"link": self._make_room_link(room_id),
"avatar_url": await self._get_room_avatar(room_state_ids),
}

if not is_invite:
Expand Down Expand Up @@ -393,6 +394,27 @@ async def _get_room_vars(

return room_vars

async def _get_room_avatar(
self,
room_state_ids: StateMap[str],
) -> Optional[str]:
clokep marked this conversation as resolved.
Show resolved Hide resolved
"""
Retrieve the avatar url for this room---if it exists.

Args:
room_state_ids: The event IDs of the current room state.

Returns:
room's avatar url if it's present and a string; otherwise None.
"""
event_id = room_state_ids.get((EventTypes.RoomAvatar, ""))
if event_id:
ev = await self.store.get_event(event_id)
url = ev.content.get("url")
if isinstance(url, str):
return url
return None

async def _get_notif_vars(
self,
notif: Dict[str, Any],
Expand Down
52 changes: 47 additions & 5 deletions tests/push/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import email.message
import os
from typing import Dict, List, Sequence, Tuple

import attr
import pkg_resources
Expand Down Expand Up @@ -70,9 +71,10 @@ def make_homeserver(self, reactor, clock):
hs = self.setup_test_homeserver(config=config)

# List[Tuple[Deferred, args, kwargs]]
self.email_attempts = []
self.email_attempts: List[Tuple[Deferred, Sequence, Dict]] = []

def sendmail(*args, **kwargs):
# This mocks out synapse.reactor.send_email._sendmail.
d = Deferred()
self.email_attempts.append((d, args, kwargs))
return d
Expand Down Expand Up @@ -255,6 +257,39 @@ def test_multiple_rooms(self):
# We should get emailed about those messages
self._check_for_mail()

def test_room_notifications_include_avatar(self):
# Create a room and set its avatar.
room = self.helper.create_room_as(self.user_id, tok=self.access_token)
self.helper.send_state(
room, "m.room.avatar", {"url": "mxc://DUMMY_MEDIA_ID"}, self.access_token
)

# Invite two other uses.
for other in self.others:
self.helper.invite(
room=room, src=self.user_id, tok=self.access_token, targ=other.id
)
self.helper.join(room=room, user=other.id, tok=other.token)

# The other users send some messages.
# TODO It seems that two messages are required to trigger an email?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's surprising, any idea what's happening there? Or do we do this in other tests?

self.helper.send(room, body="Alpha", tok=self.others[0].token)
self.helper.send(room, body="Beta", tok=self.others[1].token)

# We should get emailed about those messages
args, kwargs = self._check_for_mail()

# That email should contain the room's avatar
msg: bytes = args[5]
# Multipart: plain text, base 64 encoded; html, base 64 encoded
html = (
email.message_from_bytes(msg)
.get_payload()[1]
.get_payload(decode=True)
.decode()
)
self.assertIn("_matrix/media/v1/thumbnail/DUMMY_MEDIA_ID", html)

def test_empty_room(self):
"""All users leaving a room shouldn't cause the pusher to break."""
# Create a simple room with two users
Expand Down Expand Up @@ -344,9 +379,14 @@ def test_no_email_sent_after_removed(self):
pushers = list(pushers)
self.assertEqual(len(pushers), 0)

def _check_for_mail(self):
"""Check that the user receives an email notification"""
def _check_for_mail(self) -> Tuple[Sequence, Dict]:
"""
Assert that synapse sent off exactly one email notification.

Returns:
args and kwargs passed to synapse.reactor.send_email._sendmail for
that notification.
"""
# Get the stream ordering before it gets sent
pushers = self.get_success(
self.hs.get_datastore().get_pushers_by({"user_name": self.user_id})
Expand All @@ -369,8 +409,9 @@ def _check_for_mail(self):
# One email was attempted to be sent
self.assertEqual(len(self.email_attempts), 1)

deferred, sendmail_args, sendmail_kwargs = self.email_attempts[0]
# Make the email succeed
self.email_attempts[0][0].callback(True)
deferred.callback(True)
self.pump()

# One email was attempted to be sent
Expand All @@ -386,3 +427,4 @@ def _check_for_mail(self):

# Reset the attempts.
self.email_attempts = []
return sendmail_args, sendmail_kwargs